osdu-python-client 0.9.0__py3-none-any.whl → 0.11.0__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.
@@ -12,6 +12,7 @@ from osdu_python_client.errors import (
12
12
  OsduConfigError,
13
13
  OsduError,
14
14
  OsduRetryExhausted,
15
+ OsduTransportError,
15
16
  )
16
17
  from osdu_python_client.logging_setup import enable_debug_logging
17
18
 
@@ -26,6 +27,7 @@ __all__ = [
26
27
  "OsduConfigError",
27
28
  "OsduError",
28
29
  "OsduRetryExhausted",
30
+ "OsduTransportError",
29
31
  "TokenProvider",
30
32
  "enable_debug_logging",
31
33
  ]
@@ -15,7 +15,7 @@ from osdu_python_client.services.registry import (
15
15
  ServiceSpec,
16
16
  load_authenticated_client,
17
17
  )
18
- from osdu_python_client.transport import AsyncRetryTransport
18
+ from osdu_python_client.transport import AsyncRetryTransport, warn_on_overwritten_headers
19
19
 
20
20
 
21
21
  class AsyncOsduClient:
@@ -98,6 +98,17 @@ class AsyncOsduClient:
98
98
  async def with_headers(
99
99
  self, *, service: str | None = None, **headers: str
100
100
  ) -> AsyncIterator["AsyncOsduClient"]:
101
+ """Temporarily set extra headers on one or all built service clients.
102
+
103
+ The headers are written onto the service clients themselves, which
104
+ every caller of this ``AsyncOsduClient`` shares. Use it for process-wide
105
+ values (a CRS ``frame-of-reference``, a batch job's correlation id),
106
+ not per-request ones: in a web service, concurrent requests would see
107
+ each other's headers. Per-request partition goes in as
108
+ ``data_partition_id=`` on the operation; per-request auth goes through
109
+ a ``TokenProvider``.
110
+ """
111
+ warn_on_overwritten_headers(headers)
101
112
  if service is not None:
102
113
  getattr(self, service)
103
114
  # ``_httpx_clients`` is keyed by the canonical attribute, so an
@@ -15,7 +15,7 @@ from osdu_python_client.services.registry import (
15
15
  ServiceSpec,
16
16
  load_authenticated_client,
17
17
  )
18
- from osdu_python_client.transport import RetryTransport
18
+ from osdu_python_client.transport import RetryTransport, warn_on_overwritten_headers
19
19
 
20
20
 
21
21
  class OsduClient:
@@ -96,7 +96,17 @@ class OsduClient:
96
96
  def with_headers(
97
97
  self, *, service: str | None = None, **headers: str
98
98
  ) -> Iterator["OsduClient"]:
99
- """Temporarily set extra headers on one or all built service clients."""
99
+ """Temporarily set extra headers on one or all built service clients.
100
+
101
+ The headers are written onto the service clients themselves, which
102
+ every caller of this ``OsduClient`` shares. Use it for process-wide
103
+ values (a CRS ``frame-of-reference``, a batch job's correlation id),
104
+ not per-request ones: in a web service, concurrent requests would see
105
+ each other's headers. Per-request partition goes in as
106
+ ``data_partition_id=`` on the operation; per-request auth goes through
107
+ a ``TokenProvider``.
108
+ """
109
+ warn_on_overwritten_headers(headers)
100
110
  if service is not None:
101
111
  getattr(self, service)
102
112
  # ``_httpx_clients`` is keyed by the canonical attribute, so an
@@ -46,4 +46,14 @@ class OsduConfig(BaseSettings):
46
46
  return spec.endpoint
47
47
 
48
48
  def url_for(self, service: str) -> str:
49
- return f"{self.server.rstrip('/')}{self.endpoint_for(service)}"
49
+ """Base URL for ``service``.
50
+
51
+ An ``endpoint_overrides`` value is normally a path appended to
52
+ ``server``. An absolute ``http(s)://`` value is used as-is instead, for
53
+ deployments where a service is not reachable under the shared server --
54
+ a sidecar, a port-forward, or a service on another host.
55
+ """
56
+ endpoint = self.endpoint_for(service)
57
+ if endpoint.startswith(("http://", "https://")):
58
+ return endpoint.rstrip("/")
59
+ return f"{self.server.rstrip('/')}{endpoint}"
@@ -1,3 +1,8 @@
1
+ import functools
2
+
3
+ import httpx
4
+
5
+
1
6
  class OsduError(Exception):
