UncountablePythonSDK 0.0.166__py3-none-any.whl → 0.0.168__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.
@@ -426,9 +426,11 @@ def _build_parser_inner(
426
426
  return inner
427
427
 
428
428
  v_parser = _build_parser_inner(args[1], context, type_var_map)
429
- return lambda value: origin(
430
- (key_parser(k), v_parser(v)) for k, v in value.items()
431
- )
429
+
430
+ def parse_dict(value: typing.Any) -> typing.Any:
431
+ return origin((key_parser(k), v_parser(v)) for k, v in value.items())
432
+
433
+ return parse_dict
432
434
 
433
435
  if origin == typing.Literal:
434
436
  valid_values: set[T] = set(typing.get_args(parsed_type))
@@ -67,6 +67,7 @@ class SFTPSession(FileSystemSession):
67
67
  def __init__(self, sftp_config: FileSystemSFTPConfig) -> None:
68
68
  super().__init__()
69
69
  self.host: str = sftp_config.ip
70
+ self.port: int = sftp_config.port
70
71
  self.username: str = sftp_config.username
71
72
  self.key_file: str | paramiko.PKey | None = (
72
73
  sftp_config.pem_path
@@ -81,6 +82,7 @@ class SFTPSession(FileSystemSession):
81
82
  if self.key_file is not None:
82
83
  self.connection = pysftp.Connection(
83
84
  self.host,
85
+ port=self.port,
84
86
  username=self.username,
85
87
  private_key=self.key_file,
86
88
  cnopts=cnopts,
@@ -88,6 +90,7 @@ class SFTPSession(FileSystemSession):
88
90
  elif self.password is not None:
89
91
  self.connection = pysftp.Connection(
90
92
  self.host,
93
+ port=self.port,
91
94
  username=self.username,
92
95
  password=self.password,
93
96
  cnopts=cnopts,
@@ -65,6 +65,7 @@ class FileSystemSFTPConfig:
65
65
  password: str | None = None
66
66
  valid_extensions: tuple[str] | None = None
67
67
  recursive: bool = True
68
+ port: int = 22
68
69
 
69
70
 
70
71
  @dataclass(kw_only=True)
@@ -264,7 +264,7 @@ class Client(ClientMethods):
264
264
  }
265
265
  with push_scope_optional(self._cfg.logger, "api_call", attributes=attributes):
266
266
  if self._cfg.logger is not None:
267
- self._cfg.logger.log_info(api_request.endpoint, attributes=attributes)
267
+ self._cfg.logger.log_debug(api_request.endpoint, attributes=attributes)
268
268
  timeout = (
269
269
  api_request.request_options.timeout_secs
270
270
  if api_request.request_options is not None
@@ -142,7 +142,7 @@ class FileUploader:
142
142
  self._logger, "upload_file", attributes=attributes
143
143
  ):
144
144
  if self._logger is not None:
145
- self._logger.log_info("Uploading file", attributes=attributes)
145
+ self._logger.log_debug("Uploading file", attributes=attributes)
146
146
  with file_upload_data(file_upload) as file_bytes:
147
147
  if file_bytes.bytes_data.read(1) == b"":
148
148
  raise UploadFailed(
@@ -1,11 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
- import time
5
4
  from copy import copy
6
5
  from dataclasses import make_dataclass
7
6
  from decimal import Decimal
8
- from typing import Self
7
+ from typing import Callable, Self
9
8
 
10
9
  from pkgs.argument_parser import CachedParser
11
10
  from pkgs.serialization.serial_class import serial_class
@@ -29,8 +28,7 @@ from uncountable.types import (
29
28
  from uncountable.types.api.entity import list_entities as list_entities_t
30
29
  from uncountable.types.api.files.download_file import FileDownloadQueryEntityField
31
30
 
32
- _ASYNC_EXPORT_TIMEOUT_SECONDS = 60 * 10
33
- _ASYNC_EXPORT_POLL_INTERVAL_SECONDS = 1
31
+ _FETCH_ROW_LIMIT = 100
34
32
 
35
33
 
36
34
  class _AsyncExportJob(QueryRow):
@@ -99,7 +97,9 @@ class QueryBuilder[QueryRowT: QueryRow]:
99
97
  )
100
98
  return branch
101
99
 
102
- def _fetch(self, limit: int = 100, offset: int = 0) -> list[QueryRowT]:
100
+ def _fetch(self, limit: int, offset: int = 0) -> list[QueryRowT]:
101
+ if limit > _FETCH_ROW_LIMIT:
102
+ raise ValueError(f"Fetch row limit cannot exceed {_FETCH_ROW_LIMIT}")
103
103
  rows = self._client.fetch_listing(
104
104
  entity_type=self._base_type,
105
105
  columns=list(self._column_identifiers),
@@ -107,7 +107,6 @@ class QueryBuilder[QueryRowT: QueryRow]:
107
107
  limit=limit,
108
108
  offset=offset,
109
109
  ).results
110
-
111
110
  return [self._parse_row(row.column_values) for row in rows]
112
111
 
113
112
  def _hook_after_parse_row(self, row: QueryRowT) -> None:
@@ -124,13 +123,13 @@ class QueryBuilder[QueryRowT: QueryRow]:
124
123
  return parsed_model
125
124
 
126
125
  def one(self) -> QueryRowT:
127
- results = self._fetch()
126
+ results = self._fetch(limit=2)
128
127
  if len(results) != 1:
129
128
  raise ValueError(f"Expected one result, received {len(results)}")
130
129
  return results[0]
131
130
 
132
131
  def one_or_none(self) -> QueryRowT | None:
133
- results = self._fetch()
132
+ results = self._fetch(limit=2)
134
133
  if len(results) > 1:
135
134
  raise ValueError(f"Expected one result, received {len(results)}")
136
135
  if len(results) == 0:
@@ -142,59 +141,74 @@ class QueryBuilder[QueryRowT: QueryRow]:
142
141
  return None if len(results) == 0 else results[0]
143
142
 
144
143
  def all(self) -> list[QueryRowT]:
145
- results = self._fetch()
146
- if len(results) < 100:
144
+ results = self._fetch(limit=_FETCH_ROW_LIMIT)
145
+ if len(results) < _FETCH_ROW_LIMIT:
147
146
  return results
148
- return self._await_async_export()
149
147
 
150
- def _await_async_export(self) -> list[QueryRowT]:
148
+ # Generate an async listing export if the number of results >= 100
149
+ poll_for_async_export_results = self._submit_async_export()
150
+
151
+ # Resolve exponentially increasing pagination batches until all rows are fetched or the async job completes
152
+ iteration_count: int = 0
153
+ while True:
154
+ for _ in range(2**iteration_count):
155
+ rows = self._fetch(limit=_FETCH_ROW_LIMIT, offset=len(results))
156
+ results.extend(rows)
157
+ if len(rows) < _FETCH_ROW_LIMIT:
158
+ return results
159
+
160
+ if export_results := poll_for_async_export_results():
161
+ return export_results
162
+ iteration_count += 1
163
+
164
+ def _submit_async_export(self) -> Callable[[], list[QueryRowT] | None]:
165
+ """
166
+ Requests an async export of the query results. Returns a closure for polling the results of the export.
167
+ """
151
168
  async_job_entity = self._client.export_listing(
152
169
  entity_type=self._base_type,
153
170
  columns=list(self._column_identifiers),
154
171
  filters=listing_t.FilterNodeColumnAnd(filters=tuple(self._filters)),
155
172
  ).entity
156
173
  if async_job_entity is None:
157
- raise ValueError("Listing export failed to create async job")
158
-
159
- status_query = QueryBuilder(client=self._client, model=_AsyncExportJob)
160
-
161
- start_time = time.time()
162
- while time.time() - start_time < _ASYNC_EXPORT_TIMEOUT_SECONDS:
163
- match (
164
- status_query
174
+ raise ValueError("async listing export generation failed")
175
+ async_job_status: async_jobs_t.AsyncJobStatus | str | None = None
176
+
177
+ def poll_for_results() -> list[QueryRowT] | None:
178
+ nonlocal async_job_status
179
+ if async_job_status == async_jobs_t.AsyncJobStatus.ERROR:
180
+ return None
181
+ async_job_status = (
182
+ QueryBuilder(client=self._client, model=_AsyncExportJob)
165
183
  .filter(_AsyncExportJob.id == async_job_entity.id)
166
184
  .one()
167
185
  .status
168
- ):
169
- case async_jobs_t.AsyncJobStatus.COMPLETED:
170
- export_file = self._client.download_files(
171
- file_query=FileDownloadQueryEntityField(
172
- entity=entity_t.EntityIdentifier(
173
- identifier_key=identifier_t.IdentifierKeyId(
174
- id=async_job_entity.id
175
- ),
176
- type=entity_t.EntityType.ASYNC_JOB,
177
- ),
178
- field_key=identifier_t.IdentifierKeyRefName(
179
- ref_name="unc_async_listing_export_file"
186
+ )
187
+ if async_job_status == async_jobs_t.AsyncJobStatus.COMPLETED:
188
+ export_file = self._client.download_files(
189
+ file_query=FileDownloadQueryEntityField(
190
+ entity=entity_t.EntityIdentifier(
191
+ identifier_key=identifier_t.IdentifierKeyId(
192
+ id=async_job_entity.id
180
193
  ),
194
+ type=entity_t.EntityType.ASYNC_JOB,
195
+ ),
196
+ field_key=identifier_t.IdentifierKeyRefName(
197
+ ref_name="unc_async_listing_export_file"
198
+ ),
199
+ )
200
+ )[0].data.read()
201
+ return [
202
+ self._parse_row(row.column_values)
203
+ for row in CachedParser(list_entities_t.Data)
204
+ .parse_api(
205
+ json.loads(
206
+ export_file,
207
+ parse_float=Decimal,
181
208
  )
182
- )[0].data.read()
183
-
184
- return [
185
- self._parse_row(row.column_values)
186
- for row in CachedParser(list_entities_t.Data)
187
- .parse_api(
188
- json.loads(
189
- export_file,
190
- parse_float=Decimal,
191
- )
192
- )
193
- .results
194
- ]
195
- case async_jobs_t.AsyncJobStatus.ERROR:
196
- raise ValueError("Listing export failed")
197
- time.sleep(_ASYNC_EXPORT_POLL_INTERVAL_SECONDS)
198
- raise TimeoutError(
199
- f"Async listing export timed out after {_ASYNC_EXPORT_TIMEOUT_SECONDS} seconds"
200
- )
209
+ )
210
+ .results
211
+ ]
212
+ return None
213
+
214
+ return poll_for_results
@@ -93,7 +93,7 @@ def execute_job(
93
93
  job = resolve_executor(job_definition.executor, profile_metadata)
94
94
  job_location = f"{job.__class__.__module__}.{job.__class__.__name__}"
95
95
 
96
- job_logger.log_info(f"Starting job: `{job_location}`")
96
+ job_logger.log_debug(f"Starting job: `{job_location}`")
97
97
 
98
98
  run_entity: entity_t.Entity | None = None
99
99
  try:
@@ -115,7 +115,7 @@ def execute_job(
115
115
  result = job_definition_t.JobResult(success=False)
116
116
 
117
117
  submitted_batch_job_ids = args.batch_processor.get_submitted_job_ids()
118
- job_logger.log_info(
118
+ job_logger.log_debug(
119
119
  f"Completed job: `{job_location}` (success={result.success})",
120
120
  attributes={
121
121
  "submitted_batch_job_ids": submitted_batch_job_ids,
@@ -136,7 +136,7 @@ async def start_scheduler(
136
136
  job_payload=payload,
137
137
  job_ref_name=job_ref_name,
138
138
  )
139
- logger.log_info(
139
+ logger.log_debug(
140
140
  "Job submitted successfully.",
141
141
  attributes={"job.queued_job_uuid": queued_job.queued_job_uuid},
142
142
  )
@@ -168,7 +168,7 @@ async def start_scheduler(
168
168
  )
169
169
  )
170
170
 
171
- logger.log_info(
171
+ logger.log_debug(
172
172
  "Job cancelled successfully.",
173
173
  attributes={"job.queued_job_uuid": command.queued_job_uuid},
174
174
  )
@@ -201,10 +201,10 @@ async def start_scheduler(
201
201
  def _handle_vaccuum_queued_jobs_command(
202
202
  command: CommandVaccuumQueuedJobs,
203
203
  ) -> None:
204
- logger.log_info("Vaccuuming queued jobs...")
204
+ logger.log_debug("Vaccuuming queued jobs...")
205
205
  deleted_uuids = datastore.vaccuum_queued_jobs()
206
206
 
207
- logger.log_info(
207
+ logger.log_debug(
208
208
  "Queued jobs vacuumed successfully.",
209
209
  attributes={"job.queued_job_uuids": deleted_uuids},
210
210
  )
@@ -71,7 +71,7 @@ ProcessAlarm = ProcessAlarmRestart | ProcessAlarmShutdownAll
71
71
 
72
72
 
73
73
  def handle_shutdown(logger: Logger, processes: dict[ProcessName, ProcessInfo]) -> None:
74
- logger.log_info("received shutdown command, shutting down sub-processes")
74
+ logger.log_debug("received shutdown command, shutting down sub-processes")
75
75
  for proc_info in processes.values():
76
76
  if proc_info.is_alive:
77
77
  proc_info.process.terminate()
@@ -83,7 +83,7 @@ def handle_shutdown(logger: Logger, processes: dict[ProcessName, ProcessInfo]) -
83
83
  and len(still_living_processes) > 0
84
84
  ):
85
85
  current_loop_processes = [*still_living_processes]
86
- logger.log_info(
86
+ logger.log_debug(
87
87
  "waiting for sub-processes to shut down",
88
88
  attributes={
89
89
  "still_living_processes": [
@@ -94,7 +94,7 @@ def handle_shutdown(logger: Logger, processes: dict[ProcessName, ProcessInfo]) -
94
94
  still_living_processes = []
95
95
  for proc_info in current_loop_processes:
96
96
  if not proc_info.is_alive:
97
- logger.log_info(f"{proc_info.name} shut down successfully")
97
+ logger.log_debug(f"{proc_info.name} shut down successfully")
98
98
  else:
99
99
  still_living_processes.append(proc_info)
100
100
  time.sleep(1)
@@ -121,7 +121,7 @@ def restart_process(
121
121
  processes[ProcessName.QUEUE_RUNNER] = new_info
122
122
  try:
123
123
  _wait_queue_runner_online()
124
- logger.log_info("queue runner restarted successfully")
124
+ logger.log_debug("queue runner restarted successfully")
125
125
  except Exception as e:
126
126
  logger.log_exception(e)
127
127
  logger.log_error(
@@ -135,13 +135,13 @@ def restart_process(
135
135
  cron_proc.start()
136
136
  new_info = ProcessInfo(name=ProcessName.CRON_SERVER, process=cron_proc)
137
137
  processes[ProcessName.CRON_SERVER] = new_info
138
- logger.log_info("cron server restarted successfully")
138
+ logger.log_debug("cron server restarted successfully")
139
139
 
140
140
  case ProcessName.UWSGI:
141
141
  uwsgi_proc: AnyProcess = subprocess.Popen(["uwsgi", "--die-on-term"])
142
142
  new_info = ProcessInfo(name=ProcessName.UWSGI, process=uwsgi_proc)
143
143
  processes[ProcessName.UWSGI] = new_info
144
- logger.log_info("uwsgi restarted successfully")
144
+ logger.log_debug("uwsgi restarted successfully")
145
145
 
146
146
 
147
147
  def check_process_alarms(
@@ -188,7 +188,7 @@ def main() -> None:
188
188
 
189
189
  def add_process(process: ProcessInfo) -> None:
190
190
  processes[process.name] = process
191
- logger.log_info(f"started process {process.name}")
191
+ logger.log_debug(f"started process {process.name}")
192
192
 
193
193
  def _start_queue_runner() -> None:
194
194
  runner_process = multiprocessing.Process(target=start_queue_runner)
@@ -94,6 +94,7 @@ def get_otel_logger() -> OTELLogger:
94
94
 
95
95
 
96
96
  class LogSeverity(StrEnum):
97
+ DEBUG = "Debug"
97
98
  INFO = "Info"
98
99
  WARN = "Warn"
99
100
  ERROR = "Error"
@@ -102,6 +103,8 @@ class LogSeverity(StrEnum):
102
103
  def _get_severity_number(severity: LogSeverity) -> _logs.SeverityNumber:
103
104
  """Map LogSeverity to OpenTelemetry SeverityNumber for Datadog."""
104
105
  match severity:
106
+ case LogSeverity.DEBUG:
107
+ return _logs.SeverityNumber.DEBUG
105
108
  case LogSeverity.INFO:
106
109
  return _logs.SeverityNumber.INFO
107
110
  case LogSeverity.WARN:
@@ -172,6 +175,13 @@ class Logger:
172
175
  def bind(self, *, attributes: Attributes) -> None:
173
176
  self._bound_attributes.update(attributes)
174
177
 
178
+ def log_debug(self, message: str, *, attributes: Attributes | None = None) -> None:
179
+ self._emit_log(
180
+ message=message, severity=LogSeverity.DEBUG, attributes=attributes
181
+ )
182
+
183
+ # Use log_info for user-facing job logs (e.g. example_* and custom executor output).
184
+ # SDK-internal instrumentation should use log_debug instead.
175
185
  def log_info(self, message: str, *, attributes: Attributes | None = None) -> None:
176
186
  self._emit_log(
177
187
  message=message, severity=LogSeverity.INFO, attributes=attributes
@@ -268,7 +278,7 @@ class PerJobResourceTracker:
268
278
  self.max_rss = max(self.max_rss, rss)
269
279
  now = time.monotonic()
270
280
  if now - last_log_time >= self.log_interval:
271
- self.logger.log_info(
281
+ self.logger.log_debug(
272
282
  "Job resource usage (periodic)",
273
283
  attributes=self._current_stats(),
274
284
  )
@@ -310,7 +320,7 @@ class PerJobResourceTracker:
310
320
  ),
311
321
  )
312
322
  return
313
- self.logger.log_info("Job resource usage summary", attributes=stats)
323
+ self.logger.log_debug("Job resource usage summary", attributes=stats)
314
324
  return
315
325
 
316
326
  def summary(self) -> Attributes:
@@ -91,7 +91,7 @@ def register_route(
91
91
  methods=["POST"],
92
92
  )
93
93
 
94
- server_logger.log_info(
94
+ server_logger.log_debug(
95
95
  f"job {job.id} webhook registered at: {route}", attributes=log_attributes
96
96
  )
97
97
 
@@ -20,6 +20,7 @@ from .api.recipes import clear_recipe_outputs as clear_recipe_outputs_t
20
20
  from . import client_config_t as client_config_t
21
21
  from .api.uploader import complete_async_parse as complete_async_parse_t
22
22
  from .api.runsheet import complete_async_upload as complete_async_upload_t
23
+ from . import condition_match_t as condition_match_t
23
24
  from .api.chemical import convert_chemical_formats as convert_chemical_formats_t
24
25
  from .api.entity import create_entities as create_entities_t
25
26
  from .api.entity import create_entity as create_entity_t
@@ -145,6 +146,7 @@ from .api.material_families import update_entity_material_families as update_ent
145
146
  from .api.outputs import update_output_condition_parameter as update_output_condition_parameter_t
146
147
  from . import uploader_t as uploader_t
147
148
  from .api.condition_parameters import upsert_condition_match as upsert_condition_match_t
149
+ from .api.condition_parameters import upsert_condition_matches as upsert_condition_matches_t
148
150
  from .api.field_options import upsert_field_options as upsert_field_options_t
149
151
  from .api.recipes import upsert_recipe_workflow_step as upsert_recipe_workflow_step_t
150
152
  from .api.recipes import upsert_step_relationships as upsert_step_relationships_t
@@ -172,6 +174,7 @@ __all__: list[str] = [
172
174
  "client_config_t",
173
175
  "complete_async_parse_t",
174
176
  "complete_async_upload_t",
177
+ "condition_match_t",
175
178
  "convert_chemical_formats_t",
176
179
  "create_entities_t",
177
180
  "create_entity_t",
@@ -297,6 +300,7 @@ __all__: list[str] = [
297
300
  "update_output_condition_parameter_t",
298
301
  "uploader_t",
299
302
  "upsert_condition_match_t",
303
+ "upsert_condition_matches_t",
300
304
  "upsert_field_options_t",
301
305
  "upsert_recipe_workflow_step_t",
302
306
  "upsert_step_relationships_t",
@@ -6,11 +6,12 @@ from __future__ import annotations
6
6
  import typing # noqa: F401
7
7
  import datetime # noqa: F401
8
8
  from decimal import Decimal # noqa: F401
9
- from enum import StrEnum
10
9
  import dataclasses
11
10
  from pkgs.serialization import serial_class
11
+ from pkgs.serialization import serial_alias_annotation
12
12
  from ... import async_batch_t
13
13
  from ... import base_t
14
+ from ... import condition_match_t
14
15
  from ... import identifier_t
15
16
 
16
17
  __all__: list[str] = [
@@ -42,11 +43,12 @@ class ConditionParameter:
42
43
 
43
44
 
44
45
  # DO NOT MODIFY -- This file is generated by type_spec
45
- class MatchType(StrEnum):
46
- ALL = "all"
47
- EQ = "eq"
48
- EQCATEGORICAL = "eqCategorical"
49
- RANGE = "range"
46
+ MatchType = typing.Annotated[
47
+ condition_match_t.MatchType,
48
+ serial_alias_annotation(
49
+ named_type_path="sdk.api.condition_parameters.upsert_condition_match.MatchType",
50
+ ),
51
+ ]
50
52
 
51
53
 
52
54
  # DO NOT MODIFY -- This file is generated by type_spec
@@ -0,0 +1,161 @@
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 pkgs.serialization import serial_union_annotation
12
+ from ... import async_batch_t
13
+ from ... import base_t
14
+ from ... import condition_match_t
15
+ from ... import identifier_t
16
+
17
+ __all__: list[str] = [
18
+ "Arguments",
19
+ "ConditionParameter",
20
+ "ConditionParameterAll",
21
+ "ConditionParameterEqCategorical",
22
+ "ConditionParameterEqNumeric",
23
+ "ConditionParameterRange",
24
+ "Data",
25
+ "ENDPOINT_METHOD",
26
+ "ENDPOINT_PATH",
27
+ "UpsertArgs",
28
+ "UpsertArgsOutputConditions",
29
+ "UpsertArgsParameters",
30
+ ]
31
+
32
+ ENDPOINT_METHOD = "POST"
33
+ ENDPOINT_PATH = "api/external/condition_parameters/upsert_condition_matches"
34
+
35
+
36
+ # DO NOT MODIFY -- This file is generated by type_spec
37
+ @serial_class(
38
+ named_type_path="sdk.api.condition_parameters.upsert_condition_matches.ConditionParameterAll",
39
+ parse_require={"match_type"},
40
+ )
41
+ @dataclasses.dataclass(slots=base_t.ENABLE_SLOTS, kw_only=True) # type: ignore[literal-required]
42
+ class ConditionParameterAll:
43
+ condition_parameter_key: identifier_t.IdentifierKey
44
+ allow_null: bool
45
+ match_type: typing.Literal[condition_match_t.MatchType.ALL] = condition_match_t.MatchType.ALL
46
+
47
+
48
+ # DO NOT MODIFY -- This file is generated by type_spec
49
+ @serial_class(
50
+ named_type_path="sdk.api.condition_parameters.upsert_condition_matches.ConditionParameterEqNumeric",
51
+ parse_require={"match_type"},
52
+ )
53
+ @dataclasses.dataclass(slots=base_t.ENABLE_SLOTS, kw_only=True) # type: ignore[literal-required]
54
+ class ConditionParameterEqNumeric:
55
+ condition_parameter_key: identifier_t.IdentifierKey
56
+ allow_null: bool
57
+ numeric_values: list[Decimal | None]
58
+ match_type: typing.Literal[condition_match_t.MatchType.EQ] = condition_match_t.MatchType.EQ
59
+
60
+
61
+ # DO NOT MODIFY -- This file is generated by type_spec
62
+ @serial_class(
63
+ named_type_path="sdk.api.condition_parameters.upsert_condition_matches.ConditionParameterEqCategorical",
64
+ parse_require={"match_type"},
65
+ )
66
+ @dataclasses.dataclass(slots=base_t.ENABLE_SLOTS, kw_only=True) # type: ignore[literal-required]
67
+ class ConditionParameterEqCategorical:
68
+ condition_parameter_key: identifier_t.IdentifierKey
69
+ allow_null: bool
70
+ categorical_values: list[str | None]
71
+ match_type: typing.Literal[condition_match_t.MatchType.EQCATEGORICAL] = condition_match_t.MatchType.EQCATEGORICAL
72
+
73
+
74
+ # DO NOT MODIFY -- This file is generated by type_spec
75
+ @serial_class(
76
+ named_type_path="sdk.api.condition_parameters.upsert_condition_matches.ConditionParameterRange",
77
+ to_string_values={"max_value", "min_value"},
78
+ parse_require={"match_type"},
79
+ )
80
+ @dataclasses.dataclass(slots=base_t.ENABLE_SLOTS, kw_only=True) # type: ignore[literal-required]
81
+ class ConditionParameterRange:
82
+ condition_parameter_key: identifier_t.IdentifierKey
83
+ allow_null: bool
84
+ min_value: Decimal
85
+ max_value: Decimal
86
+ match_type: typing.Literal[condition_match_t.MatchType.RANGE] = condition_match_t.MatchType.RANGE
87
+
88
+
89
+ # DO NOT MODIFY -- This file is generated by type_spec
90
+ ConditionParameter = typing.Annotated[
91
+ ConditionParameterAll | ConditionParameterEqNumeric | ConditionParameterEqCategorical | ConditionParameterRange,
92
+ serial_union_annotation(
93
+ named_type_path="sdk.api.condition_parameters.upsert_condition_matches.ConditionParameter",
94
+ discriminator="match_type",
95
+ discriminator_map={
96
+ "all": ConditionParameterAll,
97
+ "eq": ConditionParameterEqNumeric,
98
+ "eqCategorical": ConditionParameterEqCategorical,
99
+ "range": ConditionParameterRange,
100
+ },
101
+ ),
102
+ ]
103
+
104
+
105
+ # DO NOT MODIFY -- This file is generated by type_spec
106
+ @serial_class(
107
+ named_type_path="sdk.api.condition_parameters.upsert_condition_matches.UpsertArgsParameters",
108
+ parse_require={"kind"},
109
+ )
110
+ @dataclasses.dataclass(slots=base_t.ENABLE_SLOTS, kw_only=True) # type: ignore[literal-required]
111
+ class UpsertArgsParameters:
112
+ condition_parameters: list[ConditionParameter]
113
+ kind: typing.Literal["condition_parameters"] = "condition_parameters"
114
+ name: str | None = None
115
+ existing_condition_match_key: identifier_t.IdentifierKey | None = None
116
+
117
+
118
+ # DO NOT MODIFY -- This file is generated by type_spec
119
+ @serial_class(
120
+ named_type_path="sdk.api.condition_parameters.upsert_condition_matches.UpsertArgsOutputConditions",
121
+ parse_require={"kind"},
122
+ )
123
+ @dataclasses.dataclass(slots=base_t.ENABLE_SLOTS, kw_only=True) # type: ignore[literal-required]
124
+ class UpsertArgsOutputConditions:
125
+ output_condition_keys: list[identifier_t.IdentifierKey]
126
+ kind: typing.Literal["output_conditions"] = "output_conditions"
127
+ name: str | None = None
128
+ existing_condition_match_key: identifier_t.IdentifierKey | None = None
129
+
130
+
131
+ # DO NOT MODIFY -- This file is generated by type_spec
132
+ UpsertArgs = typing.Annotated[
133
+ UpsertArgsParameters | UpsertArgsOutputConditions,
134
+ serial_union_annotation(
135
+ named_type_path="sdk.api.condition_parameters.upsert_condition_matches.UpsertArgs",
136
+ discriminator="kind",
137
+ discriminator_map={
138
+ "condition_parameters": UpsertArgsParameters,
139
+ "output_conditions": UpsertArgsOutputConditions,
140
+ },
141
+ ),
142
+ ]
143
+
144
+
145
+ # DO NOT MODIFY -- This file is generated by type_spec
146
+ @serial_class(
147
+ named_type_path="sdk.api.condition_parameters.upsert_condition_matches.Arguments",
148
+ )
149
+ @dataclasses.dataclass(slots=base_t.ENABLE_SLOTS, kw_only=True) # type: ignore[literal-required]
150
+ class Arguments:
151
+ upserts: list[UpsertArgs]
152
+
153
+
154
+ # DO NOT MODIFY -- This file is generated by type_spec
155
+ @serial_class(
156
+ named_type_path="sdk.api.condition_parameters.upsert_condition_matches.Data",
157
+ )
158
+ @dataclasses.dataclass(slots=base_t.ENABLE_SLOTS, kw_only=True) # type: ignore[literal-required]
159
+ class Data:
160
+ results: list[async_batch_t.AsyncBatchActionReturn]
161
+ # DO NOT MODIFY -- This file is generated by type_spec
@@ -120,6 +120,7 @@ import uncountable.types.api.material_families.update_entity_material_families a
120
120
  import uncountable.types.api.outputs.update_output_condition_parameter as update_output_condition_parameter_t
121
121
  from uncountable.types import uploader_t
122
122
  import uncountable.types.api.condition_parameters.upsert_condition_match as upsert_condition_match_t
123
+ import uncountable.types.api.condition_parameters.upsert_condition_matches as upsert_condition_matches_t
123
124
  import uncountable.types.api.field_options.upsert_field_options as upsert_field_options_t
124
125
  import uncountable.types.api.recipes.upsert_recipe_workflow_step as upsert_recipe_workflow_step_t
125
126
  import uncountable.types.api.recipes.upsert_step_relationships as upsert_step_relationships_t
@@ -2457,6 +2458,26 @@ class ClientMethods(ABC):
2457
2458
  )
2458
2459
  return self.do_request(api_request=api_request, return_type=upsert_condition_match_t.Data)
2459
2460
 
2461
+ def upsert_condition_matches(
2462
+ self,
2463
+ *,
2464
+ upserts: list[upsert_condition_matches_t.UpsertArgs],
2465
+ _request_options: client_config_t.RequestOptions | None = None,
2466
+ ) -> upsert_condition_matches_t.Data:
2467
+ """Bulk variant of upsert_condition_match. Accepts a list of upsert arguments and applies them via a single batched database round-trip per bulk operation (insert, update, delete) through the shared upsert_condition_matches helper. Returns one AsyncBatchActionReturn per input in the same order.
2468
+
2469
+ """
2470
+ args = upsert_condition_matches_t.Arguments(
2471
+ upserts=upserts,
2472
+ )
2473
+ api_request = APIRequest(
2474
+ method=upsert_condition_matches_t.ENDPOINT_METHOD,
2475
+ endpoint=upsert_condition_matches_t.ENDPOINT_PATH,
2476
+ args=args,
2477
+ request_options=_request_options,
2478
+ )
2479
+ return self.do_request(api_request=api_request, return_type=upsert_condition_matches_t.Data)
2480
+
2460
2481
  def upsert_field_options(
2461
2482
  self,
2462
2483
  *,
@@ -0,0 +1,7 @@
1
+ # ruff: noqa: E402 Q003
2
+ # fmt: off
3
+ # isort: skip_file
4
+ # DO NOT MODIFY -- This file is generated by type_spec
5
+ # Kept only for SDK backwards compatibility
6
+ from .condition_match_t import MatchType as MatchType
7
+ # DO NOT MODIFY -- This file is generated by type_spec
@@ -0,0 +1,22 @@
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
+ from enum import StrEnum
10
+
11
+ __all__: list[str] = [
12
+ "MatchType",
13
+ ]
14
+
15
+
16
+ # DO NOT MODIFY -- This file is generated by type_spec
17
+ class MatchType(StrEnum):
18
+ ALL = "all"
19
+ EQ = "eq"
20
+ EQCATEGORICAL = "eqCategorical"
21
+ RANGE = "range"
22
+ # 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.166
3
+ Version: 0.0.168
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
@@ -46,7 +46,7 @@ pkgs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
46
  pkgs/argument_parser/__init__.py,sha256=kbarKmvTSa9lrt5hE0PUdJe3UmBHe8KbbnOLqupRZJE,1040
47
47
  pkgs/argument_parser/_is_enum.py,sha256=Gw6jJa8nBwYGqXwwCZbSnWL8Rvr5alkg5lSVAqXtOZM,257
48
48
  pkgs/argument_parser/_is_namedtuple.py,sha256=InCP2orqKbUYc4JsmE7ccri2EQPvLZeRijYPGqVSeXY,323
49
- pkgs/argument_parser/argument_parser.py,sha256=3qUDflMdtgC7cDZljo3-eSpikVqhT8WVb1nhul8Q_v8,26638
49
+ pkgs/argument_parser/argument_parser.py,sha256=pjphvyGLMA2DC5qqbdiID0SsNvlekWdWtHCfcb7bRbo,26691
50
50
  pkgs/argument_parser/case_convert.py,sha256=NuJLJUJRbyVb6_Slen4uqaStEHbcOS1d-hBBfDrrw-c,605
51
51
  pkgs/argument_parser/parser_error.py,sha256=2DuYW-vQL4F1V_NEr-WW_ZdBPmH7L_uJiOuv7KhMLXw,2732
52
52
  pkgs/filesystem_utils/__init__.py,sha256=Ik9algr3R5KJkMMe-EOBiw22rSvm68yjhYZ7WIKyCQ0,1614
@@ -55,8 +55,8 @@ pkgs/filesystem_utils/_file_share_session.py,sha256=xlxCbNtOOT1xXCiL2Y_unjWZlQ99
55
55
  pkgs/filesystem_utils/_gdrive_session.py,sha256=4P2MSsA0GNxIYJxvmf7zKR8dM8oVMCCSzDIEkGA7XqE,11089
56
56
  pkgs/filesystem_utils/_local_session.py,sha256=wbmcGqqus417lY_b6VzgEaZA0SUU8Ar5MrgMkXMp58M,2339
57
57
  pkgs/filesystem_utils/_s3_session.py,sha256=8SClYopSg4Wgn6EuUZWe8SqO3oaZGzY-Ojj356AsLYg,4503
58
- pkgs/filesystem_utils/_sftp_session.py,sha256=N2GoSFzXDy1kMghGPKQXd4qN4_ErAG8Wcpw8I_9Ve3c,4860
59
- pkgs/filesystem_utils/file_type_utils.py,sha256=OL_kS1lfNFfZdwcsaXtXpYnfXfsOrUB03vc1I4H0ZEc,2116
58
+ pkgs/filesystem_utils/_sftp_session.py,sha256=0s9y9IyYwCXZx7hPrifgQciRbNxsfWY7feF7x553lfc,4966
59
+ pkgs/filesystem_utils/file_type_utils.py,sha256=gSZ12rnhjjc0t13_n5nV7NIXLbDj-shF-eUpZTCZtNA,2135
60
60
  pkgs/filesystem_utils/filesystem_session.py,sha256=BQ2Go8Mu9-GcnaWh2Pm4x7ugLVsres6XrOQ8RoiEpcE,1045
61
61
  pkgs/serialization/__init__.py,sha256=xkCveM7syIh9N5tZ7ojjKKuFrqPwYcH9mu3iFcQ19s4,1213
62
62
  pkgs/serialization/annotation.py,sha256=UJNMb9wMUWLWW-om17HSNmfFtAEcZJcCmvirdmmGmhg,2114
@@ -110,12 +110,12 @@ uncountable/__init__.py,sha256=8l8XWNCKsu7TG94c-xa2KHpDegvxDC2FyQISdWC763Y,89
110
110
  uncountable/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
111
111
  uncountable/core/__init__.py,sha256=RFv0kO6rKFf1PtBPu83hCGmxqkJamRtsgQ9_-ztw7tA,341
112
112
  uncountable/core/async_batch.py,sha256=9pYGFzVCQXt8059qFHgutweGIFPquJ5Xfq6NT5P-1K0,1206
113
- uncountable/core/client.py,sha256=DRb2LUacLQaxwQu7YPMK4DHQd68K3CGswa8hF3D2Lac,15159
113
+ uncountable/core/client.py,sha256=MYalNgB9SMIq0rqhAw8w43qGrwUPTqGsxlY6x4KgkS8,15160
114
114
  uncountable/core/environment.py,sha256=Z9vu7JtnSDgQB_KKcZnjTFNyARXjRr_PDW9krwxNNAo,1132
115
- uncountable/core/file_upload.py,sha256=oIhtUvVfbI7mJO6DaP2aHltv1DD_XV_pOnxyj3wtoLE,5729
115
+ uncountable/core/file_upload.py,sha256=hvxz6lq1Hv4nK51bux8W_Tl22_UNBtd8U21OWteXWjI,5730
116
116
  uncountable/core/types.py,sha256=s2CjqYJpsmbC7xMwxxT7kJ_V9bwokrjjWVVjpMcQpKI,333
117
117
  uncountable/core/query/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
118
- uncountable/core/query/builder.py,sha256=Wv7KaU5n6HmN59kg7Ut-yLOBeJpvw_csMrNVwJFBOuA,7523
118
+ uncountable/core/query/builder.py,sha256=bnsHdeV6Pe2gf2kpbVDtY8_cQmmp51CMprLfNjymN8Q,8243
119
119
  uncountable/core/query/column.py,sha256=Xxvp9C4PkHtcSI5NGAt90NBI4-xpNwpvRJr6f1L51hA,44285
120
120
  uncountable/core/query/row.py,sha256=dy68YKvMz336nyOsj_g2oNTLKBuUnKRUz5n99sApfT0,1600
121
121
  uncountable/core/query/types.py,sha256=xZgc0_O1PIxZoJWQhxkg3qnSvHj3yt-bv9QX1UCOFGw,481
@@ -127,20 +127,20 @@ uncountable/integration/entrypoint.py,sha256=BHOYPQgKvZE6HG8Rv15MkdYl8lRkvfDgv1O
127
127
  uncountable/integration/job.py,sha256=ZVcMddHdOobFHO8MZAPNH3Ht6A8Odu6uEUsqtsKmgtc,8447
128
128
  uncountable/integration/request_context.py,sha256=N_FJJxqvfUJ0yV9h3I3vFTGNJiDfyLYObczcYa44pw8,999
129
129
  uncountable/integration/scan_profiles.py,sha256=iTpzYKBHarlhYG6CKYtyAmQ7vUhEUbj2icGVHell8AU,3059
130
- uncountable/integration/scheduler.py,sha256=4fLxzSwmuZ0FafM3HmiThFFxfZKG9WlcQCG4fT6yyXM,8470
130
+ uncountable/integration/scheduler.py,sha256=BajQ4txvgEBw8S9x1P0eGm-uBkKp_oecJK65SQd2hNw,8477
131
131
  uncountable/integration/server.py,sha256=P4RRGwU9jMselHPWbU6GxhRLgVtN7Ydcxr18sFn2zI8,5778
132
- uncountable/integration/telemetry.py,sha256=BvbL7pM_OX02UUX4gpxk-eMEEUHIqlRx9FZ2V12USPQ,14530
132
+ uncountable/integration/telemetry.py,sha256=rWq5DUYldx6vz-L_a3dsohVX8PSJFlBUIo5NfjAYbT4,14986
133
133
  uncountable/integration/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
134
134
  uncountable/integration/db/connect.py,sha256=mE3bdV0huclH2iT_dXCQdRL4LkjIuf_myAR64RTWXEs,498
135
135
  uncountable/integration/db/session.py,sha256=96cGQXpe6IugBTdSsjdP0S5yhJ6toSmbVB6qhc3FJzE,693
136
136
  uncountable/integration/executors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
137
- uncountable/integration/executors/executors.py,sha256=NWe9KjJL9C76h77sutzoqhI8HkdBkLUjLrhRUmC3bpU,5147
137
+ uncountable/integration/executors/executors.py,sha256=E3a0_yrQesfigDzjhUAwD7-l-oc9qnvPh-A8QTK6F3k,5149
138
138
  uncountable/integration/executors/generic_upload_executor.py,sha256=BdakXkAvNvLcM96fGvN0Jw2jCvGah6Q29vlXcX1nL-A,12094
139
139
  uncountable/integration/executors/script_executor.py,sha256=BBQ9f0l7uH2hgKf60jtm-pONzwk-EeOhM2qBAbv_URo,846
140
140
  uncountable/integration/http_server/__init__.py,sha256=WY2HMcL0UCAGYv8y6Pz-j0azbDGXwubFF21EH_zNPkc,189
141
141
  uncountable/integration/http_server/types.py,sha256=3JJSulRfv784SbXnXo1Pywto7RwGxgS-iJ2-a6TOnDI,1869
142
142
  uncountable/integration/queue_runner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
143
- uncountable/integration/queue_runner/job_scheduler.py,sha256=RWWDADzLFoTVVIAFqSzDedh8Kcpz9ChamEnjCZZa6Aw,9799
143
+ uncountable/integration/queue_runner/job_scheduler.py,sha256=LuhJjomXhIy7am-JsdhrF2RxtGd3U4a6mLmIbdqfAPc,9803
144
144
  uncountable/integration/queue_runner/queue_runner.py,sha256=N4sUXmlGzVquybiJ7NQZavCJOBGrxBj6k7mb-TITaN0,1139
145
145
  uncountable/integration/queue_runner/types.py,sha256=8HS6KnYMS_vc5XHeMpg0BFAQC-5P3QLzd-aDYDMMt3E,244
146
146
  uncountable/integration/queue_runner/worker.py,sha256=YU1qRGCYF7vrEcsV4zGnNslYGc6XM9WMCpoATtXB_g0,7314
@@ -160,8 +160,8 @@ uncountable/integration/queue_runner/datastore/interface.py,sha256=zonEm2O1l5GXE
160
160
  uncountable/integration/queue_runner/datastore/model.py,sha256=SH0tpXH0G7oydUbOT1z7aFZ6SNIZ6EHc9YAEaM5l1oM,832
161
161
  uncountable/integration/secret_retrieval/__init__.py,sha256=3QXVj35w8rRMxVvmmsViFYDi3lcb3g70incfalOEm6o,87
162
162
  uncountable/integration/secret_retrieval/retrieve_secret.py,sha256=LBEf18KHtXZxg-ZZ80stJ1vW39AWf0CQllP6pNu3Eq8,2994
163
- uncountable/integration/webhook_server/entrypoint.py,sha256=TeHkpufKsbklbHXCXz57Hvh887jNOD8LV7p8x-9LE5k,4267
164
- uncountable/types/__init__.py,sha256=nOt7Bnspaz_A1XILTyUt9QG8JN9Wj6K67lAD_CtzPMY,13216
163
+ uncountable/integration/webhook_server/entrypoint.py,sha256=_1360zLIvEVQVf8ToyK-E2fnaszGKs2HcxcZjNraADc,4268
164
+ uncountable/types/__init__.py,sha256=k3T7TUSXwR7FbNComx3pP66mbc1yxz37OIy_u7ZdOi0,13421
165
165
  uncountable/types/async_batch.py,sha256=yCCWrrLQfxXVqZp-KskxLBNkNmuELdz4PJjx8ULppgs,662
166
166
  uncountable/types/async_batch_processor.py,sha256=A6ohKoJhUW2jCE8xOUNNt9ab5l6P5jWvWCNBTJPeqQk,46460
167
167
  uncountable/types/async_batch_t.py,sha256=9MIXbtMYpHOZW9MkxXb346OfvS5CjuUrBKMd7uyAfyw,4402
@@ -175,9 +175,11 @@ uncountable/types/calculations.py,sha256=fApOFpgBemt_t7IVneVR0VdI3X5EOxiG6Xhzr6R
175
175
  uncountable/types/calculations_t.py,sha256=pl-lhjyDQuj11Sf9g1-0BsSkN7Ez8UxDp8-KMQ_3enM,709
176
176
  uncountable/types/chemical_structure.py,sha256=ujyragaD26-QG5jgKnWhO7TN3N1V9b_04T2WhqNYxxo,281
177
177
  uncountable/types/chemical_structure_t.py,sha256=VFFyits_vx4t5L2euu_qFiSpsGJjURkDPr3ISnr3nPc,855
178
- uncountable/types/client_base.py,sha256=yD9z1uiWnym0AKSIr5eS5EYWCR0r8eF2Csag9PhqUo8,129153
178
+ uncountable/types/client_base.py,sha256=ed9pNkxaRBwWdrMMLqcDtqt6-I-GHYZoRzVQvE5IHdU,130243
179
179
  uncountable/types/client_config.py,sha256=M7FZ0m_lGmBsIYcMn8pm92DdoVzrLpzd8sH6DqTQLKo,456
180
180
  uncountable/types/client_config_t.py,sha256=m1sVg0zaVf8DvQKaDJH_fG1abU2-VZZH0wOIMDjlECU,1944
181
+ uncountable/types/condition_match.py,sha256=ekDzij7-e1PtVwIslSjD1T9fuBIDr5c_7QPdaasiGJw,262
182
+ uncountable/types/condition_match_t.py,sha256=iykByQKFtFa2Gk3sqi9xkqoLHeztRHagV4zkiXqwUh4,536
181
183
  uncountable/types/curves.py,sha256=QyEyC20jsG-LGKVx6miiF-w70vKMwNkILFBDIJ5Ok9g,345
182
184
  uncountable/types/curves_t.py,sha256=DxYepdC3QKKR7mepOOBoyarNcFZQdUa5ZYH-hwCY3BI,1469
183
185
  uncountable/types/data.py,sha256=u2isf4XEug3Eu-xSIoqGaCQmW2dFaKBHCkP_WKYwwBc,500
@@ -281,7 +283,8 @@ uncountable/types/api/batch/execute_batch_load_async.py,sha256=YLr3Ae3xruoZv3BHL
281
283
  uncountable/types/api/chemical/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
282
284
  uncountable/types/api/chemical/convert_chemical_formats.py,sha256=V-s2EMqjodA_zS7-fOdo4iI4yFrvOc2bAwRFCsl445Y,1961
283
285
  uncountable/types/api/condition_parameters/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
284
- uncountable/types/api/condition_parameters/upsert_condition_match.py,sha256=7I9lAiYfu8_abkKdmDtw11br_Ii9HCqBpGoaXBr5Vog,2319
286
+ uncountable/types/api/condition_parameters/upsert_condition_match.py,sha256=VpC3ZNTmP9qxyuoVy6MOnJR1QMH7lnbKmaLpskvbElU,2457
287
+ uncountable/types/api/condition_parameters/upsert_condition_matches.py,sha256=iooN1XX9jqXbS16Tj1iAJ5AglLm5q8J8PJW7SX9Fwuk,6073
285
288
  uncountable/types/api/entity/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
286
289
  uncountable/types/api/entity/create_entities.py,sha256=cCDEra2SHvGWvz7nIxxMDSQN6OWrHMTT0JSomWUesto,1794
287
290
  uncountable/types/api/entity/create_entity.py,sha256=urT6C7iGAa7_rCv9Wcz6GM_lKg1tP55E__rjNkj-Rjc,1879
@@ -395,7 +398,7 @@ uncountable/types/api/uploader/complete_async_parse.py,sha256=ffS3ApqCNkZb6QPuYE
395
398
  uncountable/types/api/uploader/invoke_uploader.py,sha256=Bj7Dq4A90k00suacwk3bLA_dCb2aovS1kAbVam2AQnM,1395
396
399
  uncountable/types/api/user/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
397
400
  uncountable/types/api/user/get_current_user_info.py,sha256=Avqi_RXtRgbefrT_dwJ9MrO6eDNSSa_Nu650FSuESlg,1109
398
- uncountablepythonsdk-0.0.166.dist-info/METADATA,sha256=E-holHf_W4PtrzbwUMmVB-6mJ-3xaKNhUs7sZSAzJ4I,2289
399
- uncountablepythonsdk-0.0.166.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
400
- uncountablepythonsdk-0.0.166.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
401
- uncountablepythonsdk-0.0.166.dist-info/RECORD,,
401
+ uncountablepythonsdk-0.0.168.dist-info/METADATA,sha256=Eh2iAbMOCD1M_CLUBemNthcUHEjuZ2DMeTfZeruzGYk,2289
402
+ uncountablepythonsdk-0.0.168.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
403
+ uncountablepythonsdk-0.0.168.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
404
+ uncountablepythonsdk-0.0.168.dist-info/RECORD,,