UncountablePythonSDK 0.0.184__py3-none-any.whl → 0.0.185__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.
@@ -0,0 +1,44 @@
1
+ import json
2
+
3
+ from uncountable.integration.http_server import (
4
+ GenericHttpRequest,
5
+ GenericHttpResponse,
6
+ HttpException,
7
+ )
8
+ from uncountable.integration.job import CustomHttpJob, register_job
9
+ from uncountable.types import job_definition_t
10
+
11
+ _EXPECTED_USER_ID = 1
12
+
13
+
14
+ @register_job
15
+ class HttpGetExample(CustomHttpJob):
16
+ @staticmethod
17
+ def validate_request(
18
+ *,
19
+ request: GenericHttpRequest,
20
+ job_definition: job_definition_t.HttpJobDefinitionBase, # ruff:ignore[unused-static-method-argument]
21
+ profile_meta: job_definition_t.ProfileMetadata,
22
+ ) -> None:
23
+ if (
24
+ CustomHttpJob.get_validated_oauth_request_user_id(
25
+ request=request, profile_metadata=profile_meta
26
+ )
27
+ != _EXPECTED_USER_ID
28
+ ):
29
+ raise HttpException(
30
+ message="unauthorized; invalid oauth token", error_code=401
31
+ )
32
+
33
+ @staticmethod
34
+ def handle_request(
35
+ *,
36
+ request: GenericHttpRequest,
37
+ job_definition: job_definition_t.HttpJobDefinitionBase, # ruff:ignore[unused-static-method-argument]
38
+ profile_meta: job_definition_t.ProfileMetadata, # ruff:ignore[unused-static-method-argument]
39
+ ) -> GenericHttpResponse:
40
+ return GenericHttpResponse(
41
+ response=json.dumps(request.query_params),
42
+ status_code=200,
43
+ headers={"Content-Type": "application/json"},
44
+ )
@@ -74,6 +74,13 @@ jobs:
74
74
  executor:
75
75
  type: script
76
76
  import_path: example_http
77
+ - id: example_http_get
78
+ type: custom_http
79
+ name: Custom HTTP GET
80
+ http_method: get
81
+ executor:
82
+ type: script
83
+ import_path: example_http_get
77
84
  - id: example_runsheet_wh
78
85
  type: webhook
79
86
  name: Runsheet Webhook
@@ -210,8 +210,7 @@ def serialize_for_api(obj: Any) -> JsonValue:
210
210
  serialization_type == SerializationType.UNKNOWN
211
211
  ): # performance optimization to not do function lookup
212
212
  return obj # type: ignore
213
- r = _CONVERSION_SERIALIZATION_FUNCS[serialization_type](obj)
214
- return r
213
+ return _CONVERSION_SERIALIZATION_FUNCS[serialization_type](obj)
215
214
 
216
215
 