2
7
  pass
3
8
 
@@ -14,3 +19,26 @@ class OsduRetryExhausted(OsduError):
14
19
  def __init__(self, message: str, status_code: int | None = None) -> None:
15
20
  super().__init__(message)
16
21
  self.status_code = status_code
22
+
23
+
24
+ class OsduTransportError(OsduError):
25
+ """No HTTP response was received: connect failure, timeout, protocol error.
26
+
27
+ Raised by facade calls in place of the underlying ``httpx.TransportError``.
28
+ The concrete exception also subclasses the original httpx type (see
29
+ :func:`wrap_transport_error`), so ``except httpx.ConnectError`` written
30
+ before this existed still catches it, and ``except OsduError`` now catches
31
+ every facade failure.
32
+ """
33
+
34
+
35
+ @functools.cache
36
+ def _transport_error_type(cls: type[httpx.TransportError]) -> type[OsduTransportError]:
37
+ return type(f"Osdu{cls.__name__}", (OsduTransportError, cls), {})
38
+
39
+
40
+ def wrap_transport_error(err: httpx.TransportError) -> OsduTransportError:
41
+ request: httpx.Request | None = getattr(err, "_request", None)
42
+ target = f" calling {request.method} {request.url}" if request is not None else ""
43
+ wrapped_cls = _transport_error_type(type(err))
44
+ return wrapped_cls(f"{type(err).__name__}{target}: {err}", request=request) # type: ignore[call-arg]
@@ -6,7 +6,9 @@ import pkgutil
6
6
  from types import ModuleType
7
7
  from typing import Any
8
8
 
9
- from osdu_python_client.errors import OsduError
9
+ import httpx
10
+
11
+ from osdu_python_client.errors import OsduError, wrap_transport_error
10
12
 
11
13
 
12
14
  _ERROR_BODY_MAX = 1024
@@ -43,6 +45,9 @@ class Endpoint:
43
45
 
44
46
  ``.detailed(...)`` returns the full ``Response`` envelope for callers who
45
47
  need status code / headers / raw parsed error model.
