Esempio n. 1
0
    def test_wait(self, vector_client, _):
        calls = [
            DotDict({
                'data': {
                    'attributes': {
                        'created': '2019-01-03T20:07:51.720000+00:00',
                        'started': '2019-01-03T20:07:51.903000+00:00',
                        'state': 'RUNNING'
                    },
                    'id': 'c589d688-3230-4caf-9f9d-18854f71e91d',
                    'type': 'copy_query'
                }
            }),
            DotDict({
                'data': {
                    'attributes': {
                        'created': '2019-01-03T20:07:51.720000+00:00',
                        'started': '2019-01-03T20:07:51.903000+00:00',
                        'state': 'DONE'
                    },
                    'id': 'c589d688-3230-4caf-9f9d-18854f71e91d',
                    'type': 'copy_query'
                }
            })
        ]

        mock_get = mock.MagicMock(side_effect=calls)
        vector_client.return_value.get_product_from_query_status = mock_get

        FeatureCollection.COMPLETION_POLL_INTERVAL_SECONDS = 1
        FeatureCollection('foo').wait_for_copy()
        self.assertEqual(2, mock_get.call_count)
Esempio n. 2
0
    def test_wait(self, vector_client, _):
        calls = [
            DotDict({
                "data": {
                    "attributes": {
                        "created": "2019-01-03T20:07:51.720000+00:00",
                        "started": "2019-01-03T20:07:51.903000+00:00",
                        "state": "RUNNING",
                    },
                    "id": "c589d688-3230-4caf-9f9d-18854f71e91d",
                    "type": "copy_query",
                }
            }),
            DotDict({
                "data": {
                    "attributes": {
                        "created": "2019-01-03T20:07:51.720000+00:00",
                        "started": "2019-01-03T20:07:51.903000+00:00",
                        "state": "DONE",
                    },
                    "id": "c589d688-3230-4caf-9f9d-18854f71e91d",
                    "type": "copy_query",
                }
            }),
        ]

        mock_get = mock.MagicMock(side_effect=calls)
        vector_client.return_value.get_product_from_query_status = mock_get

        FeatureCollection.COMPLETION_POLL_INTERVAL_SECONDS = 1
        FeatureCollection("foo").wait_for_copy()
        assert 2 == mock_get.call_count
Esempio n. 3
0
 def test_items(self):
     d = DotDict({
         "a": 1,
         "subdict": {
             "x": 0,
             "z": -1
         },
         "sublist": [{
             "y": "foo"
         }]
     })
     items = d.items()
     if six.PY2:
         assert isinstance(items, list)
     elif six.PY3:
         assert isinstance(items, DotDict_items)
     for k, v in items:
         if isinstance(v, dict):
             assert isinstance(v, DotDict)
             v.foo = "bar"
         if isinstance(v, list):
             assert isinstance(v, DotList)
             v.append(None)
     assert d.subdict.foo == "bar"
     assert d.sublist[1] is None
Esempio n. 4
0
 def test_items(self):
     d = DotDict({
         "a": 1,
         "subdict": {
             "x": 0,
             "z": -1
         },
         "sublist": [{
             "y": "foo"
         }]
     })
     items = d.items()
     if six.PY2:
         self.assertIsInstance(items, list)
     elif six.PY3:
         self.assertIsInstance(items, DotDict_items)
     for k, v in items:
         if isinstance(v, dict):
             self.assertIsInstance(v, DotDict)
             v.foo = "bar"
         if isinstance(v, list):
             self.assertIsInstance(v, DotList)
             v.append(None)
     self.assertEqual(d.subdict.foo, "bar")
     self.assertEqual(d.sublist[1], None)
Esempio n. 5
0
 def test_get(self):
     d = DotDict({"subdict": {"x": 0}})
     subdict = d.get("subdict")
     self.assertEqual(subdict.x, 0)
     subdict.foo = "bar"
     self.assertEqual(d.subdict.foo, "bar")
     default = d.get("not_here", {"foo": 1})
     self.assertEqual(default.foo, 1)
