datacosmos 0.0.7__py3-none-any.whl → 0.0.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of datacosmos might be problematic. Click here for more details.

@@ -29,7 +29,6 @@ class Config(BaseSettings):
29
29
  authentication: Optional[AuthenticationConfig] = None
30
30
  stac: Optional[URL] = None
31
31
  datacosmos_cloud_storage: Optional[URL] = None
32
- mission_id: int = 0
33
32
 
34
33
  DEFAULT_AUTH_TYPE: ClassVar[str] = "m2m"
35
34
  DEFAULT_AUTH_TOKEN_URL: ClassVar[str] = "https://login.open-cosmos.com/oauth/token"
@@ -1,8 +1,4 @@
1
- """DatacosmosClient handles authenticated interactions with the Datacosmos API.
2
-
3
- Automatically manages token refreshing and provides HTTP convenience
4
- methods.
5
- """
1
+ """Client to interact with the Datacosmos API with authentication and request handling."""
6
2
 
7
3
  from datetime import datetime, timedelta, timezone
8
4
  from typing import Any, Optional
@@ -19,23 +15,57 @@ from datacosmos.exceptions.datacosmos_exception import DatacosmosException
19
15
  class DatacosmosClient:
20
16
  """Client to interact with the Datacosmos API with authentication and request handling."""
21
17
 
22
- def __init__(self, config: Optional[Config] = None):
18
+ def __init__(
19
+ self,
20
+ config: Optional[Config | Any] = None,
21
+ http_session: Optional[requests.Session | OAuth2Session] = None,
22
+ ):
23
23
  """Initialize the DatacosmosClient.
24
24
 
25
25
  Args:
26
- config (Optional[Config]): Configuration object.
26
+ config (Optional[Config]): Configuration object (only needed when SDK creates its own session).
27
+ http_session (Optional[requests.Session]): Pre-authenticated session.
27
28
  """
28
- if config:
29
- self.config = config
30
- else:
29
+ if http_session is not None:
30
+ self._http_client = http_session
31
+ self._owns_session = False
32
+ if isinstance(http_session, OAuth2Session):
33
+ token_data = http_session.token
34
+ elif isinstance(http_session, requests.Session):
35
+ auth_header = http_session.headers.get("Authorization", "")
36
+ if not auth_header.startswith("Bearer "):
37
+ raise DatacosmosException(
38
+ "Injected requests.Session must include a 'Bearer' token in its headers"
39
+ )
40
+ token_data = {"access_token": auth_header.split(" ", 1)[1]}
41
+ else:
42
+ raise DatacosmosException(
43
+ f"Unsupported session type: {type(http_session)}"
44
+ )
31
45
  try:
32
- self.config = Config.from_yaml()
33
- except ValueError:
34
- self.config = Config.from_env()
46
+ self.token = token_data.get("access_token")
47
+ self.token_expiry = token_data.get("expires_at") or token_data.get(
48
+ "expires_in"
49
+ )
50
+ except Exception:
51
+ raise DatacosmosException(
52
+ "Failed to extract token from injected session"
53
+ )
35
54
 
36
- self.token = None
37
- self.token_expiry = None
38
- self._http_client = self._authenticate_and_initialize_client()
55
+ self.config = config
56
+ else:
57
+ if config:
58
+ self.config = config
59
+ else:
60
+ try:
61
+ self.config = Config.from_yaml()
62
+ except ValueError:
63
+ self.config = Config.from_env()
64
+
65
+ self._owns_session = True
66
+ self.token = None
67
+ self.token_expiry = None
68
+ self._http_client = self._authenticate_and_initialize_client()
39
69
 
40
70
  def _authenticate_and_initialize_client(self) -> requests.Session:
41
71
  """Authenticate and initialize the HTTP client with a valid token."""
@@ -68,8 +98,10 @@ class DatacosmosClient:
68
98
  ) from e
69
99
 
70
100
  def _refresh_token_if_needed(self):