48
+
49
+ Both raise ``OsduTransportError`` when no response arrives at all
50
+ (connect failure, timeout), so ``except OsduError`` covers every failure.
46
51
  """
47
52
 
48
53
  __slots__ = (
@@ -88,15 +93,27 @@ class Endpoint:
88
93
  def __call__(self, *args: Any, **kw: Any) -> Any:
89
94
  if self._is_async:
90
95
  return self._async_call(*args, **kw)
91
- return _unwrap(self._sync_detailed(*args, **self._bind(kw)))
96
+ return _unwrap(self._sync_send(*args, **kw))
92
97
 
93
98
  def detailed(self, *args: Any, **kw: Any) -> Any:
94
99
  if self._is_async:
95
- return self._async_detailed(*args, **self._bind(kw))
96
- return self._sync_detailed(*args, **self._bind(kw))
100
+ return self._async_send(*args, **kw)
101
+ return self._sync_send(*args, **kw)
102
+
103
+ def _sync_send(self, *args: Any, **kw: Any) -> Any:
104
+ try:
105
+ return self._sync_detailed(*args, **self._bind(kw))
106
+ except httpx.TransportError as err:
107
+ raise wrap_transport_error(err) from err
108
+
109
+ async def _async_send(self, *args: Any, **kw: Any) -> Any:
110
+ try:
111
+ return await self._async_detailed(*args, **self._bind(kw))
112
+ except httpx.TransportError as err:
113
+ raise wrap_transport_error(err) from err
97
114
 
98
115
  async def _async_call(self, *args: Any, **kw: Any) -> Any:
99
- return _unwrap(await self._async_detailed(*args, **self._bind(kw)))
116
+ return _unwrap(await self._async_send(*args, **kw))
100
117
 
101
118
 
102
119
  class ServiceFacade:
@@ -22,7 +22,9 @@ from __future__ import annotations
22
22
  from typing import Any
23
23
  from urllib.parse import quote
24
24
 
25
- from osdu_python_client.errors import OsduError
25
+ import httpx
26
+
27
+ from osdu_python_client.errors import OsduError, wrap_transport_error
26
28
  from osdu_python_client.services.facade import ServiceFacade
27
29
 
28
30
  PARQUET_MEDIA_TYPE = "application/x-parquet"
@@ -345,6 +347,18 @@ class WellboreDdmsFacade(ServiceFacade):
345
347
 
346
348
  # -- helpers -----------------------------------------------------------
347
349
 
350
+ def _request_sync(self, **kw: Any) -> httpx.Response:
351
+ try:
352
+ return self._client.get_httpx_client().request(**kw)
353
+ except httpx.TransportError as err:
354
+ raise wrap_transport_error(err) from err
355
+
356
+ async def _request_async(self, **kw: Any) -> httpx.Response:
357
+ try:
358
+ return await self._client.get_async_httpx_client().request(**kw)
359
+ except httpx.TransportError as err:
360
+ raise wrap_transport_error(err) from err
361
+
348
362
  def _headers(self, accept_or_content: str, *, write: bool, dp: str | None) -> dict[str, str]:
349
363
  key = "Content-Type" if write else "Accept"
350
364
  headers = {key: accept_or_content}
@@ -382,7 +396,7 @@ class WellboreDdmsFacade(ServiceFacade):
382
396
  as_dataframe, data_partition_id,
383
397
  ) -> Any:
384
398
  url = _data_url(entity, record_id, version)
385
- response = self._client.get_httpx_client().request(
399
+ response = self._request_sync(
386
400
  method="get",
387
401
  url=url,
388
402
  params=_read_params(offset, limit, curves, filter_),
@@ -394,7 +408,7 @@ class WellboreDdmsFacade(ServiceFacade):
394
408
  def _write_bulk_parquet_sync(self, record_id, data, entity, data_partition_id) -> Any:
395
409
  url = _data_url(entity, record_id, None)
396
410
  body = self._encode_table(data)
397
- response = self._client.get_httpx_client().request(
411
+ response = self._request_sync(
398
412
  method="post",
399
413
  url=url,
400
414
  content=body,
@@ -407,7 +421,7 @@ class WellboreDdmsFacade(ServiceFacade):
407
421
  self, record_id, entity, mode, from_version, time_to_live, meta, dp,
408
422
  ) -> str:
409
423
  url = _sessions_url(entity, record_id)
410
- response = self._client.get_httpx_client().request(
424
+ response = self._request_sync(
411
425
  method="post",
412
426
  url=url,
413
427
  json=_create_session_body(mode, from_version, time_to_live, meta),
@@ -419,7 +433,7 @@ class WellboreDdmsFacade(ServiceFacade):
419
433
  def _write_chunk_parquet_sync(self, record_id, session_id, data, entity, dp) -> Any:
420
434
  url = _session_data_url(entity, record_id, session_id)
421
435
  body = self._encode_table(data)
422
- response = self._client.get_httpx_client().request(
436
+ response = self._request_sync(
423
437
  method="post",
424
438
  url=url,
425
439
  content=body,
@@ -430,7 +444,7 @@ class WellboreDdmsFacade(ServiceFacade):
430
444
 
431
445
  def _patch_session_sync(self, record_id, session_id, entity, state, dp) -> Any:
432
446
  url = _session_url(entity, record_id, session_id)
433
- response = self._client.get_httpx_client().request(
447
+ response = self._request_sync(
434
448
  method="patch",
435
449
  url=url,
436
450
  json={"state": state},
@@ -464,7 +478,7 @@ class WellboreDdmsFacade(ServiceFacade):
464
478
  as_dataframe, data_partition_id,
465
479
  ) -> Any:
466
480
  url = _data_url(entity, record_id, version)
467
- response = await self._client.get_async_httpx_client().request(
481
+ response = await self._request_async(
468
482
  method="get",
469
483
  url=url,
470
484
  params=_read_params(offset, limit, curves, filter_),
@@ -476,7 +490,7 @@ class WellboreDdmsFacade(ServiceFacade):
476
490
  async def _write_bulk_parquet_async(self, record_id, data, entity, data_partition_id) -> Any:
477
491
  url = _data_url(entity, record_id, None)
478
492
  body = self._encode_table(data)
479
- response = await self._client.get_async_httpx_client().request(
493
+ response = await self._request_async(
480
494
  method="post",
481
495
  url=url,
482
496
  content=body,
@@ -489,7 +503,7 @@ class WellboreDdmsFacade(ServiceFacade):
489
503
  self, record_id, entity, mode, from_version, time_to_live, meta, dp,
490
504
  ) -> str:
491
505
  url = _sessions_url(entity, record_id)
492
- response = await self._client.get_async_httpx_client().request(
506
+ response = await self._request_async(
493
507
  method="post",
494
508
  url=url,
495
509
  json=_create_session_body(mode, from_version, time_to_live, meta),
@@ -501,7 +515,7 @@ class WellboreDdmsFacade(ServiceFacade):
501
515
  async def _write_chunk_parquet_async(self, record_id, session_id, data, entity, dp) -> Any:
502
516
  url = _session_data_url(entity, record_id, session_id)
503
517
  body = self._encode_table(data)
504
- response = await self._client.get_async_httpx_client().request(
518
+ response = await self._request_async(
505
519
  method="post",
506
520
  url=url,
507
521
  content=body,
@@ -512,7 +526,7 @@ class WellboreDdmsFacade(ServiceFacade):
512
526
 
513
527
  async def _patch_session_async(self, record_id, session_id, entity, state, dp) -> Any:
514
528
  url = _session_url(entity, record_id, session_id)
515
- response = await self._client.get_async_httpx_client().request(
529
+ response = await self._request_async(
516
530
  method="patch",
517
531
  url=url,
518
532
  json={"state": state},
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import logging
4
4
  import random
5
5
  import time
6
+ import warnings
6
7
  from typing import Iterable, Mapping
7
8
 
8
9
  import httpx
@@ -66,6 +67,25 @@ def _log_response_body(response: httpx.Response) -> None:
66
67
  body_log.debug("← body=%s", _truncate_body(response.content))
67
68
 
68
69
 
70
+ def warn_on_overwritten_headers(headers: Mapping[str, str]) -> None:
71
+ """Warn when ``with_headers`` is given a header the transport replaces.
72
+
73
+ The transport sets ``Authorization`` from the token provider on every
74
+ request, so a value passed through ``with_headers`` never reaches the
75
+ server. Forwarding a caller's token takes a ``TokenProvider`` instead
76
+ (see docs/usage.md, "Using the client in a web service").
77
+ """
78
+ if any(name.lower() == "authorization" for name in headers):
79
+ warnings.warn(
80
+ "with_headers(Authorization=...) has no effect: the transport sets "
81
+ "Authorization from the token provider on every request. Pass a "
82
+ "TokenProvider that returns the token you want sent instead.",
83
+ UserWarning,
84
+ # user code -> contextmanager __enter__ -> with_headers -> here
85
+ stacklevel=4,
86
+ )
87
+
88
+
69
89
  class RetryTransport(httpx.BaseTransport):
70
90
  def __init__(
71
91
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: osdu-python-client
3
- Version: 0.9.0
3
+ Version: 0.11.0
4
4
  Summary: Python client for OSDU services
5
5
  Author: OSDU team
6
6
  License-Expression: Apache-2.0
@@ -20,11 +20,8 @@ Description-Content-Type: text/markdown
20
20
  License-File: LICENSE
21
21
  Requires-Dist: attrs>=26.1.0
22
22
  Requires-Dist: httpx>=0.28.1
23
- Requires-Dist: openapi-python-client>=0.28.3
24
23
  Requires-Dist: pydantic-settings>=2.13.1
25
24
  Requires-Dist: python-dateutil>=2.9.0.post0
26
- Requires-Dist: pyyaml>=6.0.3
27
- Requires-Dist: ruff>=0.15.12
28
25
  Provides-Extra: azure
29
26
  Requires-Dist: msal>=1.35.1; extra == "azure"
30
27
  Requires-Dist: azure-identity>=1.14.0; extra == "azure"
@@ -45,6 +42,9 @@ Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
45
42
  Requires-Dist: pytest-mock==3.15.1; extra == "dev"
46
43
  Requires-Dist: pydantic-settings==2.14.0; extra == "dev"
47
44
  Requires-Dist: msal==1.36.0; extra == "dev"
45
+ Requires-Dist: openapi-python-client<0.29,>=0.28.3; extra == "dev"
46
+ Requires-Dist: pyyaml>=6.0.3; extra == "dev"
47
+ Requires-Dist: ruff>=0.15.12; extra == "dev"
48
48
  Dynamic: license-file
49
49
 
50
50
  # OSDU Python Client
@@ -90,7 +90,7 @@ with OsduClient() as osdu: # config loaded from .env
90
90
  print(record.additional_properties)
91
91
  ```
92
92
 
93
- Each `osdu.<service>.<operation>(...)` call auto-binds the underlying client and `data-partition-id`, returns the parsed response model on 2xx, and raises `OsduError` on non-2xx. Call `.detailed(...)` on any operation to get the full `Response` envelope (status code, headers, parsed error model).
93
+ Each `osdu.<service>.<operation>(...)` call auto-binds the underlying client and `data-partition-id`, returns the parsed response model on 2xx, and raises `OsduError` on non-2xx — or `OsduTransportError`, also an `OsduError`, when no response arrives at all. Call `.detailed(...)` on any operation to get the full `Response` envelope (status code, headers, parsed error model).
94
94
 
95
95
  ## Typed data models (optional companion)
96
96
 
@@ -241,6 +241,8 @@ with osdu.with_headers(**{"x-correlation-id": correlation_id}):
241
241
 
242
242
  The async client exposes the same helper as `async with osdu.with_headers(...)`.
243
243
 
244
+ `with_headers` changes service clients that every caller of `osdu` shares, so it is for process-wide values. In a web service, pass per-request values per call instead — see [Using the client in a web service](https://community.opengroup.org/osdu/platform/system/sdks/osdu-python-client/-/blob/main/docs/usage.md#using-the-client-in-a-web-service).
245
+
244
246
  ## Wellbore DDMS: Parquet bulk data
245
247
 
246
248
  Well-log bulk data is served as Parquet by Wellbore DDMS (the `/data` endpoints
@@ -1,14 +1,14 @@
1
- osdu_python_client/__init__.py,sha256=IfMMte_prpYP-4VZa-gmeYoQGgArDGuoFxYW9RKZol8,796
2
- osdu_python_client/async_client.py,sha256=4O07GD4mEFIblPK5FAkzw9yJoOtlsjoPPeq71TEjBIk,4995
1
+ osdu_python_client/__init__.py,sha256=lH0Q4LFBhfdGQIVxALAOc4IaUO-Ly0CJp3dMb2aw8w0,846
2
+ osdu_python_client/async_client.py,sha256=XocPs2KyoS7c5azGmQvIiGqGMmqSmgPeNR7uuN3-y1Y,5642
3
3
  osdu_python_client/async_client.pyi,sha256=6OqjLvRoN_MrxZiJ9T2aQKHpjyIApkyGpd4D9PlBx-U,2937
4
- osdu_python_client/client.py,sha256=-cnJK4Z5qqV50KoHT5frZnUvvxWsKEsWgjYl6XlkLfY,4926
4
+ osdu_python_client/client.py,sha256=iZbYwiiJYw8T7Xgp6ACZK0-yx6aVgmPqKkx_ahgHaSc,5487
5
5
  osdu_python_client/client.pyi,sha256=mqlvJgG9A9Qse2-IkO6Yutt3vX4SEhu08cA0irV1XbQ,2685
6
- osdu_python_client/config.py,sha256=bVEYrYGmD5yDfIT-uzvLb4UKR4x5qC67Cn6jYRhps6E,1666
7
- osdu_python_client/errors.py,sha256=4Je5XW67es-uYUXs7mjBQxxYcw-VPAHOUgqRjxcsQdc,315
6
+ osdu_python_client/config.py,sha256=wo1Cfs5CNwSFsVwUxwuM8JFQT5zPd1HWJRuBleguwsw,2137
7
+ osdu_python_client/errors.py,sha256=kISd0BDTu4r1o1OBnCGmkGGZ1V00kiZUvYCMm8-2xpY,1374
8
8
  osdu_python_client/hooks.py,sha256=_VxWw1iEoEELfgJysar0jwDcdJesTF5WCrRhgY3j3cw,561
9
9
  osdu_python_client/logging_setup.py,sha256=lIOhkzg9BOsET2TbaNNdNJPT0C1-6-4ae5c7UOEX3hs,1286
10
10
  osdu_python_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- osdu_python_client/transport.py,sha256=SrFmuozAUgUtMUDCn-7g_gMFee2vcQU-Hq6ZPaetLac,9613
11
+ osdu_python_client/transport.py,sha256=eKQQUduqNACGmdHW8mpgobFYe9OwLm_zwBmSo64XSiA,10528
12
12
  osdu_python_client/auth/__init__.py,sha256=yUfClGNUgwP1RP7e_EJnTNslcJDY4NNTJmfoD0Rr_s4,2478
13
13
  osdu_python_client/auth/_base.py,sha256=GMvdYiXcbRPglG5zGZbLnGfCdNUh2C7-RwVHwFB2QRM,1967
14
14
  osdu_python_client/auth/aws.py,sha256=wT9w7YkQsa91l9qcYoYpCqRiNVZ7eegmQJKEvLdcYYI,3423
@@ -945,11 +945,11 @@ osdu_python_client/generated/workflow/models/workflow_run_response.py,sha256=ORX
945
945
  osdu_python_client/generated/workflow/models/workflow_run_response_status.py,sha256=cjW9NPUtP1s31DBOObnTH3feY3_kLxu7YXHV5uKNmcE,274
946
946
  osdu_python_client/generated/workflow/models/workflow_run_status.py,sha256=cvpitWQX_eoirngqDCfFYHDtN3RAPoWU_9Zk6IPXT-U,268
947
947
  osdu_python_client/services/__init__.py,sha256=_ZJwu7SBUPudjm_Kotd5vjSo_nQKlUh4SaSxBAYqiuM,112
948
- osdu_python_client/services/facade.py,sha256=YJqRc5gex8dU1bax2k_mk_OiPo7dcTfVaxnd69dMto4,5947
948
+ osdu_python_client/services/facade.py,sha256=pXDYYfVcn3Ls1L1It6tVOv-gZgD8uKg8K9JmEn_kbDc,6544
949
949
  osdu_python_client/services/registry.py,sha256=T8norw7WM3RucbktjENpA00CTnRu3chn9KzAAfddviA,5459
950
- osdu_python_client/services/wellbore_ddms.py,sha256=xyVCTe2O5UkhReMVmDuT5XmhzHWJTLx7fdSNK8ckFYA,20468
951
- osdu_python_client-0.9.0.dist-info/licenses/LICENSE,sha256=mbmpdYp4WedCVTL1gTJqhNVnR6h012ht-b3eG9xYM3A,11418
952
- osdu_python_client-0.9.0.dist-info/METADATA,sha256=ZrTdw2nfEAymupN9rPmzM5RY59QFVygOKVWf1afIdVc,14663
953
- osdu_python_client-0.9.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
954
- osdu_python_client-0.9.0.dist-info/top_level.txt,sha256=1WpzwJhlCok7kjThasFuwXMOk7lfy_GaubVUv0217Ck,19
955
- osdu_python_client-0.9.0.dist-info/RECORD,,
950
+ osdu_python_client/services/wellbore_ddms.py,sha256=3DunJJcjcn0DRx_ONgm14xl3ikrz6AoQs_JYKwFsEwY,20756
951
+ osdu_python_client-0.11.0.dist-info/licenses/LICENSE,sha256=mbmpdYp4WedCVTL1gTJqhNVnR6h012ht-b3eG9xYM3A,11418
952
+ osdu_python_client-0.11.0.dist-info/METADATA,sha256=xI9IuLqtMz20Rrm6kqBxFwvvy645D_DThUj7KaHpMpA,15152
953
+ osdu_python_client-0.11.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
954
+ osdu_python_client-0.11.0.dist-info/top_level.txt,sha256=1WpzwJhlCok7kjThasFuwXMOk7lfy_GaubVUv0217Ck,19
955
+ osdu_python_client-0.11.0.dist-info/RECORD,,