diff --git a/.github/workflows/mypy-test.yaml b/.github/workflows/mypy-test.yaml new file mode 100644 index 000000000..92c8e2b2a --- /dev/null +++ b/.github/workflows/mypy-test.yaml @@ -0,0 +1,20 @@ +name: Mypy + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + name: Mypy + steps: + - uses: actions/checkout@v1 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.11 + - name: Install Dependencies + run: | + python3 -m pip install -r requirements-types.txt + - name: mypy + run: | + mypy fiona tests \ No newline at end of file diff --git a/fiona/__init__.pyi b/fiona/__init__.pyi new file mode 100644 index 000000000..c987b0b3a --- /dev/null +++ b/fiona/__init__.pyi @@ -0,0 +1,72 @@ +from io import ( + BufferedReader, + BytesIO, +) +from fiona._env import GDALVersion +from fiona.collection import Collection +from fiona.crs import CRS +from fiona.env import Env +from fiona.model import Geometry +from fiona.rfc3339 import FionaDateType + +from pathlib import PosixPath +from typing import ( + Any, + Dict, + List, + Optional, + Tuple, + Type, + Union, +) + +gdal_version: GDALVersion + +def bounds( + ob: Union[Dict[str, Union[str, List[List[List[int]]]]], Geometry, Dict[str, Union[str, List[List[int]]]], Dict[str, Union[Dict[str, Union[str, List[List[List[float]]]]], str, Dict[str, Optional[Union[float, str, int]]]]], Dict[str, Union[str, List[int]]]] +) -> Union[Tuple[int, int, int, int], Tuple[float, float, float, float]]: ... + + +def drivers(*args, **kwargs) -> Env: ... + + +def listdir(path: Union[int, str]) -> List[str]: ... + + +def listlayers( + fp: Union[BufferedReader, str, int, PosixPath], + vfs: Optional[Union[str, int]] = ..., + **kwargs +) -> List[str]: ... + + +def open( + fp: Union[BytesIO, str, PosixPath, BufferedReader], + mode: str = ..., + driver: Optional[str] = ..., + schema: Optional[Any] = ..., + crs: Optional[Union[CRS, Dict[str, Union[str, bool]], Dict[str, Union[int, bool, str]], str]] = ..., + encoding: Optional[str] = ..., + layer: Optional[Union[str, int]] = ..., + vfs: Optional[str] = ..., + enabled_drivers: Optional[List[str]] = ..., + crs_wkt: Optional[str] = ..., + allow_unsupported_drivers: bool = ..., + **kwargs +) -> Collection: ... + + +def prop_type(text: str) -> Union[Type[int], Type[float], Type[str], Type[FionaDateType]]: ... + + +def prop_width(val: str) -> Optional[int]: ... + + +def remove( + path_or_collection: Union[Collection, str], + driver: Optional[str] = ..., + layer: Optional[Union[str, int]] = ... +) -> None: ... + + +supported_drivers: Dict[str, str] diff --git a/fiona/_env.pyi b/fiona/_env.pyi new file mode 100644 index 000000000..6d3745152 --- /dev/null +++ b/fiona/_env.pyi @@ -0,0 +1,23 @@ +from typing import NamedTuple + + +class GDALDataFinder: + ... + +class GDALVersion(NamedTuple): + major: int + minor: int + revision: int + +class PROJDataFinder: + ... + +def calc_gdal_version_num(maj: int, min: int, rev: int) -> int: ... + +def get_gdal_release_name() -> str: ... + +def get_gdal_version_num() -> int: ... + +def get_gdal_version_tuple() -> GDALVersion: ... + +def get_proj_version_tuple() -> tuple[int, int, int]: ... \ No newline at end of file diff --git a/fiona/_geometry.pyi b/fiona/_geometry.pyi new file mode 100644 index 000000000..75d08db5f --- /dev/null +++ b/fiona/_geometry.pyi @@ -0,0 +1,8 @@ +from typing import Any + + +class GeomBuilder: + def build_from_feature(self, feature: Any) -> Any: ... + + +def geometryRT(geom) -> Any: ... diff --git a/fiona/collection.pyi b/fiona/collection.pyi new file mode 100644 index 000000000..fdc313510 --- /dev/null +++ b/fiona/collection.pyi @@ -0,0 +1,100 @@ +from fiona.crs import CRS +from fiona.model import Feature +from fiona.ogrext import ( + ItemsIterator, + Iterator, + KeysIterator, +) +from fiona.path import ( + ParsedPath, + UnparsedPath, +) +from typing import ( + Any, + Dict, + List, + Optional, + Set, + Tuple, + Union, +) + + +def _get_valid_geom_types(schema: Dict[str, Any], driver: str) -> Set[str]: ... + + +def get_filetype(bytesbuf: bytes) -> str: ... + + +class BytesCollection: + def __init__(self, bytesbuf: Union[bytes, str], **kwds) -> None: ... + def __repr__(self) -> str: ... + def close(self) -> None: ... + + +class Collection: + def __contains__(self, fid: int) -> bool: ... + def __del__(self) -> None: ... + def __enter__(self) -> Union[Collection, BytesCollection]: ... + def __exit__(self, type: None, value: None, traceback: None) -> None: ... + def __getitem__(self, item: Union[int, slice]) -> Union[Feature, List[Feature]]: ... + def __init__( + self, + path: Union[UnparsedPath, int, str, ParsedPath], + mode: Union[int, str] = ..., + driver: Optional[Union[str, int]] = ..., + schema: Optional[Any] = ..., + crs: Optional[Any] = ..., + encoding: Optional[Union[str, int]] = ..., + layer: Optional[Union[str, int, float]] = ..., + vsi: Optional[str] = ..., + archive: Optional[int] = ..., + enabled_drivers: Optional[List[str]] = ..., + crs_wkt: Optional[str] = ..., + ignore_fields: Optional[Union[List[int], List[str]]] = ..., + ignore_geometry: bool = ..., + include_fields: Optional[Union[Tuple[()], List[str]]] = ..., + wkt_version: Optional[str] = ..., + allow_unsupported_drivers: bool = ..., + **kwargs + ) -> None: ... + def __iter__(self) -> Iterator: ... + def __len__(self) -> int: ... + def __next__(self) -> Feature: ... + def __repr__(self) -> str: ... + def _check_schema_driver_support(self) -> None: ... + @property + def bounds(self) -> Tuple[float, float, float, float]: ... + def close(self) -> None: ... + @property + def closed(self) -> bool: ... + @property + def crs(self) -> Optional[CRS]: ... + @property + def crs_wkt(self) -> str: ... + @property + def driver(self) -> Optional[str]: ... + def filter(self, *args, **kwds) -> Iterator: ... + def flush(self) -> None: ... + def get(self, item: int) -> Feature: ... + def get_tag_item(self, key: str, ns: Optional[str] = ...) -> Optional[str]: ... + def guard_driver_mode(self) -> None: ... + def items(self, *args, **kwds) -> ItemsIterator: ... + def keys(self, *args, **kwds) -> KeysIterator: ... + @property + def meta(self) -> Dict[str, Union[str, Dict[str, Union[str, Dict[str, str]]], CRS]]: ... + @property + def schema(self) -> Any: ... + def tags(self, ns: Optional[str] = ...) -> Dict[str, str]: ... + def update_tag_item(self, key: str, tag: str, ns: Optional[str] = ...) -> int: ... + def update_tags(self, tags: Dict[str, str], ns: Optional[str] = ...) -> int: ... + def validate_record(self, record: Dict[str, Dict[str, Union[str, Tuple[float, float]]]]) -> bool: ... + def validate_record_geometry(self, record: Dict[str, Dict[str, Union[str, Tuple[float, float]]]]) -> bool: ... + def write( + self, + record: Union[Feature, Dict[str, Union[str, Dict[str, Union[str, Tuple[int, int]]], Dict[str, str]]]] + ) -> None: ... + def writerecords(self, records: Any) -> None: ... + + +supported_drivers: dict diff --git a/fiona/compat.pyi b/fiona/compat.pyi new file mode 100644 index 000000000..00c468806 --- /dev/null +++ b/fiona/compat.pyi @@ -0,0 +1 @@ +def strencode(instr: str, encoding: str = ...) -> bytes: ... diff --git a/fiona/crs.pyi b/fiona/crs.pyi new file mode 100644 index 000000000..d07d10219 --- /dev/null +++ b/fiona/crs.pyi @@ -0,0 +1,57 @@ +from typing import Literal, Tuple, Optional, TypedDict, Union + + +class CRS: + @property + def data(self) -> dict: ... + @property + def is_valid(self) -> bool: ... + @property + def is_epsg_code(self) -> bool: ... + @property + def wkt(self) -> str: ... + @property + def is_geographic(self) -> bool: ... + @property + def is_projected(self) -> bool: ... + @property + def linear_units(self) -> str: ... + @property + def linear_units_factor(self) -> Tuple[str, float]: ... + @property + def units_factor(self) -> Tuple[str, float]: ... + def to_dict(self, projjson: bool = False) -> dict: ... + def to_proj4(self) -> str: ... + def to_wkt(self, morph_to_esri_dialect: bool = False, version: Optional[str] = None) -> str: ... + def to_epsg(self, confidence_threshold: int = 70) -> Optional[int]: ... + def to_authority(self, confidence_threshold: int = 70) -> Optional[Tuple[str, str]]: ... + def _matches(self, confidence_threshold: int = 70) -> dict: ... + def to_string(self) -> str: ... + @staticmethod + def from_epsg(code: int) -> "CRS": ... + @staticmethod + def from_proj4(proj: str) -> "CRS": ... + @staticmethod + def from_dict(initialdata: Optional[dict] = None, **kwargs) -> "CRS": ... + @staticmethod + def from_wkt(wkt: str, morph_from_esri_dialect: bool = False) -> "CRS": ... + @staticmethod + def from_user_input(value: object, morph_from_esri_dialect: bool = False) -> "CRS": ... + @staticmethod + def from_authority(auth_name: str, code: Union[int, str]) -> "CRS": ... + @staticmethod + def from_string(value: str, morph_from_esri_dialect: bool = False) -> "CRS": ... + + +class CRSDict(TypedDict): + init: str + no_defs: Literal[True] + + +def from_epsg(code: Union[int, str]) -> CRSDict: ... + + +def from_string(prjs: str) -> dict: ... + + +def to_string(crs: dict) -> str: ... \ No newline at end of file diff --git a/fiona/drvsupport.py b/fiona/drvsupport.py index edd534c97..e35872b05 100644 --- a/fiona/drvsupport.py +++ b/fiona/drvsupport.py @@ -1,4 +1,5 @@ import os +from typing import Dict from fiona.env import Env from fiona._env import get_gdal_version_tuple @@ -13,7 +14,7 @@ # entries for each at https://gdal.org/drivers/vector/index.html to screen # out the multi-layer formats. -supported_drivers = dict( +supported_drivers: Dict[str, str] = dict( [ # OGR Vector Formats # Format Name Code Creation Georeferencing Compiled by default diff --git a/fiona/drvsupport.pyi b/fiona/drvsupport.pyi new file mode 100644 index 000000000..3a39c2095 --- /dev/null +++ b/fiona/drvsupport.pyi @@ -0,0 +1,37 @@ +from fiona.path import UnparsedPath +from typing import ( + Dict, + Union, + Any +) + + +def _driver_converts_field_type_silently_to_str(driver: str, field_type: str) -> bool: ... + + +def _driver_supports_field(driver: str, field_type: str) -> bool: ... + + +def _driver_supports_timezones(driver: str, field_type: str) -> bool: ... + + +def driver_from_extension(path: Union[str, UnparsedPath]) -> str: ... + + +def vector_driver_extensions() -> Dict[str, str]: ... + + +def _driver_supports_milliseconds(driver: str) -> bool: ... + + +def _driver_supports_mode(driver: str, mode: Any) -> bool: ... + + +supported_drivers: Dict[str, str] + +driver_mode_mingdal: Dict[str, dict[str, Any]] + +_driver_converts_to_str: Dict[str, dict[str, Any]] + + + diff --git a/fiona/enums.pyi b/fiona/enums.pyi new file mode 100644 index 000000000..05b719058 --- /dev/null +++ b/fiona/enums.pyi @@ -0,0 +1,3 @@ +class WktVersion: + @classmethod + def _missing_(cls, value: str): ... diff --git a/fiona/env.pyi b/fiona/env.pyi new file mode 100644 index 000000000..8aed4d9e2 --- /dev/null +++ b/fiona/env.pyi @@ -0,0 +1,76 @@ +from boto3.session import Session +from fiona._env import ( + calc_gdal_version_num as calc_gdal_version_num, # mypy treats this as explicit export + get_gdal_version_num as get_gdal_version_num # mypy treats this as explicit export +) +from fiona.session import ( + AWSSession, + DummySession, + GSSession, +) +from typing import ( + Callable, + Dict, + Optional, + Union, +) + + +def defenv(**options) -> None: ... + + +def delenv() -> None: ... + + +def ensure_env(f: Callable) -> Callable: ... + + +def ensure_env_with_credentials(f: Callable) -> Callable: ... + + +def env_ctx_if_needed() -> Union[NullContextManager, Env]: ... + + +def getenv() -> Dict[str, Union[str, bool]]: ... + + +def hasenv() -> bool: ... + + +def setenv(**options) -> None: ... + + +class Env: + def __enter__(self) -> Env: ... + def __exit__(self, exc_type: None = ..., exc_val: None = ..., exc_tb: None = ...) -> None: ... + def __init__( + self, + session: Optional[Union[GSSession, AWSSession, DummySession, Session]] = ..., + aws_unsigned: bool = ..., + profile_name: None = ..., + session_class: Callable = ..., + **options + ) -> None: ... + def credentialize(self) -> None: ... + @classmethod + def default_options(cls) -> Dict[str, bool]: ... + @classmethod + def from_defaults(cls, *args, **kwargs) -> Env: ... + +GDALVersionLike = Union[str, tuple[int, int], GDALVersion] + +class GDALVersion: + major: int + minor: int + + def at_least(self, other: GDALVersionLike) -> bool: ... + @classmethod + def parse(cls, input: GDALVersionLike) -> GDALVersion: ... + @classmethod + def runtime(cls) -> GDALVersion: ... + + +class NullContextManager: + def __enter__(self) -> NullContextManager: ... + def __exit__(self, *args) -> None: ... + def __init__(self) -> None: ... diff --git a/fiona/fio/helpers.pyi b/fiona/fio/helpers.pyi new file mode 100644 index 000000000..55ccb3bd5 --- /dev/null +++ b/fiona/fio/helpers.pyi @@ -0,0 +1,27 @@ +from click.testing import _NamedTextIOWrapper +from typing import ( + Any, + Dict, + Iterator, + List, + Optional, + Tuple, + Union, +) +from fiona.model import Geometry + + +def eval_feature_expression( + feature: Dict[str, Union[Dict[str, Union[str, List[List[List[float]]]]], str, Dict[str, Optional[Union[float, str, int]]]]], + expression: str +) -> Union[float, bool]: ... + + +def make_ld_context(context_items: Tuple[str]) -> Dict[str, Union[Dict[str, Union[str, Dict[str, str]]], str]]: ... + + +def obj_gen(lines: _NamedTextIOWrapper, object_hook: None = ...) -> Iterator[Any]: ... + + +RecRoundObj = Union[Geometry, int, float, List[Union[Geometry, int, float]]] +def recursive_round(obj: RecRoundObj, precision: float) -> RecRoundObj: ... \ No newline at end of file diff --git a/fiona/fio/main.pyi b/fiona/fio/main.pyi new file mode 100644 index 000000000..b549626fa --- /dev/null +++ b/fiona/fio/main.pyi @@ -0,0 +1,11 @@ +def configure_logging(verbosity: int) -> None: ... + + +def main_group( + ctx, + verbose: bool, + quiet: bool, + aws_profile: str, + aws_no_sign_requests: bool, + aws_requester_pays: bool, +) -> None: ... diff --git a/fiona/fio/options.pyi b/fiona/fio/options.pyi new file mode 100644 index 000000000..2dce6e3a9 --- /dev/null +++ b/fiona/fio/options.pyi @@ -0,0 +1,36 @@ +from click.core import ( + Context, + Option, +) +from click.decorators import FC +from typing import ( + DefaultDict, + Dict, + List, + Optional, + Tuple, + Union, + Callable, +) + + +def cb_key_val(ctx: Context, param: Option, value: Union[Tuple[str], Tuple[()]]) -> Dict[str, str]: ... + + +def cb_layer(ctx: Context, param: Option, value: Optional[str]) -> Optional[Union[str, int]]: ... + + +def cb_multilayer( + ctx: Context, + param: Option, + value: Union[Tuple[str], Tuple[()]] +) -> DefaultDict[str, List[str]]: ... + + +def validate_multilayer_file_index(files: Tuple[str], layerdict: DefaultDict[str, List[str]]) -> None: ... + + +src_crs_opt: Callable[[FC], FC] +dst_crs_opt: Callable[[FC], FC] +creation_opt: Callable[[FC], FC] +open_opt: Callable[[FC], FC] diff --git a/fiona/io.pyi b/fiona/io.pyi new file mode 100644 index 000000000..b587f8224 --- /dev/null +++ b/fiona/io.pyi @@ -0,0 +1,50 @@ +from io import BufferedReader +from fiona.collection import Collection +from fiona.crs import CRS +from typing import ( + Any, + List, + Optional, + Union, +) + + +class MemoryFile: + def __enter__(self) -> Union[MemoryFile, ZipMemoryFile]: ... + def __exit__(self, *args, **kwargs) -> None: ... + def __init__( + self, + file_or_bytes: Optional[Union[bytes, BufferedReader]] = ..., + filename: Optional[str] = ..., + ext: str = ... + ) -> None: ... + def listdir(self, path: None = ...) -> List[str]: ... + def listlayers(self, path: None = ...) -> List[str]: ... + def open( + self, + mode: Optional[str] = ..., + driver: Optional[str] = ..., + schema: Optional[Any] = ..., + crs: Optional[CRS] = ..., + encoding: None = ..., + layer: Optional[str] = ..., + vfs: None = ..., + enabled_drivers: None = ..., + crs_wkt: Optional[str] = ..., + allow_unsupported_drivers: bool = ..., + **kwargs + ) -> Collection: ... + + +class ZipMemoryFile: + def __init__(self, file_or_bytes: Optional[bytes] = ..., filename: Optional[str] = ..., ext: str = ...) -> None: ... + def open( + self, + path: Optional[str] = ..., + driver: None = ..., + encoding: None = ..., + layer: None = ..., + enabled_drivers: None = ..., + allow_unsupported_drivers: bool = ..., + **kwargs + ) -> Collection: ... diff --git a/fiona/logutils.pyi b/fiona/logutils.pyi new file mode 100644 index 000000000..5f790bda2 --- /dev/null +++ b/fiona/logutils.pyi @@ -0,0 +1,16 @@ +from logging import ( + LogRecord, + RootLogger, +) +from typing import Union + + +class FieldSkipLogFilter: + def __init__(self, name: str = ...) -> None: ... + def filter(self, record: LogRecord) -> Union[int, bool]: ... + + +class LogFiltering: + def __enter__(self) -> None: ... + def __exit__(self, *args, **kwargs) -> None: ... + def __init__(self, logger: RootLogger, filter: FieldSkipLogFilter) -> None: ... diff --git a/fiona/meta.pyi b/fiona/meta.pyi new file mode 100644 index 000000000..5b1d41410 --- /dev/null +++ b/fiona/meta.pyi @@ -0,0 +1,36 @@ +from typing import ( + Dict, + List, + Optional, + Union, +) + + +def _parse_options(xml: str) -> Dict[str, Union[Dict[str, Union[str, List[str]]], Dict[str, str]]]: ... + + +def dataset_creation_options(driver: str) -> Dict[str, Union[Dict[str, Union[str, List[str]]], Dict[str, str]]]: ... + + +def dataset_open_options(driver: str) -> Dict[str, Union[Dict[str, Union[str, List[str]]], Dict[str, str]]]: ... + + +def extension(driver: str) -> Optional[str]: ... + + +def extensions(driver: str) -> Optional[List[str]]: ... + + +def layer_creation_options(driver: str) -> Dict[str, Union[Dict[str, Union[str, List[str]]], Dict[str, str]]]: ... + + +def print_driver_options(driver: str) -> None: ... + + +def supported_field_types(driver: str) -> Optional[List[str]]: ... + + +def supported_sub_field_types(driver: str) -> Optional[List[str]]: ... + + +def supports_vsi(driver: str) -> bool: ... diff --git a/fiona/model.pyi b/fiona/model.pyi new file mode 100644 index 000000000..ea9aa6428 --- /dev/null +++ b/fiona/model.pyi @@ -0,0 +1,169 @@ +from __future__ import annotations # for Python 3.7-3.9 + +from collections import OrderedDict +from itertools import chain +from typing import ( + Any, + Dict, + List, + Literal, + Optional, + Tuple, + Union, + Protocol, +) +from typing_extensions import NotRequired, Required, TypedDict # for Python <3.11 with (Not)Required + + +def decode_object(obj: Any) -> Union[Dict[str, str], Dict[str, int], Geometry, Feature]: ... + + +def to_dict( + val: Dict[str, Union[Dict[str, Union[str, List[List[List[float]]]]], str, Dict[str, Optional[Union[float, str, int]]]]] +) -> Dict[str, Union[Dict[str, Union[str, List[List[List[float]]]]], str, Dict[str, Optional[Union[float, str, int]]]]]: ... + + +GeoJSONPosition = tuple[float, float] | tuple[float, float, float] +GeoJSONLineStringCoordinateArray = list[GeoJSONPosition] # two or more positions +GeoJSONLinearRing = list[GeoJSONPosition] # closed with four or more positions +GeoJSONPolygonCoordinateArray = list[GeoJSONLinearRing] + + +class GeoJSONPoint(TypedDict): + type: Required[Literal["Point"]] + coordinates: Required[GeoJSONPosition] + + +class GeoJSONMultiPoint(TypedDict): + type: Literal["MultiPoint"] + coordinates: list[GeoJSONPosition] + + +class GeoJSONLineString(TypedDict): + type: Literal["LineString"] + coordinates: GeoJSONLineStringCoordinateArray + + +class GeoJSONMultiLineString(TypedDict): + type: Literal["MultiLineString"] + coordinates: list[GeoJSONLineStringCoordinateArray] + + +class GeoJSONPolygon(TypedDict): + type: Literal["Polygon"] + coordinates: GeoJSONPolygonCoordinateArray + + +class GeoJSONMultiPolygon(TypedDict): + type: Literal["MultiPolygon"] + coordinates: list[GeoJSONPolygonCoordinateArray] + + +GeoJSONGeometry = GeoJSONPoint | GeoJSONMultiPoint | GeoJSONLineString | GeoJSONMultiLineString | GeoJSONPolygon | GeoJSONMultiPolygon + + +class GeoJSONGeometryCollection(TypedDict): + geometries: list[GeoJSONGeometry] + + +class GeoJSONFeature(TypedDict): + type: Literal["Feature"] + geometry: GeoJSONGeometry | None + properties: dict | None + id: NotRequired[str | float] + + +class GeoJSONFeatureCollection(TypedDict): + type: Literal["FeatureCollection"] + features: list[GeoJSONFeature] + + +class GeoInterface(Protocol): + @property + def __geo_interface__(self) -> dict: ... + + +class Feature: + def __init__( + self, + geometry: Optional[Geometry] = ..., + id: Optional[Union[int, str]] = ..., + properties: Optional[Properties] = ..., + **data + ) -> None: ... + @classmethod + def from_dict( + cls, + ob: Optional[Union[Dict[str, object], GeoInterface]] = None, + # NOTE: Make ob positional only as per https://github.com/python/mypy/issues/15321#issuecomment-1567408831 + # It is unlikely that people are using from_dict(ob=...) + /, + **kwargs: Any + ) -> Feature: ... + @property + def geometry(self) -> Optional[Geometry]: ... + @property + def id(self) -> Optional[str]: ... + @property + def properties(self) -> Union[OrderedDict, Properties]: ... + @property + def type(self) -> str: ... + + +class Geometry: + @property + def __geo_interface__(self) -> Dict[str, Union[str, Tuple[int, int]]]: ... + def __init__( + self, + coordinates: Optional[Any] = ..., + type: Optional[str] = ..., + geometries: Optional[List[Geometry]] = ..., + **data + ) -> None: ... + @property + def coordinates(self) -> Any: ... + @classmethod + def from_dict(cls, ob: None = ..., **kwargs) -> Geometry: ... + @property + def geometries(self) -> Optional[List[Geometry]]: ... + @property + def type(self) -> str: ... + + +class Object: + def __delitem__(self, key: str) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __getitem__(self, item: str) -> Any: ... + def __init__(self, **kwds) -> None: ... + def __iter__(self) -> chain: ... + def __len__(self) -> int: ... + def __setitem__(self, key: str, value: int) -> None: ... + def _props(self) -> Dict[str, Any]: ... + + +class ObjectEncoder: + def default(self, o: Any) -> Dict[str, Any]: ... + + +class Properties: + def __init__(self, **kwds) -> None: ... + @classmethod + def from_dict(cls, mapping: None = ..., **kwargs) -> Properties: ... + + +class _Feature: + def __init__( + self, + geometry: Optional[Geometry] = ..., + id: Optional[Union[int, str]] = ..., + properties: Optional[Properties] = ... + ) -> None: ... + + +class _Geometry: + def __init__( + self, + coordinates: Optional[Any] = ..., + type: Optional[str] = ..., + geometries: Optional[List[Geometry]] = ... + ) -> None: ... diff --git a/fiona/ogrext.pyi b/fiona/ogrext.pyi new file mode 100644 index 000000000..dbda44924 --- /dev/null +++ b/fiona/ogrext.pyi @@ -0,0 +1,6 @@ +class Iterator: ... +class ItemsIterator(Iterator): ... +class KeysIterator(Iterator): ... + + +def featureRT(feat, collection): ... diff --git a/fiona/path.pyi b/fiona/path.pyi new file mode 100644 index 000000000..bdbcad9c2 --- /dev/null +++ b/fiona/path.pyi @@ -0,0 +1,21 @@ +from typing import Union + + +def parse_path( + path: Union[UnparsedPath, str, ParsedPath] +) -> Union[UnparsedPath, ParsedPath]: ... + + +def vsi_path(path: Union[UnparsedPath, ParsedPath]) -> str: ... + + +class ParsedPath: + @classmethod + def from_uri(cls, uri: str) -> ParsedPath: ... + @property + def is_local(self) -> bool: ... + + +class UnparsedPath: + @property + def name(self) -> str: ... diff --git a/fiona/rfc3339.pyi b/fiona/rfc3339.pyi new file mode 100644 index 000000000..bcd343fa0 --- /dev/null +++ b/fiona/rfc3339.pyi @@ -0,0 +1,30 @@ +from re import Match +from typing import ( + Tuple, + Union, + Pattern +) + + +class FionaDateType(str):... + + +def parse_date(text: str) -> Tuple[int, int, int, int, int, int, int, None]: ... + + +def parse_datetime( + text: str +) -> Union[Tuple[int, int, int, int, int, int, int, float], Tuple[int, int, int, int, int, int, int, None], Tuple[int, int, int, int, int, int, int, int]]: ... + + +def parse_time( + text: str +) -> Union[Tuple[int, int, int, int, int, int, int, float], Tuple[int, int, int, int, int, int, int, None], Tuple[int, int, int, int, int, int, int, int]]: ... + + +def pattern_date() -> Pattern[str]: ... + + +class group_accessor: + def __init__(self, m: Match) -> None: ... + def group(self, i: int) -> Union[int, str]: ... diff --git a/fiona/schema.pyi b/fiona/schema.pyi new file mode 100644 index 000000000..837077e43 --- /dev/null +++ b/fiona/schema.pyi @@ -0,0 +1,8 @@ +from typing import Type + +FIELD_TYPES: list[Type] +FIELD_TYPES_MAP: dict[str, Type] +FIELD_TYPES_MAP_REV: dict[Type, str] + + +def normalize_field_type(ftype: str) -> str: ... diff --git a/fiona/session.pyi b/fiona/session.pyi new file mode 100644 index 000000000..afd44feb6 --- /dev/null +++ b/fiona/session.pyi @@ -0,0 +1,107 @@ +import boto3 +from typing import ( + Any, + Dict, + Optional, + Type, + Union, +) + + +class AWSSession: + def __init__( + self, + session: Optional[boto3.Session] = ..., + aws_unsigned: bool = ..., + aws_access_key_id: Optional[str] = ..., + aws_secret_access_key: Optional[str] = ..., + aws_session_token: None = ..., + region_name: Optional[str] = ..., + profile_name: Optional[str] = ..., + endpoint_url: Optional[str] = ..., + requester_pays: bool = ... + ) -> None: ... + @property + def credentials(self) -> Dict[str, str]: ... + def get_credential_options(self) -> Dict[str, str]: ... + @classmethod + def hascreds(cls, config: Dict[str, str]) -> bool: ... + + +class AzureSession: + def __init__( + self, + azure_storage_connection_string: Optional[str] = ..., + azure_storage_account: Optional[str] = ..., + azure_storage_access_key: Optional[str] = ..., + azure_unsigned: bool = ... + ) -> None: ... + @property + def credentials(self) -> Dict[str, str]: ... + def get_credential_options(self) -> Dict[str, str]: ... + + +class DummySession: + def __init__(self, *args, **kwargs) -> None: ... + def get_credential_options(self) -> Dict[Any, Any]: ... + @classmethod + def hascreds(cls, config: Dict[str, Union[bool, str]]) -> bool: ... + + +class GSSession: + def __init__(self, google_application_credentials: Optional[str] = ...) -> None: ... + @property + def credentials(self) -> Dict[str, str]: ... + def get_credential_options(self) -> Dict[str, str]: ... + @classmethod + def hascreds(cls, config: Dict[str, str]) -> bool: ... + + +class OSSSession: + def __init__( + self, + oss_access_key_id: Optional[str] = ..., + oss_secret_access_key: Optional[str] = ..., + oss_endpoint: Optional[str] = ... + ) -> None: ... + @property + def credentials(self) -> Dict[str, Optional[str]]: ... + def get_credential_options(self) -> Dict[str, Optional[str]]: ... + + +class Session: + @staticmethod + def aws_or_dummy(*args, **kwargs) -> Union[AWSSession, DummySession]: ... + @staticmethod + def cls_from_path( + path: str + ) -> Union[Type[OSSSession], Type[DummySession], Type[AWSSession], Type[AzureSession]]: ... + @staticmethod + def from_foreign_session( + session: Optional[boto3.Session], + cls: Optional[Type[AWSSession]] = ... + ) -> Union[AWSSession, DummySession]: ... + @staticmethod + def from_path( + path: str, + *args, + **kwargs + ) -> Union[AWSSession, AzureSession, OSSSession, DummySession]: ... + def get_credential_options(self) -> Dict[str, str]: ... + @classmethod + def hascreds(cls, config: Dict[str, Union[bool, str]]) -> bool: ... + + +class SwiftSession: + def __init__( + self, + session: None = ..., + swift_storage_url: Optional[str] = ..., + swift_auth_token: Optional[str] = ..., + swift_auth_v1_url: None = ..., + swift_user: None = ..., + swift_key: None = ... + ) -> None: ... + @property + def credentials(self) -> Dict[str, str]: ... + def get_credential_options(self) -> Dict[str, str]: ... diff --git a/fiona/transform.pyi b/fiona/transform.pyi new file mode 100644 index 000000000..c63b14d94 --- /dev/null +++ b/fiona/transform.pyi @@ -0,0 +1,27 @@ +from fiona.crs import CRS +from fiona.model import Geometry +from typing import ( + Dict, + List, + Optional, + Tuple, + Union, +) + + +def transform( + src_crs: Union[Dict[str, Union[str, bool]], Dict[str, str], str], + dst_crs: Union[Dict[str, Union[str, bool]], Dict[str, str], str], + xs: List[float], + ys: List[float] +) -> Tuple[List[float], List[float]]: ... + + +def transform_geom( + src_crs: Union[Dict[str, Union[str, bool]], CRS, Dict[str, str], str], + dst_crs: Union[Dict[str, Union[str, bool]], Dict[str, str], str], + geom: Union[Dict[str, Union[str, List[Dict[str, Union[List[Tuple[float, float]], str]]]]], List[Geometry], Geometry, Dict[str, Union[str, Tuple[Tuple[Tuple[float, float], Tuple[float, float], Tuple[float, float], Tuple[float, float], Tuple[float, float]]]]]], + antimeridian_cutting: bool = ..., + antimeridian_offset: float = ..., + precision: int = ... +) -> Optional[Union[List[Geometry], Geometry]]: ... diff --git a/fiona/vfs.pyi b/fiona/vfs.pyi new file mode 100644 index 000000000..310caf73c --- /dev/null +++ b/fiona/vfs.pyi @@ -0,0 +1,17 @@ +from typing import ( + Optional, + Tuple, + Union, +) + + +def parse_paths( + uri: str, + vfs: Optional[str] = ... +) -> Union[Tuple[str, str, str], Tuple[str, None, None], Tuple[str, str, None]]: ... + + +def valid_vsi(vsi: str) -> bool: ... + + +def vsi_path(path: str, vsi: Optional[str] = ..., archive: None = ...) -> str: ... diff --git a/pyproject.toml b/pyproject.toml index 6b8f09638..710366c12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,3 +83,7 @@ packages = ["fiona", "fiona.fio"] [tool.setuptools.dynamic] version = {attr = "fiona.__version__"} readme = {file = ["README.rst", "CHANGES.txt", "CREDITS.txt"]} + +[[tool.mypy.overrides]] +module = ["boto3", "boto3.session", "cligj", "IPython"] +ignore_missing_imports = true diff --git a/requirements-types.txt b/requirements-types.txt new file mode 100644 index 000000000..aeb93c36b --- /dev/null +++ b/requirements-types.txt @@ -0,0 +1,6 @@ +-r requirements.txt +mypy +pytest +types-six +types-pytz +types-setuptools \ No newline at end of file diff --git a/tests/test_collection.py b/tests/test_collection.py index 28254a25e..79f581a33 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -401,19 +401,6 @@ def test_filter_where(self): results = list(self.c.filter()) assert len(results) == 67 - def test_filter_bbox_where(self): - # combined filter criteria - results = set(self.c.keys( - bbox=(-120.0, 40.0, -100.0, 50.0), where="NAME LIKE 'Mount%'")) - assert results == set([0, 2, 5, 13]) - results = set(self.c.keys()) - assert len(results) == 67 - - def test_filter_where_error(self): - for w in ["bad stuff", "NAME=3", "NNAME LIKE 'Mount%'"]: - with pytest.raises(AttributeFilterError): - self.c.filter(where=w) - def test_filter_bbox_where(self): # combined filter criteria results = set( diff --git a/tests/test_memoryfile.py b/tests/test_memoryfile.py index 406f05bac..07e3382d4 100644 --- a/tests/test_memoryfile.py +++ b/tests/test_memoryfile.py @@ -322,30 +322,6 @@ def test_write_mode_on_non_existing_memoryfile(profile_first_coutwildrnp_shp): pass -@requires_gpkg -def test_read_multilayer_memoryfile(path_coutwildrnp_json, tmpdir): - """Test read access to multilayer dataset in from file-like object""" - with fiona.open(path_coutwildrnp_json, "r") as src: - schema = src.schema - features = list(src) - - path = os.path.join(tmpdir, "test.gpkg") - with fiona.open(path, "w", driver="GPKG", schema=schema, layer="layer1") as dst: - dst.writerecords(features[0:5]) - with fiona.open(path, "w", driver="GPKG", schema=schema, layer="layer2") as dst: - dst.writerecords(features[5:]) - - with open(path, "rb") as f: - with fiona.open(f, layer="layer1") as src: - assert src.name == "layer1" - assert len(src) == 5 - # Bug reported in #781 where this next section would fail - with open(path, "rb") as f: - with fiona.open(f, layer="layer2") as src: - assert src.name == "layer2" - assert len(src) == 62 - - def test_allow_unsupported_drivers(monkeypatch): """Test if allow unsupported drivers works as expected""" diff --git a/tests/test_rfc64_tin.py b/tests/test_rfc64_tin.py index 730a62708..8f6e57afe 100644 --- a/tests/test_rfc64_tin.py +++ b/tests/test_rfc64_tin.py @@ -4,10 +4,9 @@ """ import fiona -from fiona.model import Geometry -def _test_tin(geometry: Geometry) -> None: +def _test_tin(geometry: dict) -> None: """Test if TIN (((0 0 0, 0 0 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 0 0 0))) is correctly converted to MultiPolygon. """ @@ -18,7 +17,7 @@ def _test_tin(geometry: Geometry) -> None: ] -def _test_triangle(geometry: Geometry) -> None: +def _test_triangle(geometry: dict) -> None: """Test if TRIANGLE((0 0 0,0 1 0,1 1 0,0 0 0)) is correctly converted to MultiPolygon.""" assert geometry["type"] == "Polygon"