Esempio n. 6
0
 def test_get(self):
     d = DotDict({"subdict": {"x": 0}})
     subdict = d.get("subdict")
     assert subdict.x == 0
     subdict.foo = "bar"
     assert d.subdict.foo == "bar"
     default = d.get("not_here", {"foo": 1})
     assert default.foo == 1
Esempio n. 7
0
    def test_dottype_within_plain(self):
        # see note in DotDict.asdict
        # unsure if this is an important case to handle
        sub = {i: DotDict(val=i) for i in range(5)}
        obj = DotDict(sub=sub)

        unboxed = obj.asdict()
        assert self.is_unboxed(unboxed)
Esempio n. 8
0
 def test_setdefault(self):
     d = DotDict({"subdict": {"x": 0}})
     default = d.setdefault("subdict", {})
     assert default.x == 0
     default.foo = "bar"
     assert d.subdict.foo == "bar"
     missing = d.setdefault("missing", {"foo": 1})
     assert missing.foo == 1
     assert d.missing.foo == 1
Esempio n. 9
0
 def test_setdefault(self):
     d = DotDict({"subdict": {"x": 0}})
     default = d.setdefault("subdict", {})
     self.assertEqual(default.x, 0)
     default.foo = "bar"
     self.assertEqual(d.subdict.foo, "bar")
     missing = d.setdefault("missing", {"foo": 1})
     self.assertEqual(missing.foo, 1)
     self.assertEqual(d.missing.foo, 1)
    def create_features(self, product_id, features, fix_geometry='accept'):
        """Add multiple features to an existing vector product.

        :param str product_id: (Required) The ID of the Vector product to which these
            features will belong.

        :param list(dict) features: (Required) Each feature must be a dict with a geometry
            and properties field. If you provide more than 100 features,
            they will be batched in
            groups of 100, but consider using :meth:`upload_features()` instead.

        :param str fix_geometry: String specifying how to handle certain problem
            geometries, including those which do not follow counter-clockwise
            winding order (which is required by the GeoJSON spec but not many
            popular tools). Allowed values are ``reject`` (reject invalid geometries
            with an error), ``fix`` (correct invalid geometries if possible and use
            this corrected value when creating the feature), and ``accept``
            (the default) which will correct the geometry for internal use but
            retain the original geometry in the results.

        :rtype: DotDict
        :return: The features as a JSON API resource object. The keys are:

            ::

                data: A list of 'DotDict' instances.  Each instance contains
                      keys as described in create_feature().

        :raises ClientError: A variety of http-related exceptions can
            thrown. If more than 100 features were passed in, some
            of these may have been successfully inserted, others not.
            If this is a problem, then stick with <= 100 features.
        """

        if len(features) > 100:
            logging.warning(
                'create_features: feature collection has more than 100 features,'
                + ' will batch by 100 but consider using upload_features')

        # forcibly pass a zero-length list for appropriate validation error
        for i in range(0, max(len(features), 1), 100):
            attributes = [
                dict(feat, **{"fix_geometry": fix_geometry})
                for feat in features[i:i + 100]
            ]
            jsonapi = self.jsonapi_collection(type="feature",
                                              attributes_list=attributes)

            r = self.session.post('/products/{}/features'.format(product_id),
                                  json=jsonapi)
            if i == 0:
                result = DotDict(r.json())
            else:
                result.data.extend(DotDict(r.json()).data)

        return result
Esempio n. 11
0
 def test_values(self):
     d = DotDict({"subdictA": {"x": 0}, "subdictB": {"x": 1}})
     values = d.values()
     if six.PY2:
         assert isinstance(values, list)
     elif six.PY3:
         assert isinstance(values, DotDict_values)
     for v in values:
         assert isinstance(v.x, int)
         v.foo = "bar"
     assert d.subdictA.foo == "bar"
     assert d.subdictB.foo == "bar"
