From 898c05647d7d0a0f7bd0ca106be3bce984be5a7a Mon Sep 17 00:00:00 2001 From: Stefaan Lippens Date: Mon, 28 Aug 2023 10:28:15 +0200 Subject: [PATCH] Issue #460 avoid string (return) annotations PEP 563: Postponed Evaluation of Annotations --- openeo/api/process.py | 16 +++++++++------- openeo/capabilities.py | 9 +++++---- openeo/config.py | 8 +++++--- openeo/internal/graph_building.py | 9 ++++++--- openeo/internal/process_graph_visitor.py | 6 ++++-- openeo/internal/processes/parse.py | 18 ++++++++++-------- openeo/rest/_datacube.py | 8 +++++--- openeo/rest/auth/oidc.py | 4 +++- openeo/rest/conversions.py | 4 +++- openeo/rest/job.py | 18 ++++++++++-------- openeo/rest/mlmodel.py | 6 ++++-- openeo/rest/userfile.py | 6 ++++-- openeo/udf/feature_collection.py | 5 +++-- openeo/udf/structured_data.py | 3 ++- openeo/udf/udf_data.py | 5 +++-- openeo/udf/xarraydatacube.py | 5 +++-- openeo/util.py | 10 ++++++---- 17 files changed, 85 insertions(+), 55 deletions(-) diff --git a/openeo/api/process.py b/openeo/api/process.py index 405828f24..9eaea06e8 100644 --- a/openeo/api/process.py +++ b/openeo/api/process.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import warnings from typing import Union @@ -35,7 +37,7 @@ def to_dict(self) -> dict: return d @classmethod - def raster_cube(cls, name: str = "data", description: str = "A data cube.") -> 'Parameter': + def raster_cube(cls, name: str = "data", description: str = "A data cube.") -> Parameter: """ Helper to easily create a 'raster-cube' parameter. @@ -46,7 +48,7 @@ def raster_cube(cls, name: str = "data", description: str = "A data cube.") -> ' return cls(name=name, description=description, schema={"type": "object", "subtype": "raster-cube"}) @classmethod - def datacube(cls, name: str = "data", description: str = "A data cube.") -> "Parameter": + def datacube(cls, name: str = "data", description: str = "A data cube.") -> Parameter: """ Helper to easily create a 'datacube' parameter. @@ -59,7 +61,7 @@ def datacube(cls, name: str = "data", description: str = "A data cube.") -> "Par return cls(name=name, description=description, schema={"type": "object", "subtype": "datacube"}) @classmethod - def string(cls, name: str, description: str = None, default=_DEFAULT_UNDEFINED, values=None) -> 'Parameter': + def string(cls, name: str, description: str = None, default=_DEFAULT_UNDEFINED, values=None) -> Parameter: """Helper to create a 'string' type parameter.""" schema = {"type": "string"} if values is not None: @@ -68,21 +70,21 @@ def string(cls, name: str, description: str = None, default=_DEFAULT_UNDEFINED, @classmethod - def integer(cls, name: str, description: str = None, default=_DEFAULT_UNDEFINED) -> 'Parameter': + def integer(cls, name: str, description: str = None, default=_DEFAULT_UNDEFINED) -> Parameter: """Helper to create a 'integer' type parameter.""" return cls(name=name, description=description, schema={"type": "integer"}, default=default) @classmethod - def number(cls, name: str, description: str = None, default=_DEFAULT_UNDEFINED) -> 'Parameter': + def number(cls, name: str, description: str = None, default=_DEFAULT_UNDEFINED) -> Parameter: """Helper to create a 'number' type parameter.""" return cls(name=name, description=description, schema={"type": "number"}, default=default) @classmethod - def boolean(cls, name: str, description: str = None, default=_DEFAULT_UNDEFINED) -> 'Parameter': + def boolean(cls, name: str, description: str = None, default=_DEFAULT_UNDEFINED) -> Parameter: """Helper to create a 'boolean' type parameter.""" return cls(name=name, description=description, schema={"type": "boolean"}, default=default) @classmethod - def array(cls, name: str, description: str = None, default=_DEFAULT_UNDEFINED) -> 'Parameter': + def array(cls, name: str, description: str = None, default=_DEFAULT_UNDEFINED) -> Parameter: """Helper to create a 'array' type parameter.""" return cls(name=name, description=description, schema={"type": "array"}, default=default) diff --git a/openeo/capabilities.py b/openeo/capabilities.py index 0bc4a1270..3e8137390 100644 --- a/openeo/capabilities.py +++ b/openeo/capabilities.py @@ -1,8 +1,9 @@ +from __future__ import annotations + import contextlib -from abc import ABC import re -from typing import Union, Tuple - +from abc import ABC +from typing import Tuple, Union # TODO Is this base class (still) useful? @@ -24,7 +25,7 @@ def api_version(self) -> str: raise NotImplementedError @property - def api_version_check(self) -> 'ComparableVersion': + def api_version_check(self) -> ComparableVersion: """Helper to easily check if the API version is at least or below some threshold version.""" api_version = self.api_version() if not api_version: diff --git a/openeo/config.py b/openeo/config.py index 918fa1f99..32a5827a1 100644 --- a/openeo/config.py +++ b/openeo/config.py @@ -4,13 +4,15 @@ """ +from __future__ import annotations + import logging import os import platform from configparser import ConfigParser from copy import deepcopy from pathlib import Path -from typing import Union, Any, Sequence, Iterator, Optional, List +from typing import Any, Iterator, List, Optional, Sequence, Union from openeo.util import in_interactive_mode @@ -116,13 +118,13 @@ def get(self, key: Union[str, Sequence[str]], default=None) -> Any: # TODO: option to cast/convert to certain type? return self._config.get(self._key(key), default) - def load_ini_file(self, path: Union[str, Path]) -> "ClientConfig": + def load_ini_file(self, path: Union[str, Path]) -> ClientConfig: cp = ConfigParser() read_ok = cp.read(path) self._sources.extend(read_ok) return self.load_config_parser(cp) - def load_config_parser(self, parser: ConfigParser) -> "ClientConfig": + def load_config_parser(self, parser: ConfigParser) -> ClientConfig: for section in parser.sections(): for option, value in parser.items(section=section): self._set(key=(section, option), value=value) diff --git a/openeo/internal/graph_building.py b/openeo/internal/graph_building.py index 085596f05..31dc8b99e 100644 --- a/openeo/internal/graph_building.py +++ b/openeo/internal/graph_building.py @@ -2,6 +2,9 @@ Functionality for abstracting, building, manipulating and processing openEO process graphs. """ + +from __future__ import annotations + import abc import collections import json @@ -78,7 +81,7 @@ class _FromNodeMixin(abc.ABC): """Mixin for classes that want to hook into the generation of a "from_node" reference.""" @abc.abstractmethod - def from_node(self) -> "PGNode": + def from_node(self) -> PGNode: # TODO: "from_node" is a bit a confusing name: # it refers to the "from_node" node reference in openEO process graphs, # but as a method name here it reads like "construct from PGNode", @@ -203,7 +206,7 @@ def to_process_graph_argument(value: Union['PGNode', str, dict]) -> dict: raise ValueError(value) @staticmethod - def from_flat_graph(flat_graph: dict, parameters: Optional[dict] = None) -> 'PGNode': + def from_flat_graph(flat_graph: dict, parameters: Optional[dict] = None) -> PGNode: """Unflatten a given flat dict representation of a process graph and return result node.""" return PGNodeGraphUnflattener.unflatten(flat_graph=flat_graph, parameters=parameters) @@ -259,7 +262,7 @@ def dimension(self): def reducer_process_graph(self) -> PGNode: return self.arguments["reducer"]["process_graph"] - def clone_with_new_reducer(self, reducer: PGNode) -> 'ReduceNode': + def clone_with_new_reducer(self, reducer: PGNode) -> ReduceNode: """Copy/clone this reduce node: keep input reference, but use new reducer""" return ReduceNode( data=self.arguments["data"]["from_node"], diff --git a/openeo/internal/process_graph_visitor.py b/openeo/internal/process_graph_visitor.py index 6512b3fb7..8315eda40 100644 --- a/openeo/internal/process_graph_visitor.py +++ b/openeo/internal/process_graph_visitor.py @@ -1,6 +1,8 @@ +from __future__ import annotations + import json from abc import ABC -from typing import Union, Tuple, Any +from typing import Any, Tuple, Union from openeo.internal.warnings import deprecated from openeo.rest import OpenEoClientException @@ -63,7 +65,7 @@ def resolve_from_node(process_graph, node, from_node): raise ProcessGraphVisitException("No result node in process graph: " + dump[:1000]) return result_node - def accept_process_graph(self, graph: dict) -> 'ProcessGraphVisitor': + def accept_process_graph(self, graph: dict) -> ProcessGraphVisitor: """ Traverse a (flat) process graph diff --git a/openeo/internal/processes/parse.py b/openeo/internal/processes/parse.py index 624221161..de6f25dbc 100644 --- a/openeo/internal/processes/parse.py +++ b/openeo/internal/processes/parse.py @@ -2,9 +2,11 @@ Functionality and tools to process openEO processes. For example: parse a bunch of JSON descriptions and generate Python (stub) functions. """ +from __future__ import annotations + import json from pathlib import Path -from typing import List, Union, Iterator +from typing import Iterator, List, Union import requests @@ -16,7 +18,7 @@ def __init__(self, schema: Union[dict, list]): self.schema = schema @classmethod - def from_dict(cls, data: dict) -> 'Schema': + def from_dict(cls, data: dict) -> Schema: return cls(schema=data) @@ -34,7 +36,7 @@ def __init__(self, name: str, description: str, schema: Schema, default=NO_DEFAU self.optional = optional @classmethod - def from_dict(cls, data: dict) -> 'Parameter': + def from_dict(cls, data: dict) -> Parameter: return cls( name=data["name"], description=data["description"], schema=Schema.from_dict(data["schema"]), default=data.get("default", cls.NO_DEFAULT), optional=data.get("optional", False) @@ -52,7 +54,7 @@ def __init__(self, description: str, schema: Schema): self.schema = schema @classmethod - def from_dict(cls, data: dict) -> 'Returns': + def from_dict(cls, data: dict) -> Returns: return cls(description=data["description"], schema=Schema.from_dict(data["schema"])) @@ -71,7 +73,7 @@ def __init__( # TODO: more properties? @classmethod - def from_dict(cls, data: dict) -> 'Process': + def from_dict(cls, data: dict) -> Process: """Construct openEO process from dictionary values""" return cls( id=data["id"], @@ -82,17 +84,17 @@ def from_dict(cls, data: dict) -> 'Process': ) @classmethod - def from_json(cls, data: str) -> 'Process': + def from_json(cls, data: str) -> Process: """Parse openEO process JSON description.""" return cls.from_dict(json.loads(data)) @classmethod - def from_json_url(cls, url: str) -> 'Process': + def from_json_url(cls, url: str) -> Process: """Parse openEO process JSON description from given URL.""" return cls.from_dict(requests.get(url).json()) @classmethod - def from_json_file(cls, path: Union[str, Path]) -> 'Process': + def from_json_file(cls, path: Union[str, Path]) -> Process: """Parse openEO process JSON description file.""" with Path(path).open("r") as f: return cls.from_json(f.read()) diff --git a/openeo/rest/_datacube.py b/openeo/rest/_datacube.py index c1c9956df..13d830eb0 100644 --- a/openeo/rest/_datacube.py +++ b/openeo/rest/_datacube.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging import pathlib import re @@ -60,7 +62,7 @@ def _api_version(self): return self._connection.capabilities().api_version_check @property - def connection(self) -> "Connection": + def connection(self) -> Connection: return self._connection def result_node(self) -> PGNode: @@ -172,7 +174,7 @@ def from_file( runtime: Optional[str] = None, version: Optional[str] = None, context: Optional[dict] = None, - ) -> "UDF": + ) -> UDF: """ Load a UDF from a local file. @@ -197,7 +199,7 @@ def from_url( runtime: Optional[str] = None, version: Optional[str] = None, context: Optional[dict] = None, - ) -> "UDF": + ) -> UDF: """ Load a UDF from a URL. diff --git a/openeo/rest/auth/oidc.py b/openeo/rest/auth/oidc.py index 8d598db8b..a1ed2bf3f 100644 --- a/openeo/rest/auth/oidc.py +++ b/openeo/rest/auth/oidc.py @@ -3,6 +3,8 @@ """ +from __future__ import annotations + import base64 import contextlib import enum @@ -272,7 +274,7 @@ def __init__( self.default_clients = default_clients @classmethod - def from_dict(cls, data: dict) -> "OidcProviderInfo": + def from_dict(cls, data: dict) -> OidcProviderInfo: return cls( provider_id=data["id"], title=data["title"], issuer=data["issuer"], diff --git a/openeo/rest/conversions.py b/openeo/rest/conversions.py index 89c259b50..a61895fd3 100644 --- a/openeo/rest/conversions.py +++ b/openeo/rest/conversions.py @@ -2,6 +2,8 @@ Helpers for data conversions between Python ecosystem data types and openEO data structures. """ +from __future__ import annotations + import typing import numpy as np @@ -94,7 +96,7 @@ def timeseries_json_to_pandas(timeseries: dict, index: str = "date", auto_collap @deprecated("Use :py:meth:`XarrayDataCube.from_file` instead.", version="0.7.0") -def datacube_from_file(filename, fmt='netcdf') -> "XarrayDataCube": +def datacube_from_file(filename, fmt="netcdf") -> XarrayDataCube: from openeo.udf.xarraydatacube import XarrayDataCube return XarrayDataCube.from_file(path=filename, fmt=fmt) diff --git a/openeo/rest/job.py b/openeo/rest/job.py index 9d85712d7..c08b5cf90 100644 --- a/openeo/rest/job.py +++ b/openeo/rest/job.py @@ -1,18 +1,20 @@ +from __future__ import annotations + import datetime import json import logging import time import typing from pathlib import Path -from typing import List, Union, Dict, Optional +from typing import Dict, List, Optional, Union import requests -from openeo.api.logs import LogEntry, normalize_log_level, log_level_name +from openeo.api.logs import LogEntry, log_level_name, normalize_log_level from openeo.internal.documentation import openeo_endpoint -from openeo.internal.jupyter import render_component, render_error, VisualDict, VisualList +from openeo.internal.jupyter import VisualDict, VisualList, render_component, render_error from openeo.internal.warnings import deprecated, legacy_alias -from openeo.rest import OpenEoClientException, JobFailedException, OpenEoApiError +from openeo.rest import JobFailedException, OpenEoApiError, OpenEoClientException from openeo.util import ensure_dir if typing.TYPE_CHECKING: @@ -94,7 +96,7 @@ def estimate(self): estimate_job = legacy_alias(estimate, since="0.20.0", mode="soft") @openeo_endpoint("POST /jobs/{job_id}/results") - def start(self) -> "BatchJob": + def start(self) -> BatchJob: """ Start this batch job. @@ -160,7 +162,7 @@ def download_results(self, target: Union[str, Path] = None) -> Dict[Path, dict]: def get_result(self): return _Result(self) - def get_results(self) -> "JobResults": + def get_results(self) -> JobResults: """ Get handle to batch job results for result metadata inspection or downloading resulting assets. @@ -221,7 +223,7 @@ def logs( def run_synchronous( self, outputfile: Union[str, Path, None] = None, print=print, max_poll_interval=60, connection_retry_interval=30 - ) -> 'BatchJob': + ) -> BatchJob: """Start the job, wait for it to finish and download result""" self.start_and_wait( print=print, max_poll_interval=max_poll_interval, connection_retry_interval=connection_retry_interval @@ -233,7 +235,7 @@ def run_synchronous( def start_and_wait( self, print=print, max_poll_interval: int = 60, connection_retry_interval: int = 30, soft_error_max=10 - ) -> "BatchJob": + ) -> BatchJob: """ Start the batch job, poll its status and wait till it finishes (or fails) diff --git a/openeo/rest/mlmodel.py b/openeo/rest/mlmodel.py index 8291c4dba..ab6ed6828 100644 --- a/openeo/rest/mlmodel.py +++ b/openeo/rest/mlmodel.py @@ -1,7 +1,9 @@ +from __future__ import annotations + import logging import pathlib import typing -from typing import Union, Optional +from typing import Optional, Union from openeo.internal.documentation import openeo_process from openeo.internal.graph_building import PGNode @@ -41,7 +43,7 @@ def save_ml_model(self, options: Optional[dict] = None): @staticmethod @openeo_process - def load_ml_model(connection: "Connection", id: Union[str, BatchJob]) -> "MlModel": + def load_ml_model(connection: "Connection", id: Union[str, BatchJob]) -> MlModel: """ Loads a machine learning model from a STAC Item. diff --git a/openeo/rest/userfile.py b/openeo/rest/userfile.py index 98c176c51..be3e2dddd 100644 --- a/openeo/rest/userfile.py +++ b/openeo/rest/userfile.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import typing from pathlib import Path, PurePosixPath from typing import Any, Dict, Optional, Union @@ -35,7 +37,7 @@ def __init__( self.connection = connection @classmethod - def from_metadata(cls, metadata: dict, connection: "Connection") -> "UserFile": + def from_metadata(cls, metadata: dict, connection: "Connection") -> UserFile: """Build :py:class:`UserFile` from a workspace file metadata dictionary.""" return cls(path=None, connection=connection, metadata=metadata) @@ -69,7 +71,7 @@ def download(self, target: Union[Path, str] = None) -> Path: return target - def upload(self, source: Union[Path, str]) -> "UserFile": + def upload(self, source: Union[Path, str]) -> UserFile: """ Uploads a local file to the path corresponding to this :py:class:`UserFile` in the user workspace and returns new :py:class:`UserFile` of newly uploaded file. diff --git a/openeo/udf/feature_collection.py b/openeo/udf/feature_collection.py index df3fbc222..329c618cc 100644 --- a/openeo/udf/feature_collection.py +++ b/openeo/udf/feature_collection.py @@ -3,8 +3,9 @@ """ # Note: this module was initially developed under the ``openeo-udf`` project (https://github.com/Open-EO/openeo-udf) +from __future__ import annotations -from typing import Optional, Union, Any, List +from typing import Any, List, Optional, Union import pandas import shapely.geometry @@ -93,7 +94,7 @@ def to_dict(self) -> dict: return data @classmethod - def from_dict(cls, data: dict) -> "FeatureCollection": + def from_dict(cls, data: dict) -> FeatureCollection: """ Create a feature collection from a python dictionary that was created from the JSON definition of the FeatureCollection diff --git a/openeo/udf/structured_data.py b/openeo/udf/structured_data.py index 9dbce8822..038bb37be 100644 --- a/openeo/udf/structured_data.py +++ b/openeo/udf/structured_data.py @@ -4,6 +4,7 @@ # Note: this module was initially developed under the ``openeo-udf`` project (https://github.com/Open-EO/openeo-udf) +from __future__ import annotations import builtins from typing import Union @@ -38,7 +39,7 @@ def to_dict(self) -> dict: ) @classmethod - def from_dict(cls, data: dict) -> "StructuredData": + def from_dict(cls, data: dict) -> StructuredData: return cls( data=data["data"], description=data.get("description"), diff --git a/openeo/udf/udf_data.py b/openeo/udf/udf_data.py index 8ea798665..e07ccdf8b 100644 --- a/openeo/udf/udf_data.py +++ b/openeo/udf/udf_data.py @@ -4,8 +4,9 @@ # Note: this module was initially developed under the ``openeo-udf`` project (https://github.com/Open-EO/openeo-udf) +from __future__ import annotations -from typing import Optional, List, Union +from typing import List, Optional, Union from openeo.udf.feature_collection import FeatureCollection from openeo.udf.structured_data import StructuredData @@ -113,7 +114,7 @@ def to_dict(self) -> dict: } @classmethod - def from_dict(cls, udf_dict: dict) -> "UdfData": + def from_dict(cls, udf_dict: dict) -> UdfData: """ Create a udf data object from a python dictionary that was created from the JSON definition of the UdfData class diff --git a/openeo/udf/xarraydatacube.py b/openeo/udf/xarraydatacube.py index ba4a6ce7a..ebad9639d 100644 --- a/openeo/udf/xarraydatacube.py +++ b/openeo/udf/xarraydatacube.py @@ -4,6 +4,7 @@ # Note: this module was initially developed under the ``openeo-udf`` project (https://github.com/Open-EO/openeo-udf) +from __future__ import annotations import collections import json @@ -81,7 +82,7 @@ def to_dict(self) -> dict: }) @classmethod - def from_dict(cls, xdc_dict: dict) -> "XarrayDataCube": + def from_dict(cls, xdc_dict: dict) -> XarrayDataCube: """ Create a :py:class:`XarrayDataCube` from a Python dictionary that was created from the JSON definition of the data cube @@ -120,7 +121,7 @@ def _guess_format(path: Union[str, Path]) -> str: raise ValueError("Can not guess format of {p}".format(p=path)) @classmethod - def from_file(cls, path: Union[str, Path], fmt=None, **kwargs) -> "XarrayDataCube": + def from_file(cls, path: Union[str, Path], fmt=None, **kwargs) -> XarrayDataCube: """ Load data file as :py:class:`XarrayDataCube` in memory diff --git a/openeo/util.py b/openeo/util.py index 7506d9986..b935669ec 100644 --- a/openeo/util.py +++ b/openeo/util.py @@ -4,6 +4,8 @@ # TODO #465 split this kitchen-sink in thematic submodules +from __future__ import annotations + import datetime as dt import functools import json @@ -470,7 +472,7 @@ def elapsed(self) -> float: # Currently elapsed inside context. return self._clock() - self.start - def __enter__(self) -> 'ContextTimer': + def __enter__(self) -> ContextTimer: self.start = self._clock() return self @@ -699,7 +701,7 @@ def __init__(self, *, west: float, south: float, east: float, north: float, crs: # TODO: provide west, south, east, north, crs as @properties? Read-only or read-write? @classmethod - def from_any(cls, x: Any, *, crs: Optional[str] = None) -> 'BBoxDict': + def from_any(cls, x: Any, *, crs: Optional[str] = None) -> BBoxDict: if isinstance(x, dict): if crs and "crs" in x and crs != x["crs"]: raise InvalidBBoxException(f"Two CRS values specified: {crs} and {x['crs']}") @@ -713,7 +715,7 @@ def from_any(cls, x: Any, *, crs: Optional[str] = None) -> 'BBoxDict': raise InvalidBBoxException(f"Can not construct BBoxDict from {x!r}") @classmethod - def from_dict(cls, data: dict) -> 'BBoxDict': + def from_dict(cls, data: dict) -> BBoxDict: """Build from dictionary with at least keys "west", "south", "east", and "north".""" expected_fields = {"west", "south", "east", "north"} # TODO: also support upper case fields? @@ -727,7 +729,7 @@ def from_dict(cls, data: dict) -> 'BBoxDict': return cls(west=data["west"], south=data["south"], east=data["east"], north=data["north"], crs=data.get("crs")) @classmethod - def from_sequence(cls, seq: Union[list, tuple], crs: Optional[str] = None) -> 'BBoxDict': + def from_sequence(cls, seq: Union[list, tuple], crs: Optional[str] = None) -> BBoxDict: """Build from sequence of 4 bounds (west, south, east and north).""" if len(seq) != 4: raise InvalidBBoxException(f"Expected sequence with 4 items, but got {len(seq)}.")