diff --git a/noxfile.py b/noxfile.py index a246eb65c..6158e7b05 100644 --- a/noxfile.py +++ b/noxfile.py @@ -3,11 +3,18 @@ nox.options.stop_on_first_error = True nox.options.reuse_existing_virtualenvs = False -nox.options.sessions = ['lint', 'test', 'coverage', 'docs'] +nox.options.sessions = ['lint', 'analyze', 'test', 'coverage', 'docs'] source_files = ("planet", "examples", "tests", "setup.py", "noxfile.py") +@nox.session +def analyze(session): + session.install(".[lint]") + + session.run("mypy", "--ignore-missing", "planet") + + @nox.session def coverage(session): session.install("-e", ".[test]") diff --git a/planet/__init__.py b/planet/__init__.py index 92b4815cf..a0a36d623 100644 --- a/planet/__init__.py +++ b/planet/__init__.py @@ -15,6 +15,13 @@ from . import order_request, reporting from .__version__ import __version__ # NOQA from .auth import Auth -from .clients import DataClient, OrdersClient +from .clients import DataClient, OrdersClient # NOQA -__all__ = [Session, OrdersClient, order_request, reporting, Auth, DataClient] +__all__ = [ + 'Auth', + 'DataClient' + 'OrdersClient', + 'order_request', + 'reporting', + 'Session', +] diff --git a/planet/auth.py b/planet/auth.py index fa538e1ae..469b71669 100644 --- a/planet/auth.py +++ b/planet/auth.py @@ -30,12 +30,14 @@ BASE_URL = f'{PLANET_BASE_URL}/v0/auth' ENV_API_KEY = 'PL_API_KEY' +AuthType = httpx.Auth + class Auth(metaclass=abc.ABCMeta): '''Handle authentication information for use with Planet APIs.''' @staticmethod - def from_key(key: str) -> Auth: + def from_key(key: str) -> AuthType: '''Obtain authentication from api key. Parameters: @@ -46,7 +48,7 @@ def from_key(key: str) -> Auth: return auth @staticmethod - def from_file(filename: str = None) -> Auth: + def from_file(filename: str = None) -> AuthType: '''Create authentication from secret file. The secret file is named `.planet.json` and is stored in the user @@ -71,7 +73,7 @@ def from_file(filename: str = None) -> Auth: return auth @staticmethod - def from_env(variable_name: str = None) -> Auth: + def from_env(variable_name: str = None) -> AuthType: '''Create authentication from environment variable. Reads the `PL_API_KEY` environment variable @@ -80,7 +82,7 @@ def from_env(variable_name: str = None) -> Auth: variable_name: Alternate environment variable. ''' variable_name = variable_name or ENV_API_KEY - api_key = os.getenv(variable_name) + api_key = os.getenv(variable_name, '') try: auth = APIKeyAuth(api_key) LOGGER.debug(f'Auth set from environment variable {variable_name}') @@ -91,7 +93,9 @@ def from_env(variable_name: str = None) -> Auth: return auth @staticmethod - def from_login(email: str, password: str, base_url: str = None) -> Auth: + def from_login(email: str, + password: str, + base_url: str = None) -> AuthType: '''Create authentication from login email and password. Note: To keep your password secure, the use of `getpass` is @@ -113,7 +117,7 @@ def from_login(email: str, password: str, base_url: str = None) -> Auth: @classmethod @abc.abstractmethod - def from_dict(cls, data: dict) -> Auth: + def from_dict(cls, data: dict) -> AuthType: pass @property @@ -121,6 +125,10 @@ def from_dict(cls, data: dict) -> Auth: def value(self): pass + @abc.abstractmethod + def to_dict(self) -> dict: + pass + def write(self, filename: str = None): '''Write authentication information. diff --git a/planet/clients/__init__.py b/planet/clients/__init__.py index fdbf6eacb..fbc62a1e9 100644 --- a/planet/clients/__init__.py +++ b/planet/clients/__init__.py @@ -15,6 +15,6 @@ from .orders import OrdersClient __all__ = [ - DataClient, - OrdersClient, + 'DataClient', + 'OrdersClient', ] diff --git a/planet/clients/data.py b/planet/clients/data.py index 24b5a558d..d05233451 100644 --- a/planet/clients/data.py +++ b/planet/clients/data.py @@ -176,10 +176,11 @@ async def create_search(self, # TODO: validate item_types request_json = { - 'name': name, 'filter': search_filter, 'item_types': item_types + 'name': name, + 'filter': search_filter, + 'item_types': item_types, + '__daily_email_enabled': enable_email } - if enable_email: - request_json['__daily_email_enabled'] = True request = self._request(url, method='POST', json=request_json) response = await self._do_request(request) diff --git a/planet/http.py b/planet/http.py index d69e15df3..319951f9c 100644 --- a/planet/http.py +++ b/planet/http.py @@ -19,7 +19,7 @@ import httpx -from .auth import Auth +from .auth import Auth, AuthType from . import exceptions, models from .__version__ import __version__ @@ -112,7 +112,7 @@ class Session(BaseSession): ``` ''' - def __init__(self, auth: Auth = None): + def __init__(self, auth: AuthType = None): """Initialize a Session. Parameters: diff --git a/planet/order_request.py b/planet/order_request.py index bf86baa12..10a88faac 100644 --- a/planet/order_request.py +++ b/planet/order_request.py @@ -14,7 +14,7 @@ """Functionality for preparing order details for use in creating an order""" from __future__ import annotations # https://stackoverflow.com/a/33533514 import logging -from typing import List +from typing import Any, Dict, List from . import geojson, specs @@ -67,7 +67,7 @@ def build_request(name: str, planet.specs.SpecificationException: If order_type is not a valid order type. ''' - details = {'name': name, 'products': products} + details: Dict[str, Any] = {'name': name, 'products': products} if subscription_id: details['subscription_id'] = subscription_id @@ -79,8 +79,8 @@ def build_request(name: str, details['notifications'] = notifications if order_type: - order_type = specs.validate_order_type(order_type) - details['order_type'] = order_type + validated_order_type = specs.validate_order_type(order_type) + details['order_type'] = validated_order_type if tools: details['tools'] = tools @@ -108,18 +108,19 @@ def product(item_ids: List[str], are not valid bundles or if item_type is not valid for the given bundle or fallback bundle. ''' - product_bundle = specs.validate_bundle(product_bundle) - item_type = specs.validate_item_type(item_type, product_bundle) + validated_product_bundle = specs.validate_bundle(product_bundle) + item_type = specs.validate_item_type(item_type, validated_product_bundle) if fallback_bundle is not None: - fallback_bundle = specs.validate_bundle(fallback_bundle) - specs.validate_item_type(item_type, fallback_bundle) - product_bundle = ','.join([product_bundle, fallback_bundle]) + validated_fallback_bundle = specs.validate_bundle(fallback_bundle) + specs.validate_item_type(item_type, validated_fallback_bundle) + validated_product_bundle = ','.join( + [validated_product_bundle, validated_fallback_bundle]) product_dict = { 'item_ids': item_ids, 'item_type': item_type, - 'product_bundle': product_bundle + 'product_bundle': validated_product_bundle } return product_dict diff --git a/planet/reporting.py b/planet/reporting.py index 53423a3f9..e46e54dbd 100644 --- a/planet/reporting.py +++ b/planet/reporting.py @@ -90,15 +90,19 @@ def update_state(self, state: str): def update(self, state: str = None, order_id: str = None): if state: self.state = state - try: - self.bar.postfix[1] = self.state - except AttributeError: - # If the bar is disabled, attempting to access self.bar.postfix - # will result in an error. In this case, just skip it. - pass + if self.bar is not None: + try: + self.bar.postfix[1] = self.state + except AttributeError: + # If the bar is disabled, attempting to access + # self.bar.postfix will result in an error. In this + # case, just skip it. + pass if order_id: self.order_id = order_id - self.bar.set_description_str(self.desc, refresh=False) + if self.bar is not None: + self.bar.set_description_str(self.desc, refresh=False) - self.bar.refresh() + if self.bar is not None: + self.bar.refresh() diff --git a/setup.py b/setup.py index bcb9df61c..5996dddb5 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ 'pytest', 'pytest-asyncio==0.16', 'pytest-cov', 'respx==0.16.3' ] -lint_requires = ['flake8', 'yapf'] +lint_requires = ['flake8', 'mypy', 'yapf'] doc_requires = [ 'mkdocs==1.3', diff --git a/tests/integration/test_data_api.py b/tests/integration/test_data_api.py index 3361e29d8..7fccb1a6f 100644 --- a/tests/integration/test_data_api.py +++ b/tests/integration/test_data_api.py @@ -174,7 +174,10 @@ async def test_create_search_basic(search_filter, session): # check that request is correct expected_request = { - "item_types": ["PSScene"], "filter": search_filter, "name": "test" + "item_types": ["PSScene"], + "filter": search_filter, + "name": "test", + "__daily_email_enabled": False } actual_body = json.loads(respx.calls[0].request.content) assert actual_body == expected_request