Esempio n. 12
0
    def test_repr(self):
        d = DotDict(long=list(range(100)))
        # long lists should be truncated with "..."
        assert "..." in repr(d)

        d = DotDict({i: i for i in range(100)})
        # a long top-level dict should not be truncated
        assert d == {i: i for i in range(100)}

        # short lists and dicts should not be truncated
        d = DotDict(short=list(range(2)), other_key=list(range(3)))
        assert d == ast.literal_eval(repr(d))
Esempio n. 13
0
 def test_values(self):
     d = DotDict({"subdictA": {"x": 0}, "subdictB": {"x": 1}})
     values = d.values()
     if six.PY2:
         self.assertIsInstance(values, list)
     elif six.PY3:
         self.assertIsInstance(values, DotDict_values)
     for v in values:
         self.assertIsInstance(v.x, int)
         v.foo = "bar"
     self.assertEqual(d.subdictA.foo, "bar")
     self.assertEqual(d.subdictB.foo, "bar")
Esempio n. 14
0
    def test_repr(self):
        d = DotDict(long=list(range(100)))
        # long lists should be truncated with "..."
        with self.assertRaises((SyntaxError, ValueError)):
            ast.literal_eval(repr(d))

        d = DotDict({i: i for i in range(100)})
        # a long top-level dict should not be truncated
        self.assertEqual(d, {i: i for i in range(100)})

        # short lists and dicts should not be truncated
        d = DotDict(short=list(range(2)), other_key=list(range(3)))
        self.assertEqual(d, ast.literal_eval(repr(d)))
Esempio n. 15
0
    def create_features(self,
                        product_id,
                        features,
                        correct_winding_order=False):
        """
        Add multiple features to an existing vector product.

        :param str product_id: (Required) Product to which this feature will belong.
        :param list(dict) features: (Required) Each feature must be a dict with a geometry
                                    and properties field. If more than 100 features,
                                    will be batched in groups of 100, but consider
                                    using upload_features() instead.
        :param bool correct_winding_order: Boolean specifying whether to correct Polygon
                                    and MultiPolygon features that do not follow counter-clockwise
                                    winding order.

        :rtype: DotDict
        :return: Created features, as a JSON API resource collection.

                 The new Features' IDs are under ``.data[i].id``,
                 and their properties are under ``.data[i].attributes``.
        :raises ClientError: A variety of http-related exceptions can
                 thrown. If more than 100 features were passed in, some
                 of these may have been successfully inserted, others not.
                 If this is a problem, then stick with <= 100 features.
        """

        if len(features) > 100:
            logging.warning(
                'create_features: feature collection has more than 100 features,'
                + ' will batch by 100 but consider using upload_features')

        # forcibly pass a zero-length list for appropriate validation error
        for i in range(0, max(len(features), 1), 100):
            attributes = ([
                dict(feat, **{"correct_winding_order": True})
                for feat in features[i:i + 100]
            ] if correct_winding_order else features[i:i + 100])
            jsonapi = self.jsonapi_collection(type="feature",
                                              attributes_list=attributes)

            r = self.session.post('/products/{}/features'.format(product_id),
                                  json=jsonapi)
            if i == 0:
                result = DotDict(r.json())
            else:
                result.data.extend(DotDict(r.json()).data)

        return result
Esempio n. 16
0
    def __init__(self, geometry, properties, id=None):
        """
        Example
        -------
        >>> polygon = {
        ...    'type': 'Polygon',
        ...    'coordinates': [[[-95, 42], [-93, 42], [-93, 40], [-95, 41], [-95, 42]]]}
        >>> properties = {"temperature": 70.13, "size": "large"}
        >>> Feature(geometry=polygon, properties=properties)  # doctest: +SKIP
        Feature({
          'geometry': {
            'coordinates': (((-95.0, 42.0), (-93.0, 42.0), (-93.0, 40.0), (-95.0, 41.0), (-95.0, 42.0)),),
            'type': 'Polygon'
          },
          'id': None,
          'properties': {
            'size': 'large',
            'temperature': 70.13
          }
        })
        """
        if geometry is None:
            raise ValueError("geometry should not be None")

        self.geometry = shape(geometry)

        self.properties = DotDict(properties)
        self.id = id