217
216
  _SERIALIZATION_FUNCS_DICT: dict[
@@ -68,6 +68,7 @@ class HttpException(Exception):
68
68
  class GenericHttpRequest:
69
69
  body_base64: str
70
70
  headers: dict[str, str]
71
+ query_params: dict[str, list[str]]
71
72
 
72
73
  @functools.cached_property
73
74
  def body_bytes(self) -> bytes:
@@ -1,4 +1,5 @@
1
1
  import base64
2
+ from typing import assert_never
2
3
 
3
4
  import flask
4
5
  from flask.typing import ResponseReturnValue
@@ -16,12 +17,31 @@ from uncountable.types import job_definition_t
16
17
 
17
18
  app = flask.Flask(__name__)
18
19
 
20
+ HttpJobDefinition = (
21
+ job_definition_t.CustomHttpJobDefinition
22
+ | job_definition_t.WebhookJobDefinition
23
+ | job_definition_t.UncountableWebhookJobDefinition
24
+ )
25
+
26
+
27
+ def _resolve_route_http_method(job: HttpJobDefinition) -> job_definition_t.HttpMethod:
28
+ match job:
29
+ case job_definition_t.CustomHttpJobDefinition():
30
+ return job.http_method
31
+ case (
32
+ job_definition_t.WebhookJobDefinition()
33
+ | job_definition_t.UncountableWebhookJobDefinition()
34
+ ):
35
+ return job_definition_t.HttpMethod.POST
36
+ case _:
37
+ assert_never(job)
38
+
19
39
 
20
40
  def register_route(
21
41
  *,
22
42
  server_logger: Logger,
23
43
  profile_meta: job_definition_t.ProfileMetadata,
24
- job: job_definition_t.HttpJobDefinitionBase,
44
+ job: HttpJobDefinition,
25
45
  ) -> None:
26
46
  route = f"/{profile_meta.name}/{job.id}"
27
47
  log_attributes = {
@@ -56,6 +76,7 @@ def register_route(
56
76
  http_request = GenericHttpRequest(
57
77
  body_base64=base64.b64encode(flask.request.get_data()).decode(),
58
78
  headers=dict(flask.request.headers),
79
+ query_params=flask.request.args.to_dict(flat=False),
59
80
  )
60
81
  job_instance.validate_request(
61
82
  request=http_request, job_definition=job, profile_meta=profile_meta
@@ -88,7 +109,7 @@ def register_route(
88
109
  route,
89
110
  endpoint=f"handle_request_{job.id}",
90
111
  view_func=handle_request,
91
- methods=["POST"],
112
+ methods=[_resolve_route_http_method(job).value.upper()],
92
113
  )
93
114
 
94
115
  server_logger.log_debug(
@@ -103,10 +124,21 @@ def main() -> None:
103
124
  for profile_metadata in profiles:
104
125
  server_logger = Logger(get_current_span())
105
126
  for job in profile_metadata.jobs:
106
- if isinstance(job, job_definition_t.HttpJobDefinitionBase):
107
- register_route(
108
- server_logger=server_logger, profile_meta=profile_metadata, job=job
109
- )
127
+ match job:
128
+ case job_definition_t.CronJobDefinition():
129
+ continue
130
+ case (
131
+ job_definition_t.CustomHttpJobDefinition()
132
+ | job_definition_t.WebhookJobDefinition()
133
+ | job_definition_t.UncountableWebhookJobDefinition()
134
+ ):
135
+ register_route(
136
+ server_logger=server_logger,
137
+ profile_meta=profile_metadata,
138
+ job=job,
139
+ )
140
+ case _:
141
+ assert_never(job)
110
142
 
111
143
 
112
144
  main()
@@ -30,7 +30,7 @@ __all__: list[str] = [
30
30
 
31
31
  ENDPOINT_METHOD = "GET"
32
32
  ENDPOINT_PATH = "api/external/files/download_file"
33
- ENDPOINT_DESCRIPTION = "Download an attachment field value from an entity (e.g. a file attached to a form field) by entity + field reference, or download a text document by its ID. When querying by entity field, if the field contains a single file it is returned directly (X-File-Download-Type: raw); if multiple files are attached, they are bundled into a ZIP archive (X-File-Download-Type: archive). For downloading the binary file stored on a recipe output of type image or file, use recipes/external_download_recipe_output_file instead."
33
+ ENDPOINT_DESCRIPTION = "Download an attachment field value from an entity (e.g. a file attached to a form field) by entity + field reference, or download a text document by its ID. When querying by entity field, if the field contains a single file it is returned directly (X-File-Download-Type: raw); if multiple files are attached, they are bundled into a ZIP archive (X-File-Download-Type: archive). For downloading the binary file stored on a recipe output of type image or file, use recipes/external_download_recipe_output_file instead. A file that has not cleared virus scanning is not returned: a file still being scanned responds with HTTP 422 indicating the scan is in progress, and a file that failed the scan responds with HTTP 422 indicating a security-scan failure."
34
34
 
35
35
 
36
36
  # DO NOT MODIFY -- This file is generated by type_spec
@@ -4,6 +4,7 @@
4
4
  # DO NOT MODIFY -- This file is generated by type_spec
5
5
  # Kept only for SDK backwards compatibility
6
6
  from .job_definition_t import JobDefinitionType as JobDefinitionType
7
+ from .job_definition_t import HttpMethod as HttpMethod
7
8
  from .job_definition_t import JobExecutorType as JobExecutorType
8
9
  from .job_definition_t import GenericUploadDataSourceType as GenericUploadDataSourceType
9
10
  from .job_definition_t import JobExecutorBase as JobExecutorBase
@@ -27,6 +27,7 @@ __all__: list[str] = [
27
27
  "GenericUploadDataSourceSFTP",
28
28
  "GenericUploadDataSourceType",
29
29
  "HttpJobDefinitionBase",
30
+ "HttpMethod",
30
31
  "JobDefinition",
31
32
  "JobDefinitionBase",
32
33
  "JobDefinitionType",
@@ -53,6 +54,12 @@ class JobDefinitionType(StrEnum):
53
54
  CUSTOM_HTTP = "custom_http"
54
55
 
55
56
 
57
+ # DO NOT MODIFY -- This file is generated by type_spec
58
+ class HttpMethod(StrEnum):
59
+ GET = "get"
60
+ POST = "post"
61
+
62
+
56
63
  # DO NOT MODIFY -- This file is generated by type_spec
57
64
  class JobExecutorType(StrEnum):
58
65
  SCRIPT = "script"
@@ -224,6 +231,7 @@ class HttpJobDefinitionBase(JobDefinitionBase):
224
231
  @dataclasses.dataclass(slots=True, kw_only=True)
225
232
  class CustomHttpJobDefinition(HttpJobDefinitionBase):
226
233
  type: typing.Literal[JobDefinitionType.CUSTOM_HTTP] = JobDefinitionType.CUSTOM_HTTP
234
+ http_method: HttpMethod = HttpMethod.POST
227
235
 
228
236
 
229
237
  # DO NOT MODIFY -- This file is generated by type_spec
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: UncountablePythonSDK
3
- Version: 0.0.184
3
+ Version: 0.0.185
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
@@ -37,12 +37,13 @@ examples/integration-server/pyproject.toml,sha256=CxNt8vQ17Po7h4fOXT8jtbrFJosxBV
37
37
  examples/integration-server/jobs/materials_auto/concurrent_cron.py,sha256=xsK3H9ZEaniedC2nJUB0rqOcFI8y-ojfl_nLSJb9AMM,312
38
38
  examples/integration-server/jobs/materials_auto/example_cron.py,sha256=y1nAtGwbPJfIrfQsrHVmJLAHmQtCEHIy1g-0PjeXx04,735
39
39
  examples/integration-server/jobs/materials_auto/example_http.py,sha256=hA--iNztlTwLaEBzlmoRZ6-hRTUExUUqLV0-ipU7_mo,1560
40
+ examples/integration-server/jobs/materials_auto/example_http_get.py,sha256=_MyrwSBVCbDbb0VEmCpXpHIJzKC8NnEwjAXpQFiotyM,1438
40
41
  examples/integration-server/jobs/materials_auto/example_instrument.py,sha256=XWy1mCvDwL_Yfl3psFkD1ZX3K2vDQxPXgcMIABB5bLQ,3351
41
42
  examples/integration-server/jobs/materials_auto/example_parse.py,sha256=Z3iqAUdI5ya07VS3XayjK8brKuAyaqXlImJMJIUHz_4,8660
42
43
  examples/integration-server/jobs/materials_auto/example_predictions.py,sha256=5fO4rqRa80_968A1uVZn2TlMOUib54A8rumGW02sIMM,2112
43
44
  examples/integration-server/jobs/materials_auto/example_runsheet_wh.py,sha256=7FWDz3QpueVcf83fPrwGSqxs9pX06iKcQFte_vz3dC8,2944
44
45
  examples/integration-server/jobs/materials_auto/example_wh.py,sha256=PN-skP27yJwDZboWk5g5EZEc3AKfVayQLfnopjsDKJc,659
45
- examples/integration-server/jobs/materials_auto/profile.yaml,sha256=N9kX-xczfgBSqnfY5qsxmjxXkBXLhMnZU1TOMWI1hfg,2587
46
+ examples/integration-server/jobs/materials_auto/profile.yaml,sha256=qEgEunAcmuIIVsGCvNZ1sFJPfrefonN5r9BLTt5DIFg,2750
46
47
  pkgs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
48
  pkgs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
49
  pkgs/argument_parser/__init__.py,sha256=cEEOAQS8UJ4Kbuo0oCVAHHhkbYCfrH6cUTTt2Mq3PmI,1445
@@ -81,7 +82,7 @@ pkgs/serialization_util/__init__.py,sha256=DEMIjEmOVZHuSwsxI9uUs3CNDSztNgcpOp35D
81
82
  pkgs/serialization_util/_get_type_for_serialization.py,sha256=dW5_W9MFd6wgWfW5qlWork-GBb-QFLtiOZkjk2Zqn2M,1177
82
83
  pkgs/serialization_util/convert_to_snakecase.py,sha256=VeI7d6JMexvfveZBZE4brMTyW4I9BEdl38bioQn8M6g,759
83
84
  pkgs/serialization_util/dataclasses.py,sha256=ErjxC60hnn_2XpQM-OyQBsVOaTz8JaHB--g4CjwoTK4,417
84
- pkgs/serialization_util/serialization_helpers.py,sha256=v8omOQ2BRZZCzMeUwu29fCEqx0UNDJ_iNbqpW4oHk84,7804
85
+ pkgs/serialization_util/serialization_helpers.py,sha256=UhekzGUcscopGTknPeb-_XM19u17jLBmbqhrpqNjkcg,7794
85
86
  pkgs/strenum_compat/__init__.py,sha256=Bq1kcPIQsXRiLUGWc63QGnVa5BlhvuFWpMMvdNcHy00,76
86
87
  pkgs/strenum_compat/strenum_compat.py,sha256=uOUAgpYTjHs1MX8dG81jRlyTkt3KNbkV_25zp7xTX2s,36
87
88
  pkgs/type_spec/__init__.py,sha256=h5DmJTca4QVV10sZR1x0-MlkZfuGYDfapR3zHvXfzto,19
@@ -151,7 +152,7 @@ uncountable/integration/executors/executors.py,sha256=E3a0_yrQesfigDzjhUAwD7-l-o
151
152
  uncountable/integration/executors/generic_upload_executor.py,sha256=MuNzrMnAaj6PBhrfTKX6e6vyQhwoDeBrYLz96ai653k,12149
152
153
  uncountable/integration/executors/script_executor.py,sha256=hKq09fOG89OAZ1Iq7wm2OgpUz1bh7DIYdZ2yn6ySr10,881
153
154
  uncountable/integration/http_server/__init__.py,sha256=ld7hD3wOGwAbnVRBrkTUCP5zNG-kptMd4wGJyQvUWVc,170
154
- uncountable/integration/http_server/types.py,sha256=9Hnt_rvrhGh0MCj1O2k2KHF81U85jURUnG6vt0boPDk,2425
155
+ uncountable/integration/http_server/types.py,sha256=yrpSEPz7mS8xfNHCsYtDD3OIgZpu1dsND4pRELxFI6A,2464
155
156
  uncountable/integration/queue_runner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
156
157
  uncountable/integration/queue_runner/job_process.py,sha256=OONJ7aLsE80qIJ2ABLY3HYyKI7VdpBjI_pPQ3GCC13U,2586
157
158
  uncountable/integration/queue_runner/job_runner.py,sha256=Yh9hYfEYOya3BR6ypsal3LKPKrrEb4VQwfGuh4qyU2w,6115
@@ -180,7 +181,7 @@ uncountable/integration/queue_runner/datastore/model.py,sha256=06_fGS0QTALEA_381
180
181
  uncountable/integration/secret_retrieval/__init__.py,sha256=sisf8cxCYcJmCXCKjEPoT2xIxm1j-Ru9iTfMVxH6NM0,171
181
182
  uncountable/integration/secret_retrieval/basic_secret.py,sha256=PfpeaEDwdi8nFlHUJnzCob9GYgGG5O_3mmcaxPPY6zI,3272
182
183
  uncountable/integration/secret_retrieval/retrieve_secret.py,sha256=c0VkCvppWKXwgxdblQuj2WhRaEGBPa7vLDoS6mlNOAQ,3919
183
- uncountable/integration/webhook_server/entrypoint.py,sha256=_1360zLIvEVQVf8ToyK-E2fnaszGKs2HcxcZjNraADc,4268
184
+ uncountable/integration/webhook_server/entrypoint.py,sha256=e7aYLsp5ht8Bzmj522_-5xSEm9zyHw-NGvggYCWClDE,5399
184
185
  uncountable/types/__init__.py,sha256=7fIYhDsz995jFGK7wBXIVMzIxUJbltUmdqm9U6HA_QQ,14367
185
186
  uncountable/types/async_batch.py,sha256=yCCWrrLQfxXVqZp-KskxLBNkNmuELdz4PJjx8ULppgs,662
186
187
  uncountable/types/async_batch_processor.py,sha256=6Z1qx_H9IqLqp0rzpCtrRvw0DqQ2O5ctkEnw0yNVaV8,48727
@@ -232,8 +233,8 @@ uncountable/types/integration_session.py,sha256=MVTtZa04INF4L8PxPjqz3l1Lse6Hze3I
232
233
  uncountable/types/integration_session_t.py,sha256=Nnes-u2RzvZYsybQsNhtBfbA3d2W_nuDjAS_mU6L6yM,1801
233
234
  uncountable/types/integrations.py,sha256=0fOhtbLIOl9w1GP9J3PTagRU8mjOKV48JNLLH3SJQP0,472
234
235
  uncountable/types/integrations_t.py,sha256=ZWwEP13mIkSiljSHN2p05XCwRgT2bdADs-8hiVQTs3I,1752
235
- uncountable/types/job_definition.py,sha256=aiaoOPgQNK1sn6sNVXzGi5NLwHfz6aHZfDSrvC9M2LE,1901
236
- uncountable/types/job_definition_t.py,sha256=IRMG_xRRH9lsn_BgAZOj2elRMTZ1gzWYKUlTFXGydYE,9650
236
+ uncountable/types/job_definition.py,sha256=s6hjlxlJgAyD9aDjV0EdAMc0zW91XCD8h7VBX9I9bIE,1956
237
+ uncountable/types/job_definition_t.py,sha256=bGBvhDi0CIRmeKC5jGw5bb-LDBG53czZaZlYbgo5tuQ,9832
237
238
  uncountable/types/listing.py,sha256=U-0-opI0Wgxzmwp4y0qhyT26aK_BjaqdlFVpjud-ryk,2838
238
239
  uncountable/types/listing_t.py,sha256=z0hpUlburpIrVA5KcO1TRjkyAGGuAHvpHt5Fo6cAqIc,17521
239
240
  uncountable/types/ml_reports.py,sha256=qhZ36pbFLOcoET3D_K2DiNx59bjp8WyurbCrWPsZTrM,757
@@ -338,7 +339,7 @@ uncountable/types/api/file_folders/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHk
338
339
  uncountable/types/api/file_folders/add_file_to_folder.py,sha256=0CnGFFSKyLsN6bOOYieh0Esiqq5XgQWA83bUn1jWXwY,1534
339
340
  uncountable/types/api/file_folders/modify_file_system.py,sha256=tXjHwtR7hUudCiwtcM2lHXVc4sgCI_R3-w6SWWwpCTU,2950
340
341
  uncountable/types/api/files/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
341
- uncountable/types/api/files/download_file.py,sha256=Ou2fcfuyuuJeXL-brOpzEJfWqAcEFuUPRSHvx_ZU4nM,3746
342
+ uncountable/types/api/files/download_file.py,sha256=w_GNviiXHqrnFU2pbriy6sl5i415JA0TFzXeJB_o00A,3983
342
343
  uncountable/types/api/id_source/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
343
344
  uncountable/types/api/id_source/list_id_source.py,sha256=tvrFKTynmfcLekUJnYSxSQi5V0N68ULjtn6xx0RmKec,1585
344
345
  uncountable/types/api/id_source/match_id_source.py,sha256=xnKnhHQBYipJmMxfH-7MHyNM6QOTrkSApuN9oAlk2PA,1560
@@ -433,7 +434,7 @@ uncountable/types/api/uploader/complete_async_parse.py,sha256=Os1HAubxTlbut6Gylj
433
434
  uncountable/types/api/uploader/invoke_uploader.py,sha256=QU1KmAFgGHSWskZEKfaAww8rCj-hVNP0i4kMQE3x3Fc,1822
434
435
  uncountable/types/api/user/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
435
436
  uncountable/types/api/user/get_current_user_info.py,sha256=BT7bGp5SOHz3XdKWhhdZ6acnInafT2PgdJ-H4YH3W7c,1181
436
- uncountablepythonsdk-0.0.184.dist-info/METADATA,sha256=mC6m3lEpjQAUsOqEx1Vcc7aoLKXtvnvRXmSUTJpJ1Rg,2211
437
- uncountablepythonsdk-0.0.184.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
438
- uncountablepythonsdk-0.0.184.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
439
- uncountablepythonsdk-0.0.184.dist-info/RECORD,,
437
+ uncountablepythonsdk-0.0.185.dist-info/METADATA,sha256=E0K3f473FaOotwX30VtrvhiIc-YIbHCdyS2TyY2-pVY,2211
438
+ uncountablepythonsdk-0.0.185.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
439
+ uncountablepythonsdk-0.0.185.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
440
+ uncountablepythonsdk-0.0.185.dist-info/RECORD,,