UncountablePythonSDK 0.0.49__py3-none-any.whl → 0.0.50__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.
- {UncountablePythonSDK-0.0.49.dist-info → UncountablePythonSDK-0.0.50.dist-info}/METADATA +5 -1
- {UncountablePythonSDK-0.0.49.dist-info → UncountablePythonSDK-0.0.50.dist-info}/RECORD +17 -15
- {UncountablePythonSDK-0.0.49.dist-info → UncountablePythonSDK-0.0.50.dist-info}/WHEEL +1 -1
- docs/requirements.txt +1 -1
- uncountable/core/client.py +14 -9
- uncountable/core/version.py +11 -0
- uncountable/integration/construct_client.py +9 -3
- uncountable/integration/cron.py +23 -15
- uncountable/integration/executors/generic_upload_executor.py +2 -1
- uncountable/integration/job.py +1 -15
- uncountable/integration/server.py +9 -0
- uncountable/integration/telemetry.py +130 -0
- uncountable/types/api/uploader/invoke_uploader.py +1 -0
- uncountable/types/async_batch_processor.py +2 -0
- uncountable/types/client_base.py +2 -0
- uncountable/types/job_definition_t.py +1 -0
- {UncountablePythonSDK-0.0.49.dist-info → UncountablePythonSDK-0.0.50.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: UncountablePythonSDK
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.50
|
|
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=
|
|
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=
|
|
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=
|
|
82
|
-
uncountable/integration/cron.py,sha256=
|
|
82
|
+
uncountable/integration/construct_client.py,sha256=m6iFAhyAkD3WETLGayv5t47WpwLP1bct4k1z2a2GPh0,1703
|
|
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=
|
|
85
|
-
uncountable/integration/server.py,sha256=
|
|
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=
|
|
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
95
|
uncountable/integration/secret_retrieval/retrieve_secret.py,sha256=M0qXVJpD8hMYIFypHFeyh598sqmIDX8ZOyXK23CluF0,1323
|
|
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=
|
|
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=
|
|
106
|
+
uncountable/types/client_base.py,sha256=XjgzUcLIE-Q-Tc30IQ8hv7qw4LF7yQlKS0H_130ruwA,65347
|
|
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
|
|
@@ -125,7 +127,7 @@ uncountable/types/input_attributes_t.py,sha256=wE1ekiQfb72Z9VpF5SHipKJkgaJFUHJrN
|
|
|
125
127
|
uncountable/types/inputs.py,sha256=6RIEFfCxLqpeHEGOpu63O4i8zPogjGeB7wiV_rPBw_g,404
|
|
126
128
|
uncountable/types/inputs_t.py,sha256=RW7gF9zTOwByu-nMTMVuBabLOuWKx4O1nvfgvx_R55o,1611
|
|
127
129
|
uncountable/types/job_definition.py,sha256=sCQqtyHI3hksc5pVpk5tqbG55F91ST4FoDwr2TmTOuQ,1787
|
|
128
|
-
uncountable/types/job_definition_t.py,sha256=
|
|
130
|
+
uncountable/types/job_definition_t.py,sha256=oDHLHCophse2x8vZOYagQ49yz7GTnCKwY6ecpDQsfRk,6616
|
|
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
|
|
@@ -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=
|
|
241
|
-
UncountablePythonSDK-0.0.
|
|
242
|
-
UncountablePythonSDK-0.0.
|
|
243
|
-
UncountablePythonSDK-0.0.
|
|
244
|
-
UncountablePythonSDK-0.0.
|
|
242
|
+
uncountable/types/api/uploader/invoke_uploader.py,sha256=4zOcB_38uT73Jm3-XqkG40fBM1R5vpvPpGAg-U4lzxY,1059
|
|
243
|
+
UncountablePythonSDK-0.0.50.dist-info/METADATA,sha256=h6s5vQQCK3UNrcaHgQxLbyyskpShrzKpEToBx0Rx5EQ,1934
|
|
244
|
+
UncountablePythonSDK-0.0.50.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
|
|
245
|
+
UncountablePythonSDK-0.0.50.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
|
|
246
|
+
UncountablePythonSDK-0.0.50.dist-info/RECORD,,
|
docs/requirements.txt
CHANGED
uncountable/core/client.py
CHANGED
|
@@ -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
|
-
|
|
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] =
|
|
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
|
|
@@ -3,6 +3,7 @@ import os
|
|
|
3
3
|
from uncountable.core import AuthDetailsApiKey, Client
|
|
4
4
|
from uncountable.core.client import ClientConfig
|
|
5
5
|
from uncountable.core.types import AuthDetailsAll
|
|
6
|
+
from uncountable.integration.telemetry import JobLogger
|
|
6
7
|
from uncountable.types.job_definition_t import (
|
|
7
8
|
AuthRetrievalEnv,
|
|
8
9
|
ProfileMetadata,
|
|
@@ -30,18 +31,23 @@ def _construct_auth_details(profile_meta: ProfileMetadata) -> AuthDetailsAll:
|
|
|
30
31
|
return AuthDetailsApiKey(api_id=api_id, api_secret_key=api_secret_key)
|
|
31
32
|
|
|
32
33
|
|
|
33
|
-
def _construct_client_config(
|
|
34
|
+
def _construct_client_config(
|
|
35
|
+
profile_meta: ProfileMetadata, job_logger: JobLogger
|
|
36
|
+
) -> ClientConfig | None:
|
|
34
37
|
if profile_meta.client_options is None:
|
|
35
38
|
return None
|
|
36
39
|
return ClientConfig(
|
|
37
40
|
allow_insecure_tls=profile_meta.client_options.allow_insecure_tls,
|
|
38
41
|
extra_headers=profile_meta.client_options.extra_headers,
|
|
42
|
+
job_logger=job_logger,
|
|
39
43
|
)
|
|
40
44
|
|
|
41
45
|
|
|
42
|
-
def construct_uncountable_client(
|
|
46
|
+
def construct_uncountable_client(
|
|
47
|
+
profile_meta: ProfileMetadata, job_logger: JobLogger
|
|
48
|
+
) -> Client:
|
|
43
49
|
return Client(
|
|
44
50
|
base_url=profile_meta.base_url,
|
|
45
51
|
auth_details=_construct_auth_details(profile_meta),
|
|
46
|
-
config=_construct_client_config(profile_meta),
|
|
52
|
+
config=_construct_client_config(profile_meta, job_logger),
|
|
47
53
|
)
|
uncountable/integration/cron.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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=
|
|
30
|
-
profile_metadata=args_passed.profile_metadata,
|
|
31
|
-
job_definition=args_passed.definition,
|
|
32
|
-
),
|
|
36
|
+
logger=job_logger,
|
|
33
37
|
)
|
|
34
38
|
|
|
35
|
-
|
|
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
|
-
|
|
44
|
+
job_logger.log_info("running job")
|
|
38
45
|
|
|
39
|
-
|
|
46
|
+
job.run(args=args)
|
|
40
47
|
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
if batch_processor.current_queue_size() != 0:
|
|
49
|
+
batch_processor.send()
|
|
43
50
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
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,
|
uncountable/integration/job.py
CHANGED
|
@@ -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
|
|
@@ -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)
|
|
@@ -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
|
|
uncountable/types/client_base.py
CHANGED
|
@@ -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,
|
{UncountablePythonSDK-0.0.49.dist-info → UncountablePythonSDK-0.0.50.dist-info}/top_level.txt
RENAMED
|
File without changes
|