UncountablePythonSDK 0.0.49__py3-none-any.whl → 0.0.51__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 UncountablePythonSDK might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: UncountablePythonSDK
3
- Version: 0.0.49
3
+ Version: 0.0.51
4
4
  Summary: Uncountable SDK
5
5
  Project-URL: Homepage, https://github.com/uncountableinc/uncountable-python-sdk
6
6
  Project-URL: Repository, https://github.com/uncountableinc/uncountable-python-sdk.git
@@ -27,6 +27,10 @@ Requires-Dist: PyYAML ==6.*
27
27
  Requires-Dist: google-api-python-client ==2.*
28
28
  Requires-Dist: tqdm ==4.*
29
29
  Requires-Dist: pysftp ==0.*
30
+ Requires-Dist: opentelemetry-api ==1.*
31
+ Requires-Dist: opentelemetry-exporter-otlp-proto-common ==1.*
32
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http ==1.*
33
+ Requires-Dist: opentelemetry-sdk ==1.*
30
34
  Requires-Dist: paramiko ==3.*
31
35
  Requires-Dist: boto3 ==1.*
32
36
  Provides-Extra: test
@@ -3,7 +3,7 @@ docs/conf.py,sha256=YF5J-9g_Wg8wXmyHsGaE8xYlDEzqocNl3UWUmP0CwBg,1702
3
3
  docs/index.md,sha256=eEdirX_Ds6ICTRtIS5iT4irCquHcQyKN7E4M5QP9T8A,257
4
4
  docs/justfile,sha256=cvNcpb-ByPOF2aCrFlg3DDZBoYMx5W8xGdr13m9HcnI,215
5
5
  docs/quickstart.md,sha256=3GuJ0MB1O5kjlsrgAmdSkDq0rYqATrYy-tzEHDy8H-c,422
6
- docs/requirements.txt,sha256=Y6wdRPGnrd-HMShTgy9Fgydz868Bnyl007P9HCUKXhw,139
6
+ docs/requirements.txt,sha256=YDDAaHfuLxkdLhrjEUJeHDE-NSmD5chTgVTIO7BEeto,139
7
7
  docs/static/logo_blue.png,sha256=SyYpMTVhhBbhF5Wl8lWaVwz-_p1MIR6dW6bVhufQRME,46708
8
8
  docs/static/favicons/android-chrome-192x192.png,sha256=XoF-AhD55JlSBDGsEPJKfT_VeXT-awhwKyZnxLhrwvk,1369
9
9
  docs/static/favicons/android-chrome-512x512.png,sha256=1S4xwY9YtJQ5ifFsZ-DOzssoyBYs0t9uwdOUmYx0Xso,3888
@@ -74,26 +74,28 @@ uncountable/__init__.py,sha256=8l8XWNCKsu7TG94c-xa2KHpDegvxDC2FyQISdWC763Y,89
74
74
  uncountable/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
75
  uncountable/core/__init__.py,sha256=RFv0kO6rKFf1PtBPu83hCGmxqkJamRtsgQ9_-ztw7tA,341
76
76
  uncountable/core/async_batch.py,sha256=Gur0VOS0AH2ugwvk65hwoX-iqwQAAyJaejY_LyAZZPo,1210
77
- uncountable/core/client.py,sha256=2QRLy0GcSamQDqUABQq8R7D_wGofYewSoTQiZzOyZXk,10168
77
+ uncountable/core/client.py,sha256=C0hJ0_SGL5WEhPuAWDSj4ShjjIiQasxpfpnisTi-Uag,10554
78
78
  uncountable/core/file_upload.py,sha256=TkQ0fKbbYrPgns1Jh51JU35DUqZHB3ljOaVgjSlBx9Y,3149
79
79
  uncountable/core/types.py,sha256=s2CjqYJpsmbC7xMwxxT7kJ_V9bwokrjjWVVjpMcQpKI,333
80
+ uncountable/core/version.py,sha256=SqQIHLhiVZXQBeOwygS2FRZ4WEO27JmWhse0lKm7fgU,274
80
81
  uncountable/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
81
- uncountable/integration/construct_client.py,sha256=9ZsfkA0oZIE04mPnwGlt6EcrlnpaUPA93K100FS1F7A,1546
82
- uncountable/integration/cron.py,sha256=n4HjW-KNruYQ_09iwNBd9i4-Juy_9ddzbf71_wnydHA,1674
82
+ uncountable/integration/construct_client.py,sha256=e1uAMVp4FTbValCJ3gfaZFuObKxHbTXq496-T-KMOG4,1899
83
+ uncountable/integration/cron.py,sha256=e5456IYJF2ipiSsd1R2T334lfe7mtp-gwP7JpS645L0,1858
83
84
  uncountable/integration/entrypoint.py,sha256=9rk06gBTsCqytIs8Shsnlf6ir_4Uq5d5rfP1veiSLzc,1437
84
- uncountable/integration/job.py,sha256=5HJcjtwj_pGFYAZ6SK7xa9RqDXCoBb5rAb_OHdTR61Q,1463
85
- uncountable/integration/server.py,sha256=85yzQrrpp8OEG37CTN9wtgB9UrzWnDLuDkY0nWcp--k,3548
85
+ uncountable/integration/job.py,sha256=UTzcMes2KrBBRLOM3u94imMKLLnv50glqOkNf8-JOZw,1022
86
+ uncountable/integration/server.py,sha256=JlnbidtiSLCEod0DzLKIzTCxP1qgaHBIBolUvnihJn8,3980
87
+ uncountable/integration/telemetry.py,sha256=H5XQnRTszDL6Nl_oQYuWRr_oNYMocDaCCpKlwll-qZI,5127
86
88
  uncountable/integration/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