Esempio n. 17
0
    def shape(self, slug, output='geojson', geom='low'):
        """Get the geometry for a specific slug

        :param slug: Slug identifier.
        :param str output: Desired geometry format (`GeoJSON`).
        :param str geom: Desired resolution for the geometry (`low`, `medium`, `high`).

        :return: GeoJSON ``Feature``

        Example::
            >>> from descarteslabs.client.services import Places
            >>> kansas = Places().shape('north-america_united-states_kansas')
            >>> kansas['bbox']
            [-102.051744, 36.993016, -94.588658, 40.003078]

            >>> kansas['geometry']['type']
            'Polygon'

            >>> kansas['properties']
            {
              'name': 'Kansas',
              'parent_id': 85633793,
              'path': 'continent:north-america_country:united-states_region:kansas',
              'placetype': 'region',
              'slug': 'north-america_united-states_kansas'
            }

        """
        r = self.session.get('/shape/%s.%s' % (slug, output),
                             params={'geom': geom})
        return DotDict(r.json())
Esempio n. 18
0
 def _fetch_upload_result_page(self, product_id, continuation_token=None):
     r = self.session.get(
         '/products/{}/features/uploads'.format(product_id),
         params={'continuation_token': continuation_token},
         headers={'Content-Type': 'application/json'},
     )
     return DotDict(r.json())
Esempio n. 19
0
 def create_features(product_id,
                     attributes,
                     correct_winding_order=False):
     return DotDict(data=[
         dict(id=attr['properties']['id'], attributes=attr)
         for attr in attributes
     ])
Esempio n. 20
0
 def test_delattr(self):
     d = DotDict(delete=0)
     del d.delete
     assert "delete" not in d
     with pytest.raises(AttributeError):
         del d.delete
     pass
Esempio n. 21
0
    def create_feature(self, product_id, geometry, properties=None):
        """
        Add a feature to an existing vector product.

        :param str product_id: (Required) Product to which this feature will belong.
        :param dict geometry: (Required) Shape associated with this vector feature.
                              This accepts the following types of GeoJSON geometries:
                              - Points
                              - MultiPoints
                              - Polygons
                              - MultiPolygons
                              - LineStrings
                              - MultiLineStrings
                              - GeometryCollections

        :param dict properties: Dictionary of arbitrary properties.

        :rtype: DotDict
        :return: Created Feature, as a JSON API resource collection.

                 The new Feature's ID is under ``.data[0].id``,
                 and its properties are under ``.data[0].attributes``.
        """
        params = dict(geometry=geometry, properties=properties)

        jsonapi = self.jsonapi_document(type="feature", attributes=params)
        r = self.session.post('/products/{}/features'.format(product_id),
                              json=jsonapi)
        return DotDict(r.json())
Esempio n. 22
0
    def get(self, image_id):
        """Get metadata of a single image.

        :param str image_id: Image identifier.

        :return: A dictionary of metadata for a single image.
        :rtype: DotDict

        :raises ~descarteslabs.client.exceptions.NotFoundError: Raised if image id cannot
             be found.

        Example::

            >>> from descarteslabs.client.services import Metadata
            >>> meta = Metadata().get('landsat:LC08:PRE:TOAR:meta_LC80270312016188_v1')
            >>> keys = list(meta.keys())
            >>> keys.sort()
            >>> keys
            ['acquired', 'area', 'bits_per_pixel', 'bright_fraction', 'bucket',
             'cloud_fraction', 'cloud_fraction_0', 'confidence_dlsr', 'cs_code',
             'descartes_version', 'file_md5s', 'file_sizes', 'files', 'fill_fraction',
             'geolocation_accuracy', 'geometry', 'geotrans', 'id', 'identifier', 'key',
             'owner_type', 'processed', 'product', 'proj4', 'projcs', 'published',
             'raster_size', 'reflectance_scale', 'roll_angle', 'sat_id',
             'solar_azimuth_angle', 'solar_elevation_angle', 'storage_state',
             'sw_version', 'terrain_correction', 'tile_id']
        """
        r = self.session.get("/get/{}".format(image_id))
        return DotDict(r.json())
