UncountablePythonSDK 0.0.120__py3-none-any.whl → 0.0.122__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.

docs/conf.py CHANGED
@@ -8,6 +8,11 @@
8
8
 
9
9
  import datetime
10
10
 
11
+ from docutils import nodes # type: ignore[import-untyped]
12
+ from sphinx.addnodes import pending_xref # type: ignore[import-not-found]
13
+ from sphinx.application import Sphinx # type: ignore[import-not-found]
14
+ from sphinx.environment import BuildEnvironment # type: ignore[import-not-found]
15
+
11
16
  project = "Uncountable SDK"
12
17
  copyright = f"{datetime.datetime.now(tz=datetime.UTC).date().year}, Uncountable Inc"
13
18
  author = "Uncountable Inc"
@@ -27,17 +32,18 @@ myst_enable_extensions = ["fieldlist", "deflist"]
27
32
  autoapi_dirs = ["../uncountable"]
28
33
  autoapi_options = [
29
34
  "members",
35
+ "inherited-members",
30
36
  "undoc-members",
31
- "show-inheritance",
32
- "show-module-summary",
33
- "imported-members",
34
37
  ]
38
+ autoapi_root = "api"
35
39
  autoapi_ignore = ["*integration*"]
36
40
  autodoc_typehints = "description"
41
+ autoapi_member_order = "groupwise"
42
+ autoapi_own_page_level = "class"
37
43
 
38
44
  exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
39
45
 
40
-
46
+ python_use_unqualified_type_names = True
41
47
  # -- Options for HTML output -------------------------------------------------
42
48
  # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
43
49
 
@@ -55,3 +61,25 @@ favicons = [
55
61
  "favicons/mstile-150x150.png",
56
62
  "favicons/safari-pinned-tab.svg",
57
63
  ]
64
+
65
+
66
+ def _hook_missing_reference(
67
+ _app: Sphinx, _env: BuildEnvironment, node: pending_xref, contnode: nodes.Text
68
+ ) -> nodes.reference | None:
69
+ """
70
+ Manually resolve reference when autoapi reference resolution fails.
71
+ This is necessary because autoapi does not fully support type aliases.
72
+ """
73
+ target = node.get("reftarget", "")
74
+ if not target.startswith("uncountable"):
75
+ return None
76
+ module, name = target.rsplit(".", 1)
77
+ return nodes.reference(
78
+ text=name if python_use_unqualified_type_names else target,
79
+ children=[contnode],
80
+ refuri=f"/{autoapi_root}/{module.replace('.', '/')}/#{target}",
81
+ )
82
+
83
+
84
+ def setup(app: Sphinx) -> None:
85
+ app.connect("missing-reference", _hook_missing_reference)
docs/index.md CHANGED
@@ -7,7 +7,10 @@
7
7
 
8
8
  ```{toctree}
9
9
  :maxdepth: 2
10
+ :hidden:
10
11
 
11
12
  quickstart
13
+ Available SDK Methods <api/uncountable/core/client/Client>
14
+ SDK Reference <api/uncountable/index>
12
15
  ```
13
16
 
docs/requirements.txt CHANGED
@@ -1,4 +1,4 @@
1
- furo==2024.8.6
1
+ furo==2025.7.19
2
2
  myst-parser==4.0.0
3
3
  sphinx-autoapi==3.6.0
4
4
  sphinx-copybutton==0.5.2
@@ -6,3 +6,4 @@ Sphinx==8.2.0
6
6
  sphinx_design==0.6.1
7
7
  sphinx-favicon==1.0.1
8
8
  astroid==3.3.8
9
+ docutils==0.21.2
@@ -4,6 +4,7 @@ from uncountable.integration.http_server import (
4
4
  GenericHttpRequest,
5
5
  GenericHttpResponse,
6
6
  )
7
+ from uncountable.integration.http_server.types import HttpException
7
8
  from uncountable.integration.job import CustomHttpJob, register_job