87
89
  uncountable/integration/db/connect.py,sha256=YtQHJ1DBGPhxKFRCfiXqohOYUceKSxMVOJ88aPI48Ug,181
88
90
  uncountable/integration/executors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
91
  uncountable/integration/executors/executors.py,sha256=v5ClGVUlvrZcMdmGQa8Ll668G_HGTnKpGOnTM7UMZCQ,956
90
- uncountable/integration/executors/generic_upload_executor.py,sha256=HR5bZE1DMVLRB-lwmsfZnoLEsaL66aHZM0xCEO_D3-4,10238
92
+ uncountable/integration/executors/generic_upload_executor.py,sha256=wafNY_gpbUiQhvkFPDw-GGiJLmDVtTRgH_5jwMLy2Z4,10283
91
93
  uncountable/integration/executors/script_executor.py,sha256=OmSBOtU48G3mqza9c2lCm84pGGyaDk-ZBJCx3RsdJXc,846
92
94
  uncountable/integration/secret_retrieval/__init__.py,sha256=3QXVj35w8rRMxVvmmsViFYDi3lcb3g70incfalOEm6o,87
93
- uncountable/integration/secret_retrieval/retrieve_secret.py,sha256=M0qXVJpD8hMYIFypHFeyh598sqmIDX8ZOyXK23CluF0,1323
95
+ uncountable/integration/secret_retrieval/retrieve_secret.py,sha256=iDqLbYKKLv8Wl5hNSKl7b4hKJnDshFe6tgdAJmQgQyA,2205
94
96
  uncountable/types/__init__.py,sha256=95iOd3WXWoI_4a461IS2ieWRic3zRyNaCYzfTpX764o,8162
95
97
  uncountable/types/async_batch.py,sha256=ihCv5XWSTTPmuO-GMPn1EACGI2CBUIJTATZ3aPgsNBA,523
96
- uncountable/types/async_batch_processor.py,sha256=VO0P-oSLAvzBK_Rk-KPS74AhSyrkj5ILZrAu-gs7Lpc,8564
98
+ uncountable/types/async_batch_processor.py,sha256=R--exgi4Gw0HWCnh8M-3_2PqG2ByTBtdyuSQ2eYtYn8,8671
97
99
  uncountable/types/async_batch_t.py,sha256=9jp9rOyetRdD5aQVyijzQggTyYU4021PBVGXk0ooBCQ,1911
98
100
  uncountable/types/base.py,sha256=xVSjWvA_fUUnkCg83EjoYEFvAfmskinKFMeYFOxNc9E,359
99
101
  uncountable/types/base_t.py,sha256=XXjZXexx0xWFUxMMhW8i9nIL6n8dsZVsHwdgnhZ0zJ4,2714
@@ -101,7 +103,7 @@ uncountable/types/calculations.py,sha256=FFO_D3BbKoGDZnqWvTKpW4KF359i2vrKjpdFCLY
101
103
  uncountable/types/calculations_t.py,sha256=7GTSi2L8NYjzjUJJx3cmtVkK9uD-uhfYvIFK-ffQj-8,556
102
104
  uncountable/types/chemical_structure.py,sha256=E-LnikTFDoVQ1b2zKaVUIO_PAKm-7aZZYJi8I8SDSic,302
103
105
  uncountable/types/chemical_structure_t.py,sha256=aFsTkkbzy6Gvyde3qrrEYD95gcYhxkgKMiDRaRE0o-Y,760
104
- uncountable/types/client_base.py,sha256=3CKr_v3bXez7ZjUg41q09pju5tgYGMsbgJAoYMffh-c,65240
106
+ uncountable/types/client_base.py,sha256=db6V-Jea8iWT4kz2hXc5c_doLJKLZTd5T-F4ehobWMs,65431
105
107
  uncountable/types/client_config.py,sha256=4h5Liko9uKCo9_0gdbPhoK6Jr2Kv7tioLiQ8iKeq-_4,301
106
108
  uncountable/types/client_config_t.py,sha256=_HdS37gMSTIiD4qLnW9dIgt8_Rt5A6xhwMGGga7vnLg,625
107
109
  uncountable/types/curves.py,sha256=W6uMpG5SyW1MS82szNpxkFEn1MnxNpBFyFbQb2Ysfng,366
@@ -124,8 +126,8 @@ uncountable/types/input_attributes.py,sha256=IrIKQnHqHdS1Utdfzr9GnOe17a8riaqYcO1
124
126
  uncountable/types/input_attributes_t.py,sha256=wE1ekiQfb72Z9VpF5SHipKJkgaJFUHJrNkkJdmuhF9w,820
125
127
  uncountable/types/inputs.py,sha256=6RIEFfCxLqpeHEGOpu63O4i8zPogjGeB7wiV_rPBw_g,404
126
128
  uncountable/types/inputs_t.py,sha256=RW7gF9zTOwByu-nMTMVuBabLOuWKx4O1nvfgvx_R55o,1611
127
- uncountable/types/job_definition.py,sha256=sCQqtyHI3hksc5pVpk5tqbG55F91ST4FoDwr2TmTOuQ,1787
128
- uncountable/types/job_definition_t.py,sha256=DpqTYDocJ6n6kVq5PBFhFb5Dlb2vpHdp9IZfFb1Qcpo,6590
129
+ uncountable/types/job_definition.py,sha256=Cgwop6iNcWEsEpfjMl1EtkRES_3-SVzpMe-w73V4G_U,1862
130
+ uncountable/types/job_definition_t.py,sha256=pL-0YVyR0oz6-5wWXJsX1UyYo4R_FPwhOYFj9iMKgvo,7170
129
131
  uncountable/types/outputs.py,sha256=sUZx_X-TKCZtLm1YCEH8OISX9DdPlv9ZuUfM3-askCc,281