71
- """Refresh the token if it has expired."""
72
- if not self.token or self.token_expiry <= datetime.now(timezone.utc):
101
+ """Refresh the token if it has expired (only if SDK created it)."""
102
+ if self._owns_session and (
103
+ not self.token or self.token_expiry <= datetime.now(timezone.utc)
104
+ ):
73
105
  self._http_client = self._authenticate_and_initialize_client()
74
106
 
75
107
  def request(
@@ -8,7 +8,6 @@ import structlog
8
8
 
9
9
  from datacosmos.stac.enums.processing_level import ProcessingLevel
10
10
  from datacosmos.stac.item.models.datacosmos_item import DatacosmosItem
11
- from datacosmos.utils.missions import get_mission_id
12
11
 
13
12
  logger = structlog.get_logger()
14
13
 
@@ -35,11 +34,6 @@ class UploadPath:
35
34
  cls, item: DatacosmosItem, mission: str, item_path: str
36
35
  ) -> "Path":
37
36
  """Create a Path instance from a DatacosmosItem and a path."""
38
- for asset in item.assets.values():
39
- if mission == "":
40
- mission = cls._get_mission_name(asset.href)
41
- else:
42
- break
43
37
  dt = datetime.strptime(item.properties["datetime"], "%Y-%m-%dT%H:%M:%SZ")
44
38
  path = UploadPath(
45
39
  mission=mission,
@@ -67,27 +61,3 @@ class UploadPath:
67
61
  id=parts[5],
68
62
  path="/".join(parts[6:]),
69
63
  )
70
-
71
- @classmethod
72
- def _get_mission_name(cls, href: str) -> str:
73
- mission = ""
74
- # bruteforce mission name from asset path
75
- # traverse the path and check if any part is a mission name (generates a mission id)
76
- href_parts = href.split("/")
77
- for idx, part in enumerate(href_parts):
78
- try:
79
- # when an id is found, then the mission name is valid
80
- get_mission_id(
81
- part, "test"
82
- ) # using test as it is more wide and anything on prod should exists on test
83
- except KeyError:
84
- continue
85
- # validate the mission name by checking if the path is correct
86
- # using the same logic as the __str__ method
87
- mission = part.lower()
88
- h = "/".join(["full", *href_parts[idx:]])
89
- p = UploadPath.from_path("/".join([mission, *href_parts[idx + 1 :]]))
90
- if str(p) != h:
91
- raise ValueError(f"Could not find mission name in asset path {href}")
92
- break
93
- return mission
@@ -9,7 +9,6 @@ from datacosmos.datacosmos_client import DatacosmosClient
9
9
  from datacosmos.stac.item.item_client import ItemClient
10
10
  from datacosmos.stac.item.models.datacosmos_item import DatacosmosItem
11
11
  from datacosmos.uploader.dataclasses.upload_path import UploadPath
12
- from datacosmos.utils.missions import get_mission_name
13
12
 
14
13
 
15
14
  class DatacosmosUploader:
@@ -17,14 +16,10 @@ class DatacosmosUploader:
17
16
 
18
17
  def __init__(self, client: DatacosmosClient):
19
18
  """Initialize the uploader with DatacosmosClient."""
20
- mission_id = client.config.mission_id
21
- environment = client.config.environment
19
+ self.environment = client.config.environment
22
20
 
23
21
  self.datacosmos_client = client
24
22
  self.item_client = ItemClient(client)
25
- self.mission_name = (
26
- get_mission_name(mission_id, environment) if mission_id != 0 else ""
27
- )
28
23
  self.base_url = client.config.datacosmos_cloud_storage.as_domain_url()
29
24
 
30
25
  def upload_and_register_item(self, item_json_file_path: str) -> None:
@@ -92,9 +87,9 @@ class DatacosmosUploader:
92
87
  except Exception: # nosec
93
88
  pass # Ignore if item doesn't exist
94
89
 
95
- def _get_upload_path(self, item: DatacosmosItem) -> str:
90
+ def _get_upload_path(self, item: DatacosmosItem, mission_name: str = "") -> str:
96
91
  """Constructs the storage upload path based on the item and mission name."""
97
- return UploadPath.from_item_path(item, self.mission_name, "")
92
+ return UploadPath.from_item_path(item, mission_name, "")
98
93
 