8
9
  from uncountable.types import job_definition_t
9
10
 
@@ -14,6 +15,9 @@ class ExampleWebhookPayload:
14
15
  message: str
15
16
 
16
17
 
18
+ _EXPECTED_USER_ID = 1
19
+
20
+
17
21
  @register_job
18
22
  class HttpExample(CustomHttpJob):
19
23
  @staticmethod
@@ -23,7 +27,15 @@ class HttpExample(CustomHttpJob):
23
27
  job_definition: job_definition_t.HttpJobDefinitionBase, # noqa: ARG004
24
28
  profile_meta: job_definition_t.ProfileMetadata, # noqa: ARG004
25
29
  ) -> None:
26
- return None
30
+ if (
31
+ CustomHttpJob.get_validated_oauth_request_user_id(
32
+ request=request, profile_metadata=profile_meta
33
+ )
34
+ != _EXPECTED_USER_ID
35
+ ):
36
+ raise HttpException(
37
+ message="unauthorized; invalid oauth token", error_code=401
38
+ )
27
39
 
28
40
  @staticmethod
29
41
  def handle_request(
@@ -12,6 +12,7 @@ from uncountable.core.async_batch import AsyncBatchProcessor
12
12
  from uncountable.core.client import Client
13
13
  from uncountable.core.environment import get_local_admin_server_port
14
14
  from uncountable.core.file_upload import FileUpload
15
+ from uncountable.core.types import AuthDetailsOAuth
15
16
  from uncountable.integration.http_server import (
16
17
  GenericHttpRequest,
17
18
  GenericHttpResponse,
@@ -93,6 +94,19 @@ class WebhookResponse:
93
94
  pass
94
95
 
95
96
 
97
+ class _RequestValidatorClient(Client):
98
+ def __init__(self, *, base_url: str, oauth_bearer_token: str):
99
+ super().__init__(
100
+ base_url=base_url,
101
+ auth_details=AuthDetailsOAuth(refresh_token=""),
102
+ config=None,
103
+ )
104
+ self._oauth_bearer_token = oauth_bearer_token
105
+
106
+ def _get_oauth_bearer_token(self, *, oauth_details: AuthDetailsOAuth) -> str:
107
+ return self._oauth_bearer_token
108
+
109
+
96
110
  class CustomHttpJob(Job[GenericHttpRequest]):
97
111
  @property
98
112
  def payload_type(self) -> type[GenericHttpRequest]:
@@ -112,6 +126,24 @@ class CustomHttpJob(Job[GenericHttpRequest]):
112
126
  """
113
127
  ...
114
128
 
129
+ @staticmethod
130
+ def get_validated_oauth_request_user_id(
131
+ *, profile_metadata: ProfileMetadata, request: GenericHttpRequest
132
+ ) -> base_t.ObjectId:
133
+ token = request.headers.get("Authorization", "").replace("Bearer ", "")
134
+ if token == "":
135
+ raise HttpException(
136
+ message="unauthorized; no bearer token in request", error_code=401
137
+ )
138
+ return (
139
+ _RequestValidatorClient(
140
+ base_url=profile_metadata.base_url,
141
+ oauth_bearer_token=token,
142
+ )
143
+ .get_current_user_info()
144
+ .user_id
145
+ )
146
+
115
147
  @staticmethod
116
148
  @abstractmethod
117
149
  def handle_request(
@@ -115,6 +115,8 @@ def main() -> None:
115
115
  logger = Logger(get_current_span())
116
116
  processes: list[ProcessInfo] = []
117
117
 
118
+ multiprocessing.set_start_method("forkserver")
119
+
118
120
  def add_process(process: ProcessInfo) -> None:
119
121
  processes.append(process)
120
122
  logger.log_info(f"started process {process.name}")
@@ -41,6 +41,7 @@ from . import field_values_t as field_values_t
41
41
  from . import fields_t as fields_t
42
42
  from . import generic_upload_t as generic_upload_t
43
43
  from .api.recipes import get_column_calculation_values as get_column_calculation_values_t
44
+ from .api.user import get_current_user_info as get_current_user_info_t
44
45
  from .api.recipes import get_curve as get_curve_t
45
46
  from .api.entity import get_entities_data as get_entities_data_t
46
47
  from .api.inputs import get_input_data as get_input_data_t
@@ -160,6 +161,7 @@ __all__: list[str] = [
160
161
  "fields_t",
161
162
  "generic_upload_t",
162
163
  "get_column_calculation_values_t",
164
+ "get_current_user_info_t",
163
165
  "get_curve_t",
164
166
  "get_entities_data_t",
165
167
  "get_input_data_t",
@@ -0,0 +1 @@
1
+ # DO NOT MODIFY -- This file is generated by type_spec
@@ -0,0 +1,40 @@
1
+ # DO NOT MODIFY -- This file is generated by type_spec
2
+ # ruff: noqa: E402 Q003
3
+ # fmt: off
4
+ # isort: skip_file
5
+ from __future__ import annotations
6
+ import typing # noqa: F401
7
+ import datetime # noqa: F401
8
+ from decimal import Decimal # noqa: F401
9
+ import dataclasses
10
+ from pkgs.serialization import serial_class
11
+ from ... import base_t
12
+
13
+ __all__: list[str] = [
14
+ "Arguments",
15
+ "Data",
16
+ "ENDPOINT_METHOD",
17
+ "ENDPOINT_PATH",
18
+ ]
19
+
20
+ ENDPOINT_METHOD = "GET"
21
+ ENDPOINT_PATH = "api/external/user/get_current_user_info"
22
+
23
+
24
+ # DO NOT MODIFY -- This file is generated by type_spec
25
+ @serial_class(
26
+ named_type_path="sdk.api.user.get_current_user_info.Arguments",
27
+ )
28
+ @dataclasses.dataclass(slots=base_t.ENABLE_SLOTS, kw_only=True) # type: ignore[literal-required]
29
+ class Arguments:
30
+ pass
31
+
32
+
33
+ # DO NOT MODIFY -- This file is generated by type_spec
34
+ @serial_class(
35
+ named_type_path="sdk.api.user.get_current_user_info.Data",
36
+ )
37
+ @dataclasses.dataclass(slots=base_t.ENABLE_SLOTS, kw_only=True) # type: ignore[literal-required]
38
+ class Data:
39
+ user_id: base_t.ObjectId
40
+ # DO NOT MODIFY -- This file is generated by type_spec
@@ -37,6 +37,7 @@ from uncountable.types import exports_t
37
37
  from uncountable.types import field_values_t
38
38
  from uncountable.types import generic_upload_t
39
39
  import uncountable.types.api.recipes.get_column_calculation_values as get_column_calculation_values_t
40
+ import uncountable.types.api.user.get_current_user_info as get_current_user_info_t
40
41
  import uncountable.types.api.recipes.get_curve as get_curve_t
41
42
  import uncountable.types.api.entity.get_entities_data as get_entities_data_t
42
43
  import uncountable.types.api.inputs.get_input_data as get_input_data_t
@@ -650,6 +651,17 @@ class ClientMethods(ABC):
650
651
  )
651
652
  return self.do_request(api_request=api_request, return_type=get_column_calculation_values_t.Data)
652
653
 
654
+ def get_current_user_info(
655
+ self,
656
+ ) -> get_current_user_info_t.Data:
657
+ args = get_current_user_info_t.Arguments()
658
+ api_request = APIRequest(
659
+ method=get_current_user_info_t.ENDPOINT_METHOD,
660
+ endpoint=get_current_user_info_t.ENDPOINT_PATH,
661
+ args=args,
662
+ )
663
+ return self.do_request(api_request=api_request, return_type=get_current_user_info_t.Data)
664
+
653
665
  def get_curve(
654
666
  self,
655
667
  *,
@@ -30,4 +30,5 @@ class ListingExportUserTimezone:
30
30
  class ExportType(StrEnum):
31
31
  EXCEL = "excel"
32
32
  PDF = "pdf"
33
+ JSON = "json"
33
34
  # 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.120
3
+ Version: 0.0.122
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
@@ -1,9 +1,9 @@
1
1
  docs/.gitignore,sha256=_ebkZUcwfvfnGEJ95rfj1lxoBNd6EE9ZvtOc7FsbfFE,7
2
- docs/conf.py,sha256=B3WBkqPxlf3xYHXCy91599SJ75G2eGrDs-K_RbsxT5k,1725
3
- docs/index.md,sha256=eEdirX_Ds6ICTRtIS5iT4irCquHcQyKN7E4M5QP9T8A,257
2
+ docs/conf.py,sha256=Qaw_Ys35RUUXKpFyGjiX120kABf6keZIq7iUdsUQxGY,2843
3
+ docs/index.md,sha256=HOQ3H-hErtHlVJMQ-gZjWF_xj1U68Au4aHa_Rc-K8uk,363
4
4
  docs/justfile,sha256=WymCEQ6W2A8Ak79iUPmecmuaUNN2htb7STUrz5K7ELE,273
5
5
  docs/quickstart.md,sha256=3GuJ0MB1O5kjlsrgAmdSkDq0rYqATrYy-tzEHDy8H-c,422
6
- docs/requirements.txt,sha256=IBoo8nKwyuZXoaSX7XOYRJvfT6VjwJPXz49eZvcZGuY,153
6
+ docs/requirements.txt,sha256=Q5qvOf7nQa19R4kCWb_1DBhwW-Vtm3SAtZTPDR_aF9c,171
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
@@ -26,7 +26,7 @@ examples/upload_files.py,sha256=qMaSvMSdTMPOOP55y1AwEurc0SOdZAMvEydlqJPsGpg,432
26
26
  examples/integration-server/pyproject.toml,sha256=-ZZ1R3B-Pf-F6gQX0-Me6u3G9cVW2B2_eechemCe7_4,9149
27
27
  examples/integration-server/jobs/materials_auto/concurrent_cron.py,sha256=xsK3H9ZEaniedC2nJUB0rqOcFI8y-ojfl_nLSJb9AMM,312
28
28
  examples/integration-server/jobs/materials_auto/example_cron.py,sha256=spUMiiTEFaepbVXecjD_4aEEfqEtZGGZuWTKs9J6Xcw,736
29
- examples/integration-server/jobs/materials_auto/example_http.py,sha256=eVq-Fss_AhmztxOMqqO-GYGF3KvPt1O5HbNwwC2arh8,1037
29
+ examples/integration-server/jobs/materials_auto/example_http.py,sha256=eIL46ElWo8SKY7W5JWWkwZk6Qo7KRd9EJBxfy7YQ_sE,1429
30
30
  examples/integration-server/jobs/materials_auto/example_instrument.py,sha256=czJF3qBFay1S8fuESOvmkvBv1wCtZGAlHjwvCyYr-Mw,2336
31
31
  examples/integration-server/jobs/materials_auto/example_runsheet_wh.py,sha256=_wILTnbzzLf9zrcQb_KQKytxxcya1ej6MqQnoUSS4fA,1180
32
32
  examples/integration-server/jobs/materials_auto/example_wh.py,sha256=PN-skP27yJwDZboWk5g5EZEc3AKfVayQLfnopjsDKJc,659
@@ -105,9 +105,9 @@ uncountable/integration/cli.py,sha256=eshJ0lv4u00UOFrlkCwamE8NaxX1qN2q79wCJcsFrJ
105
105
  uncountable/integration/construct_client.py,sha256=I53mGcdS88hba3HFwgXmWQaTd1d5u0jWNSwyc_vlVsQ,1937
106
106
  uncountable/integration/cron.py,sha256=6eH-kIs3sdYPCyb62_L2M7U_uQTdMTdwY5hreEJb0hw,887
107
107
  uncountable/integration/entrypoint.py,sha256=BHOYPQgKvZE6HG8Rv15MkdYl8lRkvfDgv1OdLo0oQ9Q,433
108
- uncountable/integration/job.py,sha256=X8mNoy01Q6h26eNuPi50XwV6YLgaqYCGWt2PFDEddZU,7111
108
+ uncountable/integration/job.py,sha256=HFYA3YxqwyCvQLqXpMnKxp2IJUjFgjMsWVz_DTb_5eo,8229
109
109
  uncountable/integration/scan_profiles.py,sha256=RHBmPc5E10YZzf4cmglwrn2yAy0jHBhQ-P_GlAk2TeU,2919
110
- uncountable/integration/scheduler.py,sha256=t75ANJN21DElUFvEdtgueTluF7y17jTtBDDF8f3NRDM,4812
110
+ uncountable/integration/scheduler.py,sha256=KK-1XCr8Rxi8puaynb3H0BySvsDBJJaPcGumy49ZMB8,4864
111
111
  uncountable/integration/server.py,sha256=lL9zmzqkQRf7V1fBT20SvIy-7ryz5hFf7DF4QX4pj1E,4699
112
112
  uncountable/integration/telemetry.py,sha256=VunRaMC9ykPaxUE_s6SarQieKrGNtTSyAr9omc315OI,7419
113
113
  uncountable/integration/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -141,7 +141,7 @@ uncountable/integration/queue_runner/datastore/model.py,sha256=8-RI5A2yPZVGBLWIN
141
141
  uncountable/integration/secret_retrieval/__init__.py,sha256=3QXVj35w8rRMxVvmmsViFYDi3lcb3g70incfalOEm6o,87
142
142
  uncountable/integration/secret_retrieval/retrieve_secret.py,sha256=LBEf18KHtXZxg-ZZ80stJ1vW39AWf0CQllP6pNu3Eq8,2994
143
143
  uncountable/integration/webhook_server/entrypoint.py,sha256=NQawXl_JCRojdVniS5RF7dobQQKW_Wy03bwy-uXknuA,3441
144
- uncountable/types/__init__.py,sha256=EZC_LgnO80PfE3SW4Sg95Ew3iUORmT_7oz-6u4zTulY,10169
144
+ uncountable/types/__init__.py,sha256=ubEQxcYFowfwzlbHf_zavI2EZOAm3sM5CZGfnAJRUMM,10271
145
145
  uncountable/types/async_batch.py,sha256=yCCWrrLQfxXVqZp-KskxLBNkNmuELdz4PJjx8ULppgs,662
146
146
  uncountable/types/async_batch_processor.py,sha256=h_8Snzt3lbEFlZAZFByt4Hg4dv2YlxMijHjTHjZ0aXY,22062
147
147
  uncountable/types/async_batch_t.py,sha256=JuswurXlYW38MfAXJ0UWb7hE2rmzFaHBAsNhRYAyMD4,3779
@@ -155,7 +155,7 @@ uncountable/types/calculations.py,sha256=fApOFpgBemt_t7IVneVR0VdI3X5EOxiG6Xhzr6R
155
155
  uncountable/types/calculations_t.py,sha256=pl-lhjyDQuj11Sf9g1-0BsSkN7Ez8UxDp8-KMQ_3enM,709
156
156
  uncountable/types/chemical_structure.py,sha256=ujyragaD26-QG5jgKnWhO7TN3N1V9b_04T2WhqNYxxo,281
157
157
  uncountable/types/chemical_structure_t.py,sha256=VFFyits_vx4t5L2euu_qFiSpsGJjURkDPr3ISnr3nPc,855
158
- uncountable/types/client_base.py,sha256=Qd8kxA0C76FKTAuoJOVjEw48mInfV_IXH2CBBTyYwAs,76382
158
+ uncountable/types/client_base.py,sha256=9osmz1wYM9PpqUSrWKHOYjwqNC6pOL6RVsIXIq_niW4,76886
159
159
  uncountable/types/client_config.py,sha256=qLpHt4O_B098CyN6qQajoxZ2zjZ1DILXLUEGyyGP0TQ,280
160
160
  uncountable/types/client_config_t.py,sha256=yTFIYAitMrcc4oV9J-HADODS_Hwi45z-piz7rr7QT04,781
161
161
  uncountable/types/curves.py,sha256=QyEyC20jsG-LGKVx6miiF-w70vKMwNkILFBDIJ5Ok9g,345
@@ -167,7 +167,7 @@ uncountable/types/entity_t.py,sha256=cULJs6qZAWnN4U8pZadcsf9A5KmlLTkIlwW_sQf627I
167
167
  uncountable/types/experiment_groups.py,sha256=qUpFOx1AKgzaT_4khCOv5Xs6jwiQGbvHH-GUh3v1nv4,288
168
168
  uncountable/types/experiment_groups_t.py,sha256=29Ct-WPejpYMuGfnFfOoosU9iSfjzxpabpBX6oTPFUA,761
169
169
  uncountable/types/exports.py,sha256=VMmxUO2PpV1Y63hZ2AnVor4H-B6aswJ7YpSru_u89lU,334
170
- uncountable/types/exports_t.py,sha256=der2gk1YL5XjWTrqsLD2KNynXA_z7IzmvphOfvGT19M,894
170
+ uncountable/types/exports_t.py,sha256=p_ub9Ltk6bGE4CCe07Mfz7y4-uDMCIo5_jZr1l55zsE,912
171
171
  uncountable/types/field_values.py,sha256=iG4TvITLnlz023GuhFrlDwXB7oov5DPpAs_FBaMaJR8,1713
172
172
  uncountable/types/field_values_t.py,sha256=Br2D2dibU9avbomfkaXHXw1ineUcIkATBbEm0eZm1SE,10076
173
173
  uncountable/types/fields.py,sha256=M0_ZZr0QdNLXkdHAGo5mfU90kEtHedCSKrcod-FG30Y,245
@@ -324,7 +324,9 @@ uncountable/types/api/triggers/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr
324
324
  uncountable/types/api/triggers/run_trigger.py,sha256=dgDX_sRWSJ36UuzMZhG25oHV1HIOUKYY2G3fjKugZrw,1204
325
325
  uncountable/types/api/uploader/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
326
326
  uncountable/types/api/uploader/invoke_uploader.py,sha256=Bj7Dq4A90k00suacwk3bLA_dCb2aovS1kAbVam2AQnM,1395
327
- uncountablepythonsdk-0.0.120.dist-info/METADATA,sha256=v5j4j9nJrkLYfdm8h9Q-nEIwqmFiDSWSAGGlWUAO8bw,2174
328
- uncountablepythonsdk-0.0.120.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
329
- uncountablepythonsdk-0.0.120.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
330
- uncountablepythonsdk-0.0.120.dist-info/RECORD,,
327
+ uncountable/types/api/user/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
328
+ uncountable/types/api/user/get_current_user_info.py,sha256=Avqi_RXtRgbefrT_dwJ9MrO6eDNSSa_Nu650FSuESlg,1109
329
+ uncountablepythonsdk-0.0.122.dist-info/METADATA,sha256=KCzmpzYEmy6LpOZxLn9tvAVP2Q7H3UE5KeRynwvn9rM,2174
330
+ uncountablepythonsdk-0.0.122.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
331
+ uncountablepythonsdk-0.0.122.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
332
+ uncountablepythonsdk-0.0.122.dist-info/RECORD,,