130
132
  uncountable/types/outputs_t.py,sha256=2aORUOr0ls1ZYo-ddkWax3D1ZndmQsWtHfJxpYozlhg,656
131
133
  uncountable/types/permissions.py,sha256=1mRnSsmRgjuLgp6pylTwwACD_YRIcmlqxHkufwZtMns,297
@@ -167,8 +169,8 @@ uncountable/types/api/batch/execute_batch_load_async.py,sha256=3ptjtokj7eZ_A7OUX
167
169
  uncountable/types/api/chemical/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
168
170
  uncountable/types/api/chemical/convert_chemical_formats.py,sha256=-FKBOcg1jteFu920NM-0lBk90pfucpcg2WAsaddfDc8,1323
169
171
  uncountable/types/api/entity/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
170
- uncountable/types/api/entity/create_entities.py,sha256=wYxj0Dsi6nRy94qzTUkZYWHkFFpTC8Gt2sHOuwCosVs,1738
171
- uncountable/types/api/entity/create_entity.py,sha256=pqN0VNcWlaHFxOtuPUjE2DsONVDYCkq-gUEAV3R0Ds0,1908
172
+ uncountable/types/api/entity/create_entities.py,sha256=qvra6BustbxWmt7_c83mCMkfxaBJ4B8xayJUvk6Ab9E,1780
173
+ uncountable/types/api/entity/create_entity.py,sha256=vfbJiHOMEEzJFZNNiHbIBqisytYdKa7rTOraWdZrXpw,1950
172
174
  uncountable/types/api/entity/get_entities_data.py,sha256=_dyDJ8Aukeijj2U3ZEQYNECoI3TC4phn8RhgJQP1e1s,1162
173
175
  uncountable/types/api/entity/list_entities.py,sha256=93J8jbHOdBL7Ee2_z_M57JpIVsmI2RddDMpprTakYks,1717
174
176
  uncountable/types/api/entity/lock_entity.py,sha256=twQ-f61AKS_NrKP-TzqHkMmzTJGMc4yxZFl-2JctOg4,1004
@@ -237,8 +239,8 @@ uncountable/types/api/recipes/unlock_recipes.py,sha256=AvzQeZCLs9i7CuhMs3Xltdi4n
237
239
  uncountable/types/api/triggers/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
238
240
  uncountable/types/api/triggers/run_trigger.py,sha256=_Rpha9nxXI3Xr17CrGDtofg4HZ81x2lt0rMZ6As0qfE,893
239
241
  uncountable/types/api/uploader/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
240
- uncountable/types/api/uploader/invoke_uploader.py,sha256=w7NVSpTpKxsF2TgLzVrji1Ql5Z8QWTz6d5OvXpRtyDo,992
241
- UncountablePythonSDK-0.0.49.dist-info/METADATA,sha256=YzHISP7OjFmFRJWuVPWl6jgdlvsE6W7glCma0mhU7sc,1734
242
- UncountablePythonSDK-0.0.49.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
243
- UncountablePythonSDK-0.0.49.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
244
- UncountablePythonSDK-0.0.49.dist-info/RECORD,,
242
+ uncountable/types/api/uploader/invoke_uploader.py,sha256=4zOcB_38uT73Jm3-XqkG40fBM1R5vpvPpGAg-U4lzxY,1059
243
+ UncountablePythonSDK-0.0.51.dist-info/METADATA,sha256=AzvBprRKL--Fkabx8qrLx9P07XsCyTu1BqpTGl9AMFg,1934
244
+ UncountablePythonSDK-0.0.51.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
245
+ UncountablePythonSDK-0.0.51.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
246
+ UncountablePythonSDK-0.0.51.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (72.2.0)
2
+ Generator: setuptools (73.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
docs/requirements.txt CHANGED
@@ -2,6 +2,6 @@ furo==2024.7.18
2
2
  myst-parser==3.0.1
3
3
  sphinx-autoapi==3.2.0
4
4
  sphinx-copybutton==0.5.2
5
- Sphinx==7.4.4
5
+ Sphinx==8.0.0
6
6
  sphinx_design==0.6.0
7
7
  sphinx-favicon==1.0.1
@@ -4,16 +4,18 @@ import typing
4
4
  from dataclasses import dataclass
5
5
  from datetime import datetime, timedelta
6
6
  from enum import StrEnum
7
- from importlib.metadata import PackageNotFoundError, version
8
7
  from urllib.parse import urljoin
9
8
  from uuid import uuid4
10
9
 
11
10
  import requests
11
+ from opentelemetry.sdk.resources import Attributes
12
12
  from requests.exceptions import JSONDecodeError
13
13
 
14
14
  from pkgs.argument_parser import CachedParser
15
15
  from pkgs.serialization_util import serialize_for_api
16
16
  from pkgs.serialization_util.serialization_helpers import JsonValue
17
+ from uncountable.core.version import get_version
18
+ from uncountable.integration.telemetry import JobLogger
17
19
  from uncountable.types.client_base import APIRequest, ClientMethods
18
20
  from uncountable.types.client_config import ClientConfigOptions
19
21
 
@@ -25,12 +27,6 @@ UNC_REQUEST_ID_HEADER = "X-UNC-REQUEST-ID"
25
27
  UNC_SDK_VERSION_HEADER = "X-UNC-SDK-VERSION"
26
28
 
27
29
 
28
- try:
29
- __version__ = version("UncountablePythonSDK")
30
- except PackageNotFoundError:
31
- __version__ = "unknown"
32
-
33
-
34
30
  class EndpointMethod(StrEnum):
35
31
  POST = "POST"
36
32
  GET = "GET"
@@ -61,6 +57,7 @@ HTTPRequest = HTTPPostRequest | HTTPGetRequest
61
57
  @dataclass(kw_only=True)
62
58
  class ClientConfig(ClientConfigOptions):
63
59
  transform_request: typing.Callable[[requests.Request], requests.Request] | None = None
60
+ job_logger: typing.Optional[JobLogger] = None
64
61
 
65
62
 
66
63
  OAUTH_REFRESH_WINDOW_SECONDS = 60 * 5
@@ -204,7 +201,15 @@ class Client(ClientMethods):
204
201
  case _:
205
202
  typing.assert_never(http_request)
206
203
  request.headers = http_request.headers
207
- response = self._send_request(request)
204
+ if self._cfg.job_logger is not None:
205
+ attributes: Attributes = {
206
+ "method": http_request.method,
207
+ "endpoint": api_request.endpoint,
208
+ }
209
+ with self._cfg.job_logger.push_scope("api_call", attributes=attributes):
210
+ response = self._send_request(request)
211
+ else:
212
+ response = self._send_request(request)
208
213
  response_data = self._get_response_json(response, request_id=request_id)
209
214
  cached_parser = self._get_cached_parser(return_type)
210
215
  try:
@@ -260,7 +265,7 @@ class Client(ClientMethods):
260
265
  ) -> HTTPRequest:
261
266
  headers = self._build_auth_headers()
262
267
  headers[UNC_REQUEST_ID_HEADER] = request_id
263
- headers[UNC_SDK_VERSION_HEADER] = __version__
268
+ headers[UNC_SDK_VERSION_HEADER] = get_version()
264
269
  method = api_request.method.lower()
265
270
  data = {"data": json.dumps(serialize_for_api(api_request.args))}
266
271
  match method:
@@ -0,0 +1,11 @@
1
+ import functools
2
+ from importlib.metadata import PackageNotFoundError, version
3
+
4
+
5
+ @functools.cache
6
+ def get_version() -> str:
7
+ try:
8
+ version_str = version("UncountablePythonSDK")
9
+ except PackageNotFoundError:
10
+ version_str = "unknown"
11
+ return version_str
@@ -1,47 +1,51 @@
1
- import os
2
-
3
1
  from uncountable.core import AuthDetailsApiKey, Client
4
2
  from uncountable.core.client import ClientConfig
5
- from uncountable.core.types import AuthDetailsAll
3
+ from uncountable.core.types import AuthDetailsAll, AuthDetailsOAuth
4
+ from uncountable.integration.secret_retrieval.retrieve_secret import retrieve_secret
5
+ from uncountable.integration.telemetry import JobLogger
6
6
  from uncountable.types.job_definition_t import (
7
- AuthRetrievalEnv,
7
+ AuthRetrievalBasic,
8
+ AuthRetrievalOAuth,
8
9
  ProfileMetadata,
9
10
  )
10
11
 
11
12
 
12
- def _get_env_var(name: str) -> str:
13
- value = os.getenv(name)
14
- if value is None:
15
- raise Exception(f"environment variable {name} is missing")
16
- return value
17
-
18
-
19
13
  def _construct_auth_details(profile_meta: ProfileMetadata) -> AuthDetailsAll:
20
14
  match profile_meta.auth_retrieval:
21
- case AuthRetrievalEnv():
22
- api_id = _get_env_var(f"UNC_PROFILE_{profile_meta.name.upper()}_API_ID")
23
- api_secret_key = _get_env_var(
24
- f"UNC_PROFILE_{profile_meta.name.upper()}_API_SECRET_KEY"
15
+ case AuthRetrievalOAuth():
16
+ refresh_token = retrieve_secret(
17
+ profile_meta.auth_retrieval.refresh_token_secret,
18
+ profile_metadata=profile_meta,
19
+ )
20
+ return AuthDetailsOAuth(refresh_token=refresh_token)
21
+ case AuthRetrievalBasic():
22
+ api_id = retrieve_secret(
23
+ profile_meta.auth_retrieval.api_id_secret, profile_metadata=profile_meta
24
+ )
25
+ api_key = retrieve_secret(
26
+ profile_meta.auth_retrieval.api_key_secret, profile_metadata=profile_meta
25
27
  )
26
28
 
27
- assert api_id is not None
28
- assert api_secret_key is not None
29
-
30
- return AuthDetailsApiKey(api_id=api_id, api_secret_key=api_secret_key)
29
+ return AuthDetailsApiKey(api_id=api_id, api_secret_key=api_key)
31
30
 
32
31
 
33
- def _construct_client_config(profile_meta: ProfileMetadata) -> ClientConfig | None:
32
+ def _construct_client_config(
33
+ profile_meta: ProfileMetadata, job_logger: JobLogger
34
+ ) -> ClientConfig | None:
34
35
  if profile_meta.client_options is None:
35
36
  return None
36
37
  return ClientConfig(
37
38
  allow_insecure_tls=profile_meta.client_options.allow_insecure_tls,
38
39
  extra_headers=profile_meta.client_options.extra_headers,
40
+ job_logger=job_logger,
39
41
  )
40
42
 
41
43
 
42
- def construct_uncountable_client(profile_meta: ProfileMetadata) -> Client:
44
+ def construct_uncountable_client(
45
+ profile_meta: ProfileMetadata, job_logger: JobLogger
46
+ ) -> Client:
43
47
  return Client(
44
48
  base_url=profile_meta.base_url,
45
49
  auth_details=_construct_auth_details(profile_meta),
46
- config=_construct_client_config(profile_meta),
50
+ config=_construct_client_config(profile_meta, job_logger),
47
51
  )
@@ -4,7 +4,8 @@ from pkgs.argument_parser import CachedParser
4
4
  from uncountable.core.async_batch import AsyncBatchProcessor
5
5
  from uncountable.integration.construct_client import construct_uncountable_client
6
6
  from uncountable.integration.executors.executors import resolve_executor
7
- from uncountable.integration.job import CronJobArguments, JobLogger
7
+ from uncountable.integration.job import CronJobArguments
8
+ from uncountable.integration.telemetry import JobLogger
8
9
  from uncountable.types.job_definition_t import JobDefinition, ProfileMetadata
9
10
 
10
11
 
@@ -19,29 +20,36 @@ cron_args_parser = CachedParser(CronJobArgs)
19
20
 
20
21
  def cron_job_executor(**kwargs: dict) -> None:
21
22
  args_passed = cron_args_parser.parse_storage(kwargs)
22
- client = construct_uncountable_client(profile_meta=args_passed.profile_metadata)
23
+ job_logger = JobLogger(
24
+ profile_metadata=args_passed.profile_metadata,
25
+ job_definition=args_passed.definition,
26
+ )
27
+ client = construct_uncountable_client(
28
+ profile_meta=args_passed.profile_metadata, job_logger=job_logger
29
+ )
23
30
  batch_processor = AsyncBatchProcessor(client=client)
24
31
  args = CronJobArguments(
25
32
  job_definition=args_passed.definition,
26
33
  client=client,
27
34
  batch_processor=batch_processor,
28
35
  profile_metadata=args_passed.profile_metadata,
29
- logger=JobLogger(
30
- profile_metadata=args_passed.profile_metadata,
31
- job_definition=args_passed.definition,
32
- ),
36
+ logger=job_logger,
33
37
  )
34
38
 
35
- job = resolve_executor(args_passed.definition.executor, args_passed.profile_metadata)
39
+ with job_logger.push_scope(args_passed.definition.name) as job_logger:
40
+ job = resolve_executor(
41
+ args_passed.definition.executor, args_passed.profile_metadata
42
+ )
36
43
 
37
- print(f"running job {args_passed.definition.name}")
44
+ job_logger.log_info("running job")
38
45
 
39
- job.run(args=args)
46
+ job.run(args=args)
40
47
 
41
- if batch_processor.current_queue_size() != 0:
42
- batch_processor.send()
48
+ if batch_processor.current_queue_size() != 0:
49
+ batch_processor.send()
43
50
 
44
- print(f"completed job {args_passed.definition.name}")
45
- submitted_batch_job_ids = batch_processor.get_submitted_job_ids()
46
- if len(submitted_batch_job_ids) != 0:
47
- print("submitted batch jobs", submitted_batch_job_ids)
51
+ submitted_batch_job_ids = batch_processor.get_submitted_job_ids()
52
+ job_logger.log_info(
53
+ "completed job",
54
+ attributes={"submitted_batch_job_ids": submitted_batch_job_ids},
55
+ )
@@ -17,8 +17,9 @@ from pkgs.filesystem_utils import (
17
17
  )
18
18
  from pkgs.filesystem_utils.filesystem_session import FileSystemSession
19
19
  from uncountable.core.file_upload import DataFileUpload, FileUpload
20
- from uncountable.integration.job import Job, JobArguments, JobLogger
20
+ from uncountable.integration.job import Job, JobArguments
21
21
  from uncountable.integration.secret_retrieval import retrieve_secret
22
+ from uncountable.integration.telemetry import JobLogger
22
23
  from uncountable.types.generic_upload_t import (
23
24
  GenericRemoteDirectoryScope,
24
25
  GenericUploadStrategy,
@@ -3,24 +3,10 @@ from dataclasses import dataclass
3
3
 
4
4
  from uncountable.core.async_batch import AsyncBatchProcessor
5
5
  from uncountable.core.client import Client
6
+ from uncountable.integration.telemetry import JobLogger
6
7
  from uncountable.types.job_definition_t import JobDefinition, JobResult, ProfileMetadata
7
8
 
8
9
 
9
- class JobLogger:
10
- def __init__(
11
- self, *, profile_metadata: ProfileMetadata, job_definition: JobDefinition
12
- ) -> None:
13
- self.profile_metadata = profile_metadata
14
- self.job_definition = job_definition
15
-
16
- def log_info(self, *log_objects: object) -> None:
17
- # IMPROVE: log a json message with context that can be parsed by OT
18
- print(
19
- f"[{self.job_definition.id}] in profile ({self.profile_metadata.name}): ",
20
- *log_objects,
21
- )
22
-
23
-
24
10
  @dataclass
25
11
  class JobArgumentsBase:
26
12
  job_definition: JobDefinition
@@ -1,5 +1,10 @@
1
+ import base64
2
+ import functools
3
+ import json
1
4
  import os
2
5
 
6
+ import boto3
7
+
3
8
  from uncountable.types.job_definition_t import ProfileMetadata
4
9
  from uncountable.types.secret_retrieval_t import (
5
10
  SecretRetrieval,
@@ -22,6 +27,25 @@ class SecretRetrievalError(BaseException):
22
27
  return f"{self.secret_retrieval.type} secret retrieval failed{append_message}"
23
28
 
24
29
 
30
+ @functools.cache
31
+ def _get_aws_secret(*, secret_name: str, region_name: str, sub_key: str | None) -> str:
32
+ client = boto3.client("secretsmanager", region_name=region_name)
33
+ response = client.get_secret_value(SecretId=secret_name)
34
+
35
+ if "SecretString" in response:
36
+ secret = response["SecretString"]
37
+ else:
38
+ secret = base64.b64decode(response["SecretBinary"])
39
+
40
+ value = json.loads(secret)
41
+
42
+ if sub_key is not None:
43
+ assert isinstance(value, dict)
44
+ return str(value[sub_key])
45
+ else:
46
+ return str(value)
47
+
48
+
25
49
  def retrieve_secret(
26
50
  secret_retrieval: SecretRetrieval, profile_metadata: ProfileMetadata
27
51
  ) -> str:
@@ -37,4 +61,11 @@ def retrieve_secret(
37
61
  )
38
62
  return secret
39
63
  case SecretRetrievalAWS():
40
- raise NotImplementedError("aws secret retrieval not yet implemented")
64
+ try:
65
+ return _get_aws_secret(
66
+ secret_name=secret_retrieval.secret_name,
67
+ region_name=secret_retrieval.region,
68
+ sub_key=secret_retrieval.sub_key,
69
+ )
70
+ except Exception as e:
71
+ raise SecretRetrievalError(secret_retrieval) from e
@@ -11,6 +11,7 @@ from apscheduler.triggers.cron import CronTrigger
11
11
  from sqlalchemy.engine.base import Engine
12
12
 
13
13
  from uncountable.integration.cron import CronJobArgs, cron_job_executor
14
+ from uncountable.types import base_t
14
15
  from uncountable.types.client_config_t import ClientConfigOptions
15
16
  from uncountable.types.job_definition_t import (
16
17
  AuthRetrieval,
@@ -67,7 +68,14 @@ class IntegrationServer:
67
68
  existing_job.reschedule(
68
69
  CronTrigger.from_crontab(job_defn.cron_spec)
69
70
  )
71
+ if not job_defn.enabled:
72
+ existing_job.pause()
73
+ else:
74
+ existing_job.resume()
70
75
  else:
76
+ job_opts: dict[str, base_t.JsonValue] = {}
77
+ if not job_defn.enabled:
78
+ job_opts["next_run_time"] = None
71
79
  self._scheduler.add_job(
72
80
  cron_job_executor,
73
81
  # IMPROVE: reconsider these defaults
@@ -77,6 +85,7 @@ class IntegrationServer:
77
85
  name=job_defn.name,
78
86
  id=job_defn.id,
79
87
  kwargs=job_kwargs,
88
+ **job_opts,
80
89
  )
81
90
  case _:
82
91
  assert_never(job_defn.trigger)
@@ -0,0 +1,130 @@
1
+ import functools
2
+ import os
3
+ import sys
4
+ import time
5
+ from contextlib import contextmanager
6
+ from enum import StrEnum
7
+ from typing import Generator, TextIO, assert_never, cast
8
+
9
+ from opentelemetry import trace
10
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
11
+ from opentelemetry.sdk._logs import LogRecord
12
+ from opentelemetry.sdk.resources import Attributes, Resource
13
+ from opentelemetry.sdk.trace import TracerProvider
14
+ from opentelemetry.sdk.trace.export import (
15
+ SimpleSpanProcessor,
16
+ )
17
+ from opentelemetry.trace import Tracer
18
+
19
+ from uncountable.core.version import get_version
20
+ from uncountable.types import base_t, job_definition_t
21
+
22
+
23
+ def _cast_attributes(attributes: dict[str, base_t.JsonValue]) -> Attributes:
24
+ return cast(Attributes, attributes)
25
+
26
+
27
+ @functools.cache
28
+ def get_tracer() -> Tracer:
29
+ attributes: dict[str, base_t.JsonValue] = {
30
+ "service.name": "integration-server",
31
+ "sdk.version": get_version(),
32
+ }
33
+ unc_version = os.environ.get("UNC_VERSION")
34
+ if unc_version is not None:
35
+ attributes["service.version"] = unc_version
36
+ unc_env = os.environ.get("UNC_INTEGRATION_ENV")
37
+ if unc_env is not None:
38
+ attributes["deployment.environment"] = unc_env
39
+ resource = Resource.create(attributes=_cast_attributes(attributes))
40
+ provider = TracerProvider(resource=resource)
41
+ provider.add_span_processor(SimpleSpanProcessor(OTLPSpanExporter()))
42
+ trace.set_tracer_provider(provider)
43
+ return provider.get_tracer("integration.telemetry")
44
+
45
+
46
+ class LogSeverity(StrEnum):
47
+ INFO = "Info"
48
+ WARN = "Warn"
49
+ ERROR = "Error"
50
+
51
+
52
+ class JobLogger:
53
+ current_span_id: int | None = None
54
+ current_trace_id: int | None = None
55
+
56
+ def __init__(
57
+ self,
58
+ *,
59
+ profile_metadata: job_definition_t.ProfileMetadata,
60
+ job_definition: job_definition_t.JobDefinition,
61
+ ) -> None:
62
+ self.profile_metadata = profile_metadata
63
+ self.job_definition = job_definition
64
+
65
+ def _patch_attributes(self, attributes: Attributes | None) -> Attributes:
66
+ patched_attributes: dict[str, base_t.JsonValue] = {
67
+ **(attributes if attributes is not None else {})
68
+ }
69
+ patched_attributes["profile.name"] = self.profile_metadata.name
70
+ patched_attributes["profile.base_url"] = self.profile_metadata.base_url
71
+ patched_attributes["job.name"] = self.job_definition.name
72
+ patched_attributes["job.id"] = self.job_definition.id
73
+ patched_attributes["job.definition_type"] = self.job_definition.type
74
+ match self.job_definition:
75
+ case job_definition_t.CronJobDefinition():
76
+ patched_attributes["job.definition.cron_spec"] = (
77
+ self.job_definition.cron_spec
78
+ )
79
+ case _:
80
+ assert_never(self.job_definition)
81
+ patched_attributes["job.definition.executor.type"] = (
82
+ self.job_definition.executor.type
83
+ )
84
+ match self.job_definition.executor:
85
+ case job_definition_t.JobExecutorScript():
86
+ patched_attributes["job.definition.executor.import_path"] = (
87
+ self.job_definition.executor.import_path
88
+ )
89
+ case job_definition_t.JobExecutorGenericUpload():
90
+ patched_attributes["job.definition.executor.data_source.type"] = (
91
+ self.job_definition.executor.data_source.type
92
+ )
93
+ case _:
94
+ assert_never(self.job_definition.executor)
95
+ return _cast_attributes(patched_attributes)
96
+
97
+ @contextmanager
98
+ def push_scope(
99
+ self, scope_name: str, *, attributes: Attributes | None = None
100
+ ) -> Generator["JobLogger", None, None]:
101
+ with get_tracer().start_as_current_span(
102
+ scope_name, attributes=self._patch_attributes(attributes)
103
+ ) as span:
104
+ self.current_span_id = span.get_span_context().span_id
105
+ self.current_trace_id = span.get_span_context().trace_id
106
+ yield self
107
+
108
+ def _emit_log(
109
+ self, message: str, *, severity: LogSeverity, attributes: Attributes | None
110
+ ) -> None:
111
+ log_record = LogRecord(
112
+ body=message,
113
+ severity_text=severity,
114
+ timestamp=time.time_ns(),
115
+ attributes=self._patch_attributes(attributes),
116
+ span_id=self.current_span_id,
117
+ trace_id=self.current_trace_id,
118
+ )
119
+ log_file: TextIO = sys.stderr if severity == LogSeverity.ERROR else sys.stdout
120
+ log_file.write(log_record.to_json())
121
+ log_file.flush()
122
+
123
+ def log_info(self, message: str, *, attributes: Attributes | None = None) -> None:
124
+ self._emit_log(message=message, severity=LogSeverity.INFO, attributes=attributes)
125
+
126
+ def log_warning(self, message: str, *, attributes: Attributes | None = None) -> None:
127
+ self._emit_log(message=message, severity=LogSeverity.WARN, attributes=attributes)
128
+
129
+ def log_error(self, message: str, *, attributes: Attributes | None = None) -> None:
130
+ self._emit_log(message=message, severity=LogSeverity.ERROR, attributes=attributes)
@@ -34,7 +34,7 @@ class EntityToCreate:
34
34
  @dataclasses.dataclass(kw_only=True)
35
35
  class Arguments:
36
36
  definition_id: base_t.ObjectId
37
- entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS]]
37
+ entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS], typing.Literal[entity_t.EntityType.GOAL]]
38
38
  entities_to_create: list[EntityToCreate]
39
39
 
40
40
 
@@ -40,7 +40,7 @@ class EntityFieldInitialValue:
40
40
  @dataclasses.dataclass(kw_only=True)
41
41
  class Arguments:
42
42
  definition_id: base_t.ObjectId
43
- entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS]]
43
+ entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS], typing.Literal[entity_t.EntityType.GOAL]]
44
44
  field_values: typing.Optional[typing.Optional[list[field_values_t.FieldRefNameValue]]] = None
45
45
 
46
46
 
@@ -29,6 +29,7 @@ class Arguments:
29
29
  file_id: base_t.ObjectId
30
30
  uploader_key: identifier_t.IdentifierKey
31
31
  material_family_key: identifier_t.IdentifierKey
32
+ recipe_key: typing.Optional[identifier_t.IdentifierKey] = None
32
33
 
33
34
 
34
35
  # DO NOT MODIFY -- This file is generated by type_spec
@@ -163,6 +163,7 @@ class AsyncBatchProcessorBase(ABC):
163
163
  file_id: base_t.ObjectId,
164
164
  uploader_key: identifier_t.IdentifierKey,
165
165
  material_family_key: identifier_t.IdentifierKey,
166
+ recipe_key: typing.Optional[identifier_t.IdentifierKey] = None,
166
167
  depends_on: typing.Optional[list[str]] = None,