Esempio n. 23
0
 def test_delattr(self):
     d = DotDict(delete=0)
     del d.delete
     self.assertNotIn("delete", d)
     with self.assertRaises(AttributeError):
         del d.delete
     pass
Esempio n. 24
0
    def prefix(self, slug, output="geojson", placetype=None, geom="low"):
        """Get all the places that start with a prefix

        :param str slug: Slug identifier.
        :param str output: Desired geometry format (`GeoJSON`, `TopoJSON`).
        :param str placetype: Restrict results to a particular place type.
        :param str geom: Desired resolution for the geometry (`low`, `medium`, `high`).

        :return: GeoJSON or TopoJSON ``FeatureCollection``

        Example::
            >>> from descarteslabs.client.services import Places
            >>> il_counties = Places().prefix('north-america_united-states_illinois', placetype='county')
            >>> len(il_counties['features'])
            102

        """
        params = {}

        if placetype:
            params["placetype"] = placetype
        params["geom"] = geom
        r = self.session.get("/prefix/%s.%s" % (slug, output), params=params)

        return DotDict(r.json())
Esempio n. 25
0
    def setUp(self):
        # date format is locale-dependent, so a hardcoded date string could fail for users from different locales
        date = datetime.datetime(2015, 6, 1, 14, 25, 10)
        self.date_str = date.strftime("%c")

        properties = {
            "id": "prod:foo",
            "product": "prod",
            "crs": "EPSG:32615",
            "date": date,
            "bands": collections.OrderedDict([  # necessary to ensure deterministic order in tests
                ("blue", {
                    "resolution": 5,
                    "resolution_unit": "smoot",
                    "dtype": "UInt16",
                    "data_range": [0, 10000],
                    "physical_range": [0, 1],
                    "data_unit": "TOAR",
                }),
                ("alpha", {
                    "resolution": 5,
                    "resolution_unit": "smoot",
                    "dtype": "UInt8",
                    "data_range": [0, 1],
                    "physical_range": [0, 1],
                })
            ])
        }
        properties = DotDict(properties)
        self.scene = MockScene({}, properties)
    def get_product_from_query_status(self, product_id):
        """Get the status of the job creating a new product from a query.

        :param str product_id: (Required) The ID of the product for which to
            to check the status.  This ID must have been created
            by a call to :meth:`create_product_from_query`.

        :rtype: DotDict
        :return: A dictionary with information about the status.  The keys are
            ::

                data: A 'DotDict' instance with the following keys:

                    id:         The internal ID for this job.
                    type:       'copy_job'.
                    attributes: A DotDict instance with the following keys:

                        created: Time that the task was created in ISO-8601 UTC.
                        ended:   Time that the task completed in ISO-8601 UTC
                                 (when available).
                        started: Time that the start stared in ISO-8601 UTC
                                 (when available).
                        state:   'PENDING', 'RUNNING', or 'DONE'.
        """

        r = self.session.get(
            "/products/{}/search/copy/status".format(product_id))
        return DotDict(r.json())
    def get_product(self, product_id):
        """
        Get a product's properties.

        :param str product_id: (Required) The ID of the Vector product to fetch.

        :rtype: DotDict
        :return: The vector product, as a JSON API resource object. The keys are:

            ::

                data: A single 'DotDict' instance with the following keys:

                    id:   The ID of the Vectort product.
                    type: 'product'.
                    meta: A single DotDict instance with the following keys:

                        created: Time that the task was created in ISO-8601 UTC.

                    attributes: A single DotDict instance with the following keys:

                        title:       The title given to this product.
                        description: The description given to this product.
                        owners:      The owners of this product (at a minimum
                                     the organization and the user who created
                                     this product).
                        readers:     The users, groups, or organizations that
                                     can read this product.
                        writers:     The users, groups, or organizations that
                                     can write into this product.
        """
        r = self.session.get('/products/{}'.format(product_id))
        return DotDict(r.json())
