Repository: planetlabs/planet-client-python

Issue:
Reorganize structure, specifically remove `api` module and add `client` module
To prepare for the introduction of a new client, the data client, introduce a new module called `clients` that will contain all of the API clients as well as their common `_BaseClient` parent.

Additionally, remove the `api` module, as it is an artifact from v1 and doesn't make sense now that the api is 1st class and the CLI (which has its own module) is a thin wrapper.

Usability impact:
As all of these modules are exposed at the top level, the user experience will not change.

Discussion:

Patch:
diff --git a/planet/__init__.py b/planet/__init__.py
index e22622f..57ea767 100644
--- a/planet/__init__.py
+++ b/planet/__init__.py
@@ -11,12 +11,12 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-from .api.http import Session
-from .api.models import Order
-from .api.orders import OrdersClient
-from .api import order_request
-from .api.__version__ import __version__  # NOQA
+from .http import Session
+from .models import Order
+from . import order_request
+from .__version__ import __version__  # NOQA
 from .auth import Auth
+from .clients import OrdersClient
 
 
 __all__ = [
diff --git a/planet/api/__version__.py b/planet/__version__.py
similarity index 100%
rename from planet/api/__version__.py
rename to planet/__version__.py
diff --git a/planet/auth.py b/planet/auth.py
index 09e603f..7ee59ad 100644
--- a/planet/auth.py
+++ b/planet/auth.py
@@ -21,8 +21,7 @@ import os
 import httpx
 import jwt
 
-from . import constants
-from .api import http, models
+from . import constants, http, models
 
 AUTH_URL = constants.PLANET_BASE_URL + 'v0/auth/'
 
diff --git a/planet/api/__init__.py b/planet/clients/__init__.py
similarity index 84%
rename from planet/api/__init__.py
rename to planet/clients/__init__.py
index 75c2286..43d19e2 100644
--- a/planet/api/__init__.py
+++ b/planet/clients/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Planet Labs, Inc.
+# Copyright 2021 Planet Labs, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -11,3 +11,8 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+from .orders import OrdersClient
+
+__all__ = [
+    OrdersClient,
+]
diff --git a/planet/api/orders.py b/planet/clients/orders.py
similarity index 99%
rename from planet/api/orders.py
rename to planet/clients/orders.py
index 83620d8..cd4c2c8 100644
--- a/planet/api/orders.py
+++ b/planet/clients/orders.py
@@ -20,8 +20,8 @@ import time
 import typing
 
 from .. import constants
-from .http import Session
-from .models import Order, Orders, Request, Response, StreamingBody
+from ..http import Session
+from ..models import Order, Orders, Request, Response, StreamingBody
 
 
 BASE_URL = constants.PLANET_BASE_URL + 'compute/ops/'
diff --git a/planet/api/exceptions.py b/planet/exceptions.py
similarity index 100%
rename from planet/api/exceptions.py
rename to planet/exceptions.py
diff --git a/planet/geojson.py b/planet/geojson.py
index d99985e..752df7f 100644
--- a/planet/geojson.py
+++ b/planet/geojson.py
@@ -30,9 +30,8 @@ class WrongTypeException(GeoJSONException):
     pass
 
 
-class MultipleFeaturesException(GeoJSONException):
-    '''multiple features when only one is acceptable'''
-    pass
+class DataLossWarning(UserWarning):
+    """Warn that data will be lost."""
 
 
 def as_geom(data: dict) -> dict:
@@ -81,7 +80,7 @@ def geom_from_geojson(data: dict) -> dict:
     else:
         try:
             # feature
-            ret = as_geom(data['geometry'])
+            ret = as_geom(data["geometry"])
         except KeyError:
             try:
                 # featureclass
@@ -90,9 +89,9 @@ def geom_from_geojson(data: dict) -> dict:
                 raise GeoJSONException('Invalid GeoJSON: {data}')
 
             if len(features) > 1:
-                raise MultipleFeaturesException(
-                    'FeatureClass has multiple features. Only one feature '
-                    'can be used to get geometry.')
+                LOGGER.warning(
+                    'FeatureClass has more than one Feature, using only first'
+                    ' feature.')
 
             ret = as_geom(features[0])
     return ret
diff --git a/planet/api/http.py b/planet/http.py
similarity index 99%
rename from planet/api/http.py
rename to planet/http.py
index 79de413..7f15ee1 100644
--- a/planet/api/http.py
+++ b/planet/http.py
@@ -20,9 +20,9 @@ import logging
 
 import httpx
 
-from ..auth import Auth
+from .auth import Auth
 from . import exceptions, models
-from . __version__ import __version__
+from .__version__ import __version__
 
 RETRY_COUNT = 5
 RETRY_WAIT_TIME = 1  # seconds
diff --git a/planet/api/models.py b/planet/models.py
similarity index 100%
rename from planet/api/models.py
rename to planet/models.py
diff --git a/planet/api/order_request.py b/planet/order_request.py
similarity index 99%
rename from planet/api/order_request.py
rename to planet/order_request.py
index a08f517..b37dba2 100644
--- a/planet/api/order_request.py
+++ b/planet/order_request.py
@@ -16,7 +16,7 @@ from __future__ import annotations  # https://stackoverflow.com/a/33533514
 import logging
 from typing import List
 
-from .. import geojson, specs
+from . import geojson, specs
 
 LOGGER = logging.getLogger(__name__)
 
diff --git a/planet/scripts/cli.py b/planet/scripts/cli.py
index eeae6c0..3065ed1 100644
--- a/planet/scripts/cli.py
+++ b/planet/scripts/cli.py
@@ -62,7 +62,7 @@ def handle_exceptions(f):
     def wrapper(*args, **kwargs):
         try:
             f(*args, **kwargs)
-        except planet.api.exceptions.APIException as ex:
+        except planet.exceptions.APIException as ex:
             raise click.ClickException(ex)
     return wrapper
 
@@ -153,7 +153,7 @@ def orders(ctx, base_url):
 @coro
 @click.option('-s', '--state',
               help='Filter orders to given state.',
-              type=click.Choice(planet.api.orders.ORDERS_STATES,
+              type=click.Choice(planet.clients.orders.ORDERS_STATES,
                                 case_sensitive=False))
 @click.option('-l', '--limit', help='Filter orders to given limit.',
               default=None, type=int)
diff --git a/setup.py b/setup.py
index eac858f..66aaa35 100644
--- a/setup.py
+++ b/setup.py
@@ -15,7 +15,7 @@ from pathlib import Path
 from setuptools import setup, find_packages
 
 
-with open('planet/api/__version__.py') as f:
+with open('planet/__version__.py') as f:
     for line in f:
         if line.find("__version__") >= 0:
             version = line.split("=")[1].strip()


Test patch:
diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py
index 258e8dc..9506e0d 100644
--- a/tests/unit/test_cli.py
+++ b/tests/unit/test_cli.py
@@ -34,31 +34,9 @@ def runner():
     return CliRunner()
 
 
-def test_cli_info_verbosity(runner, monkeypatch):
-    log_level = None
-
-    def configtest(stream, level, format):
-        nonlocal log_level
-        log_level = level
-    monkeypatch.setattr(planet.scripts.cli.logging, 'basicConfig', configtest)
-
-    def patch(*args, **kwargs):
-        pass
-    monkeypatch.setattr(planet.scripts.cli, 'value', patch)
-
-    _ = runner.invoke(cli, args=['auth', 'value'])
-    assert log_level == logging.WARNING
-
-    _ = runner.invoke(cli, args=['-v', 'auth', 'value'])
-    assert log_level == logging.INFO
-
-    _ = runner.invoke(cli, args=['-vv', 'auth', 'value'])
-    assert log_level == logging.DEBUG
-
-
 def test_cli_auth_init_bad_pw(runner, monkeypatch):
     def apiexcept(*args, **kwargs):
-        raise planet.api.exceptions.APIException('nope')
+        raise planet.exceptions.APIException('nope')
     monkeypatch.setattr(planet.Auth, 'from_login', apiexcept)
     result = runner.invoke(cli, args=['auth', 'init'], input='email\npw\n')
     assert 'Error: nope' in result.output
@@ -115,7 +93,7 @@ def test_cli_orders_list_success(runner, monkeypatch):
 
 def test_cli_orders_get(runner, monkeypatch, order_description, oid):
     async def go(*arg, **kwarg):
-        return planet.api.orders.Order(order_description)
+        return planet.models.Order(order_description)
 
     monkeypatch.setattr(planet.scripts.cli.OrdersClient, 'get_order', go)
     result = runner.invoke(
@@ -192,7 +170,7 @@ def tools(tools_json, write_to_tmp_json_file):
 @pytest.fixture
 def mock_create_order(monkeypatch, order_description):
     mock_create_order = AsyncMock(
-            return_value=planet.api.orders.Order(order_description))
+            return_value=planet.models.Order(order_description))
     monkeypatch.setattr(planet.scripts.cli.OrdersClient, 'create_order',
                         mock_create_order)
     return mock_create_order
diff --git a/tests/unit/test_geojson.py b/tests/unit/test_geojson.py
index 28f9539..6094d5f 100644
--- a/tests/unit/test_geojson.py
+++ b/tests/unit/test_geojson.py
@@ -45,6 +45,10 @@ def test_geom_from_geojson_success(
     fcgeo = geojson.geom_from_geojson(featureclass_geojson)
     assert_geom_equal(fcgeo, geom_geojson)
 
+    featureclass_geojson['features'].append(feature_geojson)
+    ffcgeo = geojson.geom_from_geojson(featureclass_geojson)
+    assert_geom_equal(ffcgeo, geom_geojson)
+
 
 def test_geom_from_geojson_no_geometry(feature_geojson):
     feature_geojson.pop('geometry')
@@ -64,13 +68,6 @@ def test_geom_from_geojson_missing_type(geom_geojson):
         _ = geojson.geom_from_geojson(geom_geojson)
 
 
-def test_geom_from_geojson_multiple_features(featureclass_geojson):
-    # duplicate the feature
-    featureclass_geojson['features'] = 2 * featureclass_geojson['features']
-    with pytest.raises(geojson.MultipleFeaturesException):
-        _ = geojson.geom_from_geojson(featureclass_geojson)
-
-
 def test_validate_geom_invalid_type(geom_geojson):
     geom_geojson['type'] = 'invalid'
     with pytest.raises(geojson.GeoJSONException):
diff --git a/tests/unit/test_http.py b/tests/unit/test_http.py
index de5e995..436079a 100644
--- a/tests/unit/test_http.py
+++ b/tests/unit/test_http.py
@@ -20,7 +20,7 @@ import respx
 
 import pytest
 
-from planet.api import exceptions, http
+from planet import exceptions, http
 
 
 TEST_URL = 'mock://fantastic.com'
diff --git a/tests/unit/test_models.py b/tests/unit/test_models.py
index 8c6ac47..d261a0b 100644
--- a/tests/unit/test_models.py
+++ b/tests/unit/test_models.py
@@ -23,7 +23,7 @@ import re
 from httpx import URL
 import pytest
 
-from planet.api import models
+from planet import models
 
 
 LOGGER = logging.getLogger(__name__)
diff --git a/tests/unit/test_order_request.py b/tests/unit/test_order_request.py
index 7648b7e..a22d844 100644
--- a/tests/unit/test_order_request.py
+++ b/tests/unit/test_order_request.py
@@ -15,8 +15,7 @@ import logging
 
 import pytest
 
-from planet import geojson, specs
-from planet.api import order_request
+from planet import geojson, order_request, specs
 
 LOGGER = logging.getLogger(__name__)