167
168
  ) -> async_batch_t.QueuedAsyncBatchRequest:
168
169
  """Runs a file through an uploader.
@@ -173,6 +174,7 @@ class AsyncBatchProcessorBase(ABC):
173
174
  file_id=file_id,
174
175
  uploader_key=uploader_key,
175
176
  material_family_key=material_family_key,
177
+ recipe_key=recipe_key,
176
178
  )
177
179
  json_data = serialize_for_api(args)
178
180
 
@@ -235,7 +235,7 @@ class ClientMethods(ABC):
235
235
  self,
236
236
  *,
237
237
  definition_id: base_t.ObjectId,
238
- entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS]],
238
+ entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS], typing.Literal[entity_t.EntityType.GOAL]],
239
239
  entities_to_create: list[create_entities_t.EntityToCreate],
240
240
  ) -> create_entities_t.Data:
241
241
  """Creates new Uncountable entities
@@ -260,7 +260,7 @@ class ClientMethods(ABC):
260
260
  self,
261
261
  *,
262
262
  definition_id: base_t.ObjectId,
263
- entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS]],
263
+ entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS], typing.Literal[entity_t.EntityType.GOAL]],
264
264
  field_values: typing.Optional[typing.Optional[list[field_values_t.FieldRefNameValue]]] = None,