Esempio n. 28
0
    def dltile_from_latlon(self, lat, lon, resolution, tilesize, pad):
        """
        Return a DLTile GeoJSON Feature that covers a latitude/longitude

        :param float lat: Requested latitude
        :param float lon: Requested longitude
        :param float resolution: Resolution of DLTile
        :param int tilesize: Number of valid pixels per DLTile
        :param int pad: Number of ghost pixels per DLTile (overlap among tiles)

        :return: A DLTile GeoJSON Feature
        :rtype: DotDict

        Example::

            >>> from descarteslabs.client.services import Raster
            >>> Raster().dltile_from_latlon(45, 60, 15.0, 1024, 16)
            {
              'geometry': {
                'coordinates': [
                  [
                    [59.88428127486419, 44.8985115884728...],
                    [60.08463455818353, 44.90380671613201],
                    [60.077403974563175, 45.046212550598135],
                    [59.87655568675822, 45.040891215906676],
                    ...
                  ]
                ],
                'type': 'Polygon'
              },
              'properties': {
                'cs_code': 'EPSG:32641',
                'geotrans': [
                  254000.0,
                  15.0,
                  0,
                  4992240.0,
                  ...
                ],
                'key': '1024:16:15.0:41:-16:324',
                'outputBounds': [254000.0, 4976400.0, 269840.0, 4992240.0],
                'pad': 16,
                'proj4': '+proj=utm +zone=41 +datum=WGS84 +units=m +no_defs ',
                'resolution': 15.0,
                'ti': -16,
                'tilesize': 1024,
                'tj': 324,
                'wkt': 'PROJCS["WGS 84 / UTM zone 41N",GEOGCS["WGS...Northing",NORTH],AUTHORITY["EPSG","32641"]]',
                'zone': 41
              },
              'type': 'Feature'
            }
        """
        params = {"resolution": resolution, "tilesize": tilesize, "pad": pad}

        r = self.session.get("/dlkeys/from_latlon/%f/%f" % (lat, lon),
                             params=params)

        return DotDict(r.json())
Esempio n. 29
0
    def dltile(self, key):
        """
        Given a DLTile key, return a DLTile GeoJSON Feature

        :param str key: A DLTile key that identifies a DLTile

        :return: A DLTile GeoJSON Feature
        :rtype: DotDict

        :raises descarteslabs.client.exceptions.BadRequestError: if the given key
            is not a valid DLTile key

        Example::

            >>> from descarteslabs.client.services import Raster
            >>> Raster().dltile("1024:16:15.0:41:-16:324")
            {
              'geometry': {
                'coordinates': [
                  [
                    [59.88428127486419, 44.8985115884728...],
                    [60.08463455818353, 44.90380671613201],
                    [60.077403974563175, 45.046212550598135],
                    [59.87655568675822, 45.040891215906676],
                    ...
                  ]
                ],
                'type': 'Polygon'
              },
              'properties': {
                'cs_code': 'EPSG:32641',
                'geotrans': [
                  254000.0,
                  15.0,
                  0,
                  4992240.0,
                  ...
                ],
                'key': '1024:16:15.0:41:-16:324',
                'outputBounds': [254000.0, 4976400.0, 269840.0, 4992240.0],
                'pad': 16,
                'proj4': '+proj=utm +zone=41 +datum=WGS84 +units=m +no_defs ',
                'resolution': 15.0,
                'ti': -16,
                'tilesize': 1024,
                'tj': 324,
                'wkt': 'PROJCS["WGS 84 / UTM zone 41N",GEOGCS["WGS...Northing",NORTH],AUTHORITY["EPSG","32641"]]',
                'zone': 41
              },
              'type': 'Feature'
            }
        """

        if not key:
            raise ValueError("Invalid key")

        r = self.session.get("/dlkeys/%s" % key)

        return DotDict(r.json())
Esempio n. 30
0
 def get_webhook(self, group_id, webhook_id):
     r = self.session.get(
         '/groups/{group_id}/webhooks/{webhook_id}'.format(
             group_id=group_id,
             webhook_id=webhook_id,
         ), )
     r.raise_for_status()
     return DotDict(r.json())