99
94
  def _update_item_assets(self, item: DatacosmosItem) -> None:
100
95
  """Updates the item's assets with uploaded file URLs."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datacosmos
3
- Version: 0.0.7
3
+ Version: 0.0.8
4
4
  Summary: A library for interacting with DataCosmos from Python code
5
5
  Author-email: Open Cosmos <support@open-cosmos.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -1,7 +1,7 @@
1
1
  datacosmos/__init__.py,sha256=dVHKpbz5FVtfoJAWHRdsUENG6H-vs4UrkuwnIvOGJr4,66
2
- datacosmos/datacosmos_client.py,sha256=Uage-PRVRrDWy5frefcR2FClDsdpbFBS4JQC3OCupJU,4978
2
+ datacosmos/datacosmos_client.py,sha256=3BurTz1fPk1Dzp8B5xt5gZZrFiqk1AT5oaqKeYmXPec,6517
3
3
  datacosmos/config/__init__.py,sha256=KCsaTb9-ZgFui1GM8wZFIPLJy0D0O8l8Z1Sv3NRD9UM,140
4
- datacosmos/config/config.py,sha256=xSzri8sr_hRK1JlG0mxuBW5dLQ4ExL00uk3-77Z4wHo,7243
4
+ datacosmos/config/config.py,sha256=qj9e68enxX61Iw8lqEXUUCNJ2O7vy-1j9nAoBIB-4Ao,7219
5
5
  datacosmos/config/models/__init__.py,sha256=r3lThPkyKjBjUZXRNscFzOrmn_-m_i9DvG3RePfCFYc,41
6
6
  datacosmos/config/models/authentication_config.py,sha256=01Q90-yupbJ5orYDtatZIm9EaL7roQ-oUMoZfFMRzIM,499
7
7
  datacosmos/config/models/local_user_account_authentication_config.py,sha256=8WApn720MBXMKQa6w7bCd7Z37GRmYR-I7mBUgUI20lQ,701
@@ -32,20 +32,18 @@ datacosmos/stac/item/models/eo_band.py,sha256=YC3Scn_wFhIo51pIVcJeuJienF7JGWoEv3
32
32
  datacosmos/stac/item/models/item_update.py,sha256=_CpjQn9SsfedfuxlHSiGeptqY4M-p15t9YX__mBRueI,2088
33
33
  datacosmos/stac/item/models/raster_band.py,sha256=CoEVs-YyPE5Fse0He9DdOs4dGZpzfCsCuVzOcdXa_UM,354
34
34
  datacosmos/uploader/__init__.py,sha256=ZtfCVJ_pWKKh2F1r_NArnbG3_JtpcEiXcA_tmSwSKmQ,128
35
- datacosmos/uploader/datacosmos_uploader.py,sha256=QFFzR9Z2KFu_G5EcmvEn251IiwbPAfZSrOYZ_vC3NSg,4393
35
+ datacosmos/uploader/datacosmos_uploader.py,sha256=FDBUSZ9mpieXi3dms3RM17aIrnRuAKd0rFkD7Jwl1pY,4195
36
36
  datacosmos/uploader/dataclasses/__init__.py,sha256=IjcyA8Vod-z1_Gi1FMZhK58Owman0foL25Hs0YtkYYs,43
37
- datacosmos/uploader/dataclasses/upload_path.py,sha256=X8zkfw3_FO9qTiKHu-nL_uDmQJYfaov6e4Y2-f-opaU,3204
37
+ datacosmos/uploader/dataclasses/upload_path.py,sha256=5QadynHxkJrnOk1lyPtLyiVAHdzBshEuhjA9hwVF0NI,1903
38
38
  datacosmos/utils/__init__.py,sha256=XQbAnoqJrPpnSpEzAbjh84yqYWw8cBM8mNp8ynTG-54,50
39
- datacosmos/utils/constants.py,sha256=f7pOqCpdXk7WFGoaTyuCpr65jb-TtfhoVGuYTz3_T6Y,272
40
- datacosmos/utils/missions.py,sha256=7GOnrjxB8V11C_Jr3HHI4vpXifgkOSeirNjIDx17C58,940
41
39
  datacosmos/utils/url.py,sha256=iQwZr6mYRoePqUZg-k3KQSV9o2wju5ZuCa5WS_GyJo4,2114
42
40
  datacosmos/utils/http_response/__init__.py,sha256=BvOWwC5coYqq_kFn8gIw5m54TLpdfJKlW9vgRkfhXiA,33
43
41
  datacosmos/utils/http_response/check_api_response.py,sha256=dKWW01jn2_lWV0xpOBABhEP42CFSsx9dP0iSxykbN54,1186
44
42
  datacosmos/utils/http_response/models/__init__.py,sha256=Wj8YT6dqw7rAz_rctllxo5Or_vv8DwopvQvBzwCTvpw,45
45
43
  datacosmos/utils/http_response/models/datacosmos_error.py,sha256=Uqi2uM98nJPeCbM7zngV6vHSk97jEAb_nkdDEeUjiQM,740
46
44
  datacosmos/utils/http_response/models/datacosmos_response.py,sha256=oV4n-sue7K1wwiIQeHpxdNU8vxeqF3okVPE2rydw5W0,336
47
- datacosmos-0.0.7.dist-info/licenses/LICENSE.md,sha256=vpbRI-UUbZVQfr3VG_CXt9HpRnL1b5kt8uTVbirxeyI,1486
48
- datacosmos-0.0.7.dist-info/METADATA,sha256=tCAZlxKlDV8qI9pjgvdREZBffzkwJ4Ko0CMiK9oe2Bg,896
49
- datacosmos-0.0.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
50
- datacosmos-0.0.7.dist-info/top_level.txt,sha256=ueobs5CNeyDbPMgXPcVV0d0yNdm8CvGtDT3CaksRVtA,11
51
- datacosmos-0.0.7.dist-info/RECORD,,
45
+ datacosmos-0.0.8.dist-info/licenses/LICENSE.md,sha256=vpbRI-UUbZVQfr3VG_CXt9HpRnL1b5kt8uTVbirxeyI,1486
46
+ datacosmos-0.0.8.dist-info/METADATA,sha256=tK8nQIG9_5zJ-gCQPQB7S2q6z7HabdgYdpPLfkinP9g,896
47
+ datacosmos-0.0.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
48
+ datacosmos-0.0.8.dist-info/top_level.txt,sha256=ueobs5CNeyDbPMgXPcVV0d0yNdm8CvGtDT3CaksRVtA,11
49
+ datacosmos-0.0.8.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- """Package for storing constants."""
2
-
3
- TEST_MISSION_NAMES = {
4
- 55: "MENUT",
5
- 56: "PHISAT-2",
6
- 57: "HAMMER",
7
- 63: "MANTIS",
8
- 64: "PLATERO",
9
- }
10
- PROD_MISSION_NAMES = {
11
- 23: "MENUT",
12
- 29: "MANTIS",
13
- 35: "PHISAT-2",
14
- 37: "PLATERO",
15
- 48: "HAMMER",
16
- }
@@ -1,27 +0,0 @@
1
- """Package for storing mission specific information."""
2
-
3
- from datacosmos.utils.constants import PROD_MISSION_NAMES, TEST_MISSION_NAMES
4
-
5
-
6
- def get_mission_name(mission: int, env: str) -> str:
7
- """Get the mission name from the mission number."""
8
- if env == "test" or env == "local":
9
- return TEST_MISSION_NAMES[mission]
10
- elif env == "prod":
11
- return PROD_MISSION_NAMES[mission]
12
- else:
13
- raise ValueError(f"Unsupported environment: {env}")
14
-
15
-
16
- def get_mission_id(mission_name: str, env: str) -> int:
17
- """Get the mission number from the mission name."""
18
- if env == "test" or env == "local":
19
- return {v.upper(): k for k, v in TEST_MISSION_NAMES.items()}[
20
- mission_name.upper()
21
- ]
22
- elif env == "prod":
23
- return {v.upper(): k for k, v in PROD_MISSION_NAMES.items()}[
24
- mission_name.upper()
25
- ]
26
- else:
27
- raise ValueError(f"Unsupported environment: {env}")