265
265
  ) -> create_entity_t.Data:
266
266
  """Creates a new Uncountable entity
@@ -857,6 +857,7 @@ class ClientMethods(ABC):
857
857
  file_id: base_t.ObjectId,
858
858
  uploader_key: identifier_t.IdentifierKey,
859
859
  material_family_key: identifier_t.IdentifierKey,
860
+ recipe_key: typing.Optional[identifier_t.IdentifierKey] = None,
860
861
  ) -> invoke_uploader_t.Data:
861
862
  """Runs a file through an uploader.
862
863
 
@@ -865,6 +866,7 @@ class ClientMethods(ABC):
865
866
  file_id=file_id,
866
867
  uploader_key=uploader_key,
867
868
  material_family_key=material_family_key,
869
+ recipe_key=recipe_key,
868
870
  )
869
871
  api_request = APIRequest(
870
872
  method=invoke_uploader_t.ENDPOINT_METHOD,
@@ -21,7 +21,8 @@ from .job_definition_t import JobDefinitionBase as JobDefinitionBase
21
21
  from .job_definition_t import CronJobDefinition as CronJobDefinition
22
22
  from .job_definition_t import JobDefinition as JobDefinition
23
23
  from .job_definition_t import AuthRetrievalBase as AuthRetrievalBase
24
- from .job_definition_t import AuthRetrievalEnv as AuthRetrievalEnv
24
+ from .job_definition_t import AuthRetrievalOAuth as AuthRetrievalOAuth
25
+ from .job_definition_t import AuthRetrievalBasic as AuthRetrievalBasic
25
26
  from .job_definition_t import AuthRetrieval as AuthRetrieval
26
27
  from .job_definition_t import ProfileDefinition as ProfileDefinition
27
28
  from .job_definition_t import ProfileMetadata as ProfileMetadata
@@ -18,7 +18,8 @@ from . import secret_retrieval_t
18
18
  __all__: list[str] = [
19
19
  "AuthRetrieval",
20
20
  "AuthRetrievalBase",
21
- "AuthRetrievalEnv",
21
+ "AuthRetrievalBasic",
22
+ "AuthRetrievalOAuth",
22
23
  "AuthRetrievalType",
23
24
  "CronJobDefinition",
24
25
  "GenericUploadDataSource",
@@ -54,7 +55,8 @@ class JobExecutorType(StrEnum):
54
55
 
55
56
  # DO NOT MODIFY -- This file is generated by type_spec
56
57
  class AuthRetrievalType(StrEnum):
57
- ENV = "env"
58
+ OAUTH = "oauth"
59
+ BASIC = "basic"
58
60
 
59
61
 
60
62
  # DO NOT MODIFY -- This file is generated by type_spec
@@ -162,6 +164,7 @@ class JobDefinitionBase:
162
164
  id: str
163
165
  name: str
164
166
  executor: JobExecutor
167
+ enabled: bool = True
165
168
 
166
169
 
167
170
  # DO NOT MODIFY -- This file is generated by type_spec
@@ -197,17 +200,30 @@ class AuthRetrievalBase:
197
200
  parse_require={"type"},
198
201
  )
199
202
  @dataclasses.dataclass(kw_only=True)
200
- class AuthRetrievalEnv(AuthRetrievalBase):
201
- type: typing.Literal[AuthRetrievalType.ENV] = AuthRetrievalType.ENV
203
+ class AuthRetrievalOAuth(AuthRetrievalBase):
204
+ type: typing.Literal[AuthRetrievalType.OAUTH] = AuthRetrievalType.OAUTH
205
+ refresh_token_secret: secret_retrieval_t.SecretRetrieval
206
+
207
+
208
+ # DO NOT MODIFY -- This file is generated by type_spec
209
+ @serial_class(
210
+ parse_require={"type"},
211
+ )
212
+ @dataclasses.dataclass(kw_only=True)
213
+ class AuthRetrievalBasic(AuthRetrievalBase):
214
+ type: typing.Literal[AuthRetrievalType.BASIC] = AuthRetrievalType.BASIC
215
+ api_id_secret: secret_retrieval_t.SecretRetrieval
216
+ api_key_secret: secret_retrieval_t.SecretRetrieval
202
217
 
203
218
 
204
219
  # DO NOT MODIFY -- This file is generated by type_spec
205
220
  AuthRetrieval = typing.Annotated[
206
- typing.Union[AuthRetrievalEnv],
221
+ typing.Union[AuthRetrievalOAuth, AuthRetrievalBasic],
207
222
  serial_union_annotation(
208
223
  discriminator="type",
209
224
  discriminator_map={
210
- "env": AuthRetrievalEnv,
225
+ "oauth": AuthRetrievalOAuth,
226
+ "basic": AuthRetrievalBasic,
211
227
  },
212
228
  ),
213
229
  ]