Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add type stubs #194

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/coverage-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
run: flake8 src/h3 setup.py tests

- name: Coverage
run: pytest --cov=h3 --full-trace --cov-report=xml
run: pytest --cov=h3 --full-trace --cov-report=xml tests/*.py

- name: Upload coverage to Codecov
uses: codecov/[email protected]
Expand Down
11 changes: 10 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ jobs:
runs-on: ${{ matrix.os }}

strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9]
Expand Down Expand Up @@ -45,4 +46,12 @@ jobs:
pip install .[all]

- name: Tests
run: pytest --cov=h3 --full-trace
run: |
pytest --cov=h3 --full-trace tests/*.py
shell: bash

- name: Type Tests
if: contains('3.6 3.7 3.8 3.9', matrix.python-version) && matrix.os == 'ubuntu-latest'
run: |
pytest --mypy-ini-file=mypy.ini --full-trace tests/*.yml
shell: bash
5 changes: 4 additions & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ purge: clear
-@rm -rf env

test:
env/bin/pytest tests/* --cov=h3 --cov-report term-missing --durations=10
env/bin/pytest tests/*.py --cov=h3 --cov-report term-missing --durations=10

test-types:
env/bin/pytest tests/*.yml term-missing --durations=10 --mypy-ini-file=mypy.ini

lint:
env/bin/flake8 src/h3 setup.py tests
Expand Down
2 changes: 2 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[mypy]
ignore_missing_imports = True
26 changes: 23 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ def long_desc():
return long_description


numpy_requires = ['numpy']
test_requires = [
'pytest', 'pytest-cov', 'flake8', 'pytest-mypy-plugins;python_version>="3.6"']
typing_requires = ['typing_extensions']

setup(
name = 'h3',
version = about['__version__'],
Expand All @@ -33,11 +38,26 @@ def long_desc():
'src',
exclude = ["*.tests", "*.tests.*", "tests.*", "tests"],
),
package_data={
'h3': [
'__init__.pyi',
'api/__init__.pyi',
'api/_api.pyi',
'api/basic_int.pyi',
'api/basic_str.pyi',
'api/numpy_int.pyi',
'py.typed',
'unstable/__init__.pyi',
'unstable/vect.pyi',
]
},
zip_safe=False,
package_dir = {'': 'src'},
cmake_languages = ('C'),
extras_require={
'numpy': ['numpy'],
'test': ['pytest', 'pytest-cov', 'flake8'],
'all': ['numpy', 'pytest', 'pytest-cov', 'flake8'],
'numpy': numpy_requires,
'test': test_requires,
'types': typing_requires,
'all': numpy_requires + test_requires + typing_requires,
},
)
Empty file added src/h3/__init__.pyi
Empty file.
3 changes: 3 additions & 0 deletions src/h3/api/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import basic_int
from . import basic_str
from . import memview_int
82 changes: 82 additions & 0 deletions src/h3/api/_api.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from typing import Dict, Iterable, List, Optional, Set, Tuple, TypeVar

from typing_extensions import Literal

# Static Types
AreaUnits = Literal['km^2', 'm^2', 'rads^2']
LengthUnits = Literal['km', 'm', 'rads']
Resolution = int
GeoBoundary = Tuple[Tuple[float, float], ...]
KRange = int

# Type variables
H3Cell = TypeVar('H3Cell')
H3Edge = TypeVar('H3Edge')
UnorderedH3Cell = TypeVar('UnorderedH3Cell')

def versions() -> Dict[str, str]: ...
def string_to_h3(h: str) -> int: ...
def h3_to_string(x: int) -> str: ...
Comment on lines +18 to +19
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this accept H3Cell as input?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noting this was typed as int because h3_to_string always takes an int, no matter the API

x : int
Unsigned 64-bit integer

def num_hexagons(resolution: Resolution) -> int: ...
def hex_area(resolution: Resolution, unit: AreaUnits = 'km^2') -> float: ...
def edge_length(resolution: Resolution, unit: LengthUnits = 'km') -> float: ...
def h3_is_valid(h: H3Cell) -> bool: ...
def h3_unidirectional_edge_is_valid(edge: H3Edge) -> bool: ...
def geo_to_h3(lat: float, lng: float, resolution: Resolution) -> H3Cell: ...
def h3_to_geo(h: H3Cell) -> Tuple[float, float]: ...
def h3_get_resolution(h: H3Cell) -> int: ...
def h3_to_parent(h: H3Cell, res: Optional[Resolution] = None) -> int: ...
def h3_distance(h1: H3Cell, h2: H3Cell) -> int: ...
def h3_to_geo_boundary(h: H3Cell, geo_json: bool = False) -> GeoBoundary: ...
def k_ring(h: H3Cell, k: KRange = 1) -> UnorderedH3Cell: ...
def hex_range(h: H3Cell, k: KRange = 1) -> UnorderedH3Cell: ...
def hex_ring(h: H3Cell, k: KRange = 1) -> UnorderedH3Cell: ...

# TODO: fix these
# I tried a return value of Ordered[Unordered[H3Cell]] but didn't
# work. Have not gotten return values of Generic[Generic] to work
def hex_range_distances(h: H3Cell, K: KRange) -> List[Set[H3Cell]]: ...
def hex_ranges(
hexes: Iterable[H3Cell], K: KRange
) -> Dict[H3Cell, List[Set[H3Cell]]]: ...
def k_ring_distances(h: H3Cell, K: KRange) -> List[Set[H3Cell]]: ...
def h3_to_children(h: H3Cell, res: Optional[Resolution] = None) -> UnorderedH3Cell: ...
def compact(hexes: Iterable[H3Cell]) -> UnorderedH3Cell: ...
def uncompact(hexes: Iterable[H3Cell], res: Resolution) -> UnorderedH3Cell: ...

# Check this
def h3_set_to_multi_polygon(
hexes: Iterable[H3Cell], geo_json: bool = False
) -> List[List[Tuple[float, float]]]: ...

# def polyfill_polygon(outer, res, holes=None, lnglat_order=False) -> : ...
#
# def polyfill_geojson(geojson, res) -> : ...

# def polyfill(geojson, res: int, geo_json_conformant=False) -> : ...

def h3_is_pentagon(h: H3Cell) -> bool: ...
def h3_get_base_cell(h: H3Cell) -> int: ...
def h3_indexes_are_neighbors(h1: H3Cell, h2: H3Cell) -> bool: ...
def get_h3_unidirectional_edge(origin: H3Cell, destination: H3Cell) -> H3Edge: ...
def get_origin_h3_index_from_unidirectional_edge(e: H3Edge) -> H3Cell: ...
def get_destination_h3_index_from_unidirectional_edge(e: H3Edge) -> H3Cell: ...
def get_h3_indexes_from_unidirectional_edge(e: H3Edge) -> Tuple[H3Cell, H3Cell]: ...
def get_h3_unidirectional_edges_from_hexagon(origin: H3Cell) -> Set[H3Edge]: ...
def get_h3_unidirectional_edge_boundary(
edge: H3Edge, geo_json: bool = False
) -> GeoBoundary: ...
def h3_line(start: H3Cell, end: H3Cell) -> List[H3Cell]: ...
def h3_is_res_class_III(h: H3Cell) -> bool: ...
def h3_is_res_class_iii(h: H3Cell) -> bool: ...
def get_pentagon_indexes(resolution: Resolution) -> Set[H3Cell]: ...
def get_res0_indexes() -> Set[H3Cell]: ...
def h3_to_center_child(h: H3Cell, res: Optional[Resolution] = None) -> H3Cell: ...
def h3_get_faces(h: H3Cell) -> Set[int]: ...
def experimental_h3_to_local_ij(origin: H3Cell, h: H3Cell) -> Tuple[int, int]: ...
def experimental_local_ij_to_h3(origin: H3Cell, i: int, j: int) -> H3Cell: ...
def cell_area(h: H3Cell, unit: AreaUnits = 'km^2') -> float: ...
def exact_edge_length(e: H3Edge, unit: LengthUnits = 'km') -> float: ...
def point_dist(
point1: Tuple[float, float], point2: Tuple[float, float], unit: LengthUnits = 'km'
) -> float: ...
84 changes: 84 additions & 0 deletions src/h3/api/basic_int.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from typing import Dict, Iterable, List, Optional, Set, Tuple

from typing_extensions import Literal

from . import _api

H3Cell = int
H3Edge = int
UnorderedH3Cell = Set[H3Cell]

# Should be no changes in each API file below this line

AreaUnits = Literal['km^2', 'm^2', 'rads^2']
LengthUnits = Literal['km', 'm', 'rads']
Resolution = int
GeoBoundary = Tuple[Tuple[float, float], ...]
KRange = int

def versions() -> Dict[str, str]: ...
def string_to_h3(h: str) -> int: ...
def h3_to_string(x: int) -> str: ...
def num_hexagons(resolution: Resolution) -> int: ...
def hex_area(resolution: Resolution, unit: AreaUnits = 'km^2') -> float: ...
Comment on lines +19 to +23
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to avoid repeating the entire API definition after defining H3Cell each time? (As below in the PR?)

def edge_length(resolution: Resolution, unit: LengthUnits = 'km') -> float: ...
def h3_is_valid(h: H3Cell) -> bool: ...
def h3_unidirectional_edge_is_valid(edge: H3Edge) -> bool: ...
def geo_to_h3(lat: float, lng: float, resolution: Resolution) -> H3Cell: ...
def h3_to_geo(h: H3Cell) -> Tuple[float, float]: ...
def h3_get_resolution(h: H3Cell) -> int: ...
def h3_to_parent(h: H3Cell, res: Optional[Resolution] = None) -> int: ...
def h3_distance(h1: H3Cell, h2: H3Cell) -> int: ...
def h3_to_geo_boundary(h: H3Cell, geo_json: bool = False) -> GeoBoundary: ...
def k_ring(h: H3Cell, k: KRange = 1) -> UnorderedH3Cell: ...
def hex_range(h: H3Cell, k: KRange = 1) -> UnorderedH3Cell: ...
def hex_ring(h: H3Cell, k: KRange = 1) -> UnorderedH3Cell: ...

# TODO: fix these
# I tried a return value of Ordered[Unordered[H3Cell]] but didn't
# work. Have not gotten return values of Generic[Generic] to work
def hex_range_distances(h: H3Cell, K: KRange) -> List[Set[H3Cell]]: ...
def hex_ranges(
hexes: Iterable[H3Cell], K: KRange
) -> Dict[H3Cell, List[Set[H3Cell]]]: ...
def k_ring_distances(h: H3Cell, K: KRange) -> List[Set[H3Cell]]: ...
def h3_to_children(h: H3Cell, res: Optional[Resolution] = None) -> UnorderedH3Cell: ...
def compact(hexes: Iterable[H3Cell]) -> UnorderedH3Cell: ...
def uncompact(hexes: Iterable[H3Cell], res: Resolution) -> UnorderedH3Cell: ...

# Check this
def h3_set_to_multi_polygon(
hexes: Iterable[H3Cell], geo_json: bool = False
) -> List[List[Tuple[float, float]]]: ...

# def polyfill_polygon(outer, res, holes=None, lnglat_order=False) -> : ...
#
# def polyfill_geojson(geojson, res) -> : ...

# def polyfill(geojson, res: int, geo_json_conformant=False) -> : ...

def h3_is_pentagon(h: H3Cell) -> bool: ...
def h3_get_base_cell(h: H3Cell) -> int: ...
def h3_indexes_are_neighbors(h1: H3Cell, h2: H3Cell) -> bool: ...
def get_h3_unidirectional_edge(origin: H3Cell, destination: H3Cell) -> H3Edge: ...
def get_origin_h3_index_from_unidirectional_edge(e: H3Edge) -> H3Cell: ...
def get_destination_h3_index_from_unidirectional_edge(e: H3Edge) -> H3Cell: ...
def get_h3_indexes_from_unidirectional_edge(e: H3Edge) -> Tuple[H3Cell, H3Cell]: ...
def get_h3_unidirectional_edges_from_hexagon(origin: H3Cell) -> Set[H3Edge]: ...
def get_h3_unidirectional_edge_boundary(
edge: H3Edge, geo_json: bool = False
) -> GeoBoundary: ...
def h3_line(start: H3Cell, end: H3Cell) -> List[H3Cell]: ...
def h3_is_res_class_III(h: H3Cell) -> bool: ...
def h3_is_res_class_iii(h: H3Cell) -> bool: ...
def get_pentagon_indexes(resolution: Resolution) -> Set[H3Cell]: ...
def get_res0_indexes() -> Set[H3Cell]: ...
def h3_to_center_child(h: H3Cell, res: Optional[Resolution] = None) -> H3Cell: ...
def h3_get_faces(h: H3Cell) -> Set[int]: ...
def experimental_h3_to_local_ij(origin: H3Cell, h: H3Cell) -> Tuple[int, int]: ...
def experimental_local_ij_to_h3(origin: H3Cell, i: int, j: int) -> H3Cell: ...
def cell_area(h: H3Cell, unit: AreaUnits = 'km^2') -> float: ...
def exact_edge_length(e: H3Edge, unit: LengthUnits = 'km') -> float: ...
def point_dist(
point1: Tuple[float, float], point2: Tuple[float, float], unit: LengthUnits = 'km'
) -> float: ...
84 changes: 84 additions & 0 deletions src/h3/api/basic_str.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from typing import Dict, Iterable, List, Optional, Set, Tuple

from typing_extensions import Literal

from . import _api

H3Cell = str
H3Edge = str
UnorderedH3Cell = Set[H3Cell]

# Should be no changes in each API file below this line

AreaUnits = Literal['km^2', 'm^2', 'rads^2']
LengthUnits = Literal['km', 'm', 'rads']
Resolution = int
GeoBoundary = Tuple[Tuple[float, float], ...]
KRange = int

def versions() -> Dict[str, str]: ...
def string_to_h3(h: str) -> int: ...
def h3_to_string(x: int) -> str: ...
def num_hexagons(resolution: Resolution) -> int: ...
def hex_area(resolution: Resolution, unit: AreaUnits = 'km^2') -> float: ...
def edge_length(resolution: Resolution, unit: LengthUnits = 'km') -> float: ...
def h3_is_valid(h: H3Cell) -> bool: ...
def h3_unidirectional_edge_is_valid(edge: H3Edge) -> bool: ...
def geo_to_h3(lat: float, lng: float, resolution: Resolution) -> H3Cell: ...
def h3_to_geo(h: H3Cell) -> Tuple[float, float]: ...
def h3_get_resolution(h: H3Cell) -> int: ...
def h3_to_parent(h: H3Cell, res: Optional[Resolution] = None) -> int: ...
def h3_distance(h1: H3Cell, h2: H3Cell) -> int: ...
def h3_to_geo_boundary(h: H3Cell, geo_json: bool = False) -> GeoBoundary: ...
def k_ring(h: H3Cell, k: KRange = 1) -> UnorderedH3Cell: ...
def hex_range(h: H3Cell, k: KRange = 1) -> UnorderedH3Cell: ...
def hex_ring(h: H3Cell, k: KRange = 1) -> UnorderedH3Cell: ...

# TODO: fix these
# I tried a return value of Ordered[Unordered[H3Cell]] but didn't
# work. Have not gotten return values of Generic[Generic] to work
def hex_range_distances(h: H3Cell, K: KRange) -> List[Set[H3Cell]]: ...
def hex_ranges(
hexes: Iterable[H3Cell], K: KRange
) -> Dict[H3Cell, List[Set[H3Cell]]]: ...
def k_ring_distances(h: H3Cell, K: KRange) -> List[Set[H3Cell]]: ...
def h3_to_children(h: H3Cell, res: Optional[Resolution] = None) -> UnorderedH3Cell: ...
def compact(hexes: Iterable[H3Cell]) -> UnorderedH3Cell: ...
def uncompact(hexes: Iterable[H3Cell], res: Resolution) -> UnorderedH3Cell: ...

# Check this
def h3_set_to_multi_polygon(
hexes: Iterable[H3Cell], geo_json: bool = False
) -> List[List[Tuple[float, float]]]: ...

# def polyfill_polygon(outer, res, holes=None, lnglat_order=False) -> : ...
#
# def polyfill_geojson(geojson, res) -> : ...

# def polyfill(geojson, res: int, geo_json_conformant=False) -> : ...

def h3_is_pentagon(h: H3Cell) -> bool: ...
def h3_get_base_cell(h: H3Cell) -> int: ...
def h3_indexes_are_neighbors(h1: H3Cell, h2: H3Cell) -> bool: ...
def get_h3_unidirectional_edge(origin: H3Cell, destination: H3Cell) -> H3Edge: ...
def get_origin_h3_index_from_unidirectional_edge(e: H3Edge) -> H3Cell: ...
def get_destination_h3_index_from_unidirectional_edge(e: H3Edge) -> H3Cell: ...
def get_h3_indexes_from_unidirectional_edge(e: H3Edge) -> Tuple[H3Cell, H3Cell]: ...
def get_h3_unidirectional_edges_from_hexagon(origin: H3Cell) -> Set[H3Edge]: ...
def get_h3_unidirectional_edge_boundary(
edge: H3Edge, geo_json: bool = False
) -> GeoBoundary: ...
def h3_line(start: H3Cell, end: H3Cell) -> List[H3Cell]: ...
def h3_is_res_class_III(h: H3Cell) -> bool: ...
def h3_is_res_class_iii(h: H3Cell) -> bool: ...
def get_pentagon_indexes(resolution: Resolution) -> Set[H3Cell]: ...
def get_res0_indexes() -> Set[H3Cell]: ...
def h3_to_center_child(h: H3Cell, res: Optional[Resolution] = None) -> H3Cell: ...
def h3_get_faces(h: H3Cell) -> Set[int]: ...
def experimental_h3_to_local_ij(origin: H3Cell, h: H3Cell) -> Tuple[int, int]: ...
def experimental_local_ij_to_h3(origin: H3Cell, i: int, j: int) -> H3Cell: ...
def cell_area(h: H3Cell, unit: AreaUnits = 'km^2') -> float: ...
def exact_edge_length(e: H3Edge, unit: LengthUnits = 'km') -> float: ...
def point_dist(
point1: Tuple[float, float], point2: Tuple[float, float], unit: LengthUnits = 'km'
) -> float: ...
Loading