Skip to content

Commit

Permalink
Core stac client and asset file property (#716)
Browse files Browse the repository at this point in the history
* Simplify stac client in Asset class

* Apply hooks

* Base stac client

* Asset file property

* Bump version
  • Loading branch information
javidq authored Dec 19, 2024
1 parent d0fb8f6 commit 6cddebf
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 41 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ You can check your current version with the following command:
```

For more information, see [UP42 Python package description](https://pypi.org/project/up42-py/).
## 2.2.0a19
**Dec 20, 2024**
- Switched to using stac client descriptor in `Asset` class and reduced duplication.
- Publish `stac_client` function to `up42` namespace.
- Added `file` property to `Asset` class to unify with `pystac::Asset` experience.
- Deprecated `Asset::download` method in favour of `Asset.file::download`.

## 2.2.0a18
**Dec 19, 2024**
- Use unauthenticated session for signed url image file in `FileProvider` module with `FileProvider` class.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "up42-py"
version = "2.2.0a18"
version = "2.2.0a19"
description = "Python SDK for UP42, the geospatial marketplace and developer platform."
authors = ["UP42 GmbH <[email protected]>"]
license = "https://github.com/up42/up42-py/blob/master/LICENSE"
Expand Down
7 changes: 7 additions & 0 deletions tests/test_asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,13 @@ def test_should_save(self, requests_mock: req_mock.Mocker):
ASSET, info=expected_info, title=title, tags=tags, updated_at=updated_at
)

def test_should_provide_file(self, requests_mock: req_mock.Mocker):
requests_mock.post(
url=f"{constants.API_HOST}/v2/assets/{ASSET_ID}/download-url",
json={"url": DOWNLOAD_URL},
)
assert ASSET.file == utils.ImageFile(DOWNLOAD_URL)

def test_should_download_expected_files(
self,
requests_mock: req_mock.Mocker,
Expand Down
8 changes: 8 additions & 0 deletions tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,11 @@ def test_should_provide_stac_client(self, requests_mock: req_mock.Mocker):
record = ActiveRecord()
assert isinstance(record.stac_client, pystac_client.Client)
assert requests_mock.called


class TestStacClient:
def test_should_provide_stac_client(self, requests_mock: req_mock.Mocker):
requests_mock.get(constants.URL_STAC_CATALOG, json=constants.STAC_CATALOG_RESPONSE)
stac_client = base.stac_client()
assert isinstance(stac_client, pystac_client.Client)
assert requests_mock.called
3 changes: 2 additions & 1 deletion up42/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

# pylint: disable=only-importing-modules-is-allowed
from up42.asset import Asset, AssetSorting
from up42.base import authenticate, get_credits_balance
from up42.base import authenticate, get_credits_balance, stac_client
from up42.catalog import Catalog
from up42.glossary import CollectionSorting, CollectionType, ProductGlossary
from up42.initialization import (
Expand Down Expand Up @@ -67,6 +67,7 @@
initialize_asset,
authenticate,
get_credits_balance,
stac_client,
Job,
JobSorting,
JobStatus,
Expand Down
70 changes: 32 additions & 38 deletions up42/asset.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import dataclasses
import datetime as dt
import pathlib
from typing import Iterator, List, Literal, Optional, Tuple, Union, cast
from typing import Iterator, List, Literal, Optional, Union, cast

import pystac
import pystac_client
import requests
import tenacity as tnc

from up42 import base, host, utils
Expand All @@ -30,6 +29,7 @@ class AssetSorting:
@dataclasses.dataclass
class Asset:
session = base.Session()
stac_client = base.StacClient()
id: str
account_id: str
created_at: str
Expand Down Expand Up @@ -106,44 +106,36 @@ def all(
}
return map(cls._from_metadata, utils.paged_query(params, "/v2/assets", cls.session))

def _stac_search(self) -> Tuple[pystac_client.Client, pystac_client.ItemSearch]:
stac_client = utils.stac_client(cast(requests.auth.AuthBase, self.session.auth))
stac_search_parameters = {
"max_items": MAX_ITEM,
"limit": LIMIT,
"filter": {
"op": "=",
"args": [
{"property": "asset_id"},
self.asset_id,
],
},
}
return stac_client, stac_client.search(filter=stac_search_parameters)

@property
@_retry
def stac_info(self) -> Union[pystac.Collection, pystac_client.CollectionClient]:
"""
Gets the storage STAC information for the asset as a FeatureCollection.
One asset can contain multiple STAC items (e.g. the PAN and multispectral images).
"""
stac_client, stac_search = self._stac_search()
items = stac_search.item_collection()
if not items:
raise ValueError(f"No STAC metadata information available for this asset {self.asset_id}")
return stac_client.get_collection(items[0].collection_id)

@property
@_retry
def stac_items(self) -> pystac.ItemCollection:
"""Returns the stac items from an UP42 asset STAC representation."""
try:
_, stac_search = self._stac_search()
filter_by_asset_id = {
"max_items": MAX_ITEM,
"limit": LIMIT,
"filter": {
"op": "=",
"args": [
{"property": "asset_id"},
self.asset_id,
],
},
}
stac_search = self.stac_client.search(filter=filter_by_asset_id)
return stac_search.item_collection()
except Exception as exc:
raise ValueError(f"No STAC metadata information available for this asset {self.asset_id}") from exc

@property
@_retry
def stac_info(self) -> Union[pystac.Collection, pystac_client.CollectionClient]:
"""
Gets the storage STAC information for the asset as a FeatureCollection.
One asset can contain multiple STAC items (e.g. the PAN and multispectral images).
"""
return self.stac_client.get_collection(self.stac_items[0].collection_id)

@utils.deprecation("Asset::save", "3.0.0")
def update_metadata(
self,
Expand Down Expand Up @@ -174,13 +166,14 @@ def save(self):
for field in dataclasses.fields(asset):
setattr(self, field.name, getattr(asset, field.name))

def _get_download_url(self, stac_asset_id: Optional[str] = None) -> str:
if stac_asset_id is None:
url = host.endpoint(f"/v2/assets/{self.asset_id}/download-url")
else:
url = host.endpoint(f"/v2/assets/{stac_asset_id}/download-url")
def _get_download_url(self, asset_id: str) -> str:
url = host.endpoint(f"/v2/assets/{asset_id}/download-url")
return self.session.post(url=url).json()["url"]

@property
def file(self) -> utils.ImageFile:
return utils.ImageFile(url=self._get_download_url(self.asset_id))

@utils.deprecation("pystac::Asset.file.url", "3.0.0")
def get_stac_asset_url(self, stac_asset: pystac.Asset):
"""
Expand All @@ -192,8 +185,9 @@ def get_stac_asset_url(self, stac_asset: pystac.Asset):
Signed URL for the STAC Asset.
"""
stac_asset_id = stac_asset.href.split("/")[-1]
return self._get_download_url(stac_asset_id=stac_asset_id)
return self._get_download_url(asset_id=stac_asset_id)

@utils.deprecation("Asset.file::download", "3.0.0")
def download(
self,
output_directory: Union[str, pathlib.Path, None] = None,
Expand All @@ -219,7 +213,7 @@ def download(
output_directory.mkdir(parents=True, exist_ok=True)
logger.info("Download directory: %s", output_directory)

download_url = self._get_download_url()
download_url = self._get_download_url(asset_id=self.asset_id)
download = utils.download_archive if unpacking else utils.download_file
return download(
download_url=download_url,
Expand Down
6 changes: 5 additions & 1 deletion up42/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ def get_credits_balance(self) -> dict:
get_credits_balance = workspace.get_credits_balance


def stac_client():
return utils.stac_client(workspace.auth)


class Session:
def __get__(self, obj, obj_type=None) -> requests.Session:
return workspace.session
Expand All @@ -101,4 +105,4 @@ def __set__(self, obj, value: str) -> None:

class StacClient:
def __get__(self, obj, obj_type=None) -> pystac_client.Client:
return utils.stac_client(workspace.auth)
return stac_client()

0 comments on commit 6cddebf

Please sign in to comment.