UncountablePythonSDK 0.0.171__py3-none-any.whl → 0.0.172__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.
Files changed (147) hide show
  1. examples/integration-server/jobs/materials_auto/example_instrument.py +1 -1
  2. examples/integration-server/pyproject.toml +3 -4
  3. pkgs/argument_parser/__init__.py +47 -17
  4. pkgs/argument_parser/cached_parser.py +56 -0
  5. pkgs/argument_parser/parser_base.py +42 -0
  6. pkgs/argument_parser/parser_builder.py +52 -0
  7. pkgs/argument_parser/parser_cache.py +30 -0
  8. pkgs/argument_parser/parser_function_type.py +6 -0
  9. pkgs/argument_parser/{argument_parser.py → parser_inner.py} +65 -199
  10. pkgs/argument_parser/parser_options.py +49 -0
  11. pkgs/argument_parser/type_predicates.py +45 -0
  12. pkgs/filesystem_utils/__init__.py +29 -19
  13. pkgs/filesystem_utils/_sftp_connection.py +168 -0
  14. pkgs/filesystem_utils/_sftp_session.py +9 -10
  15. pkgs/serialization/__init__.py +23 -15
  16. pkgs/serialization/annotation.py +40 -9
  17. pkgs/serialization/serial_class.py +6 -2
  18. pkgs/serialization/serial_generic.py +2 -2
  19. pkgs/serialization/serial_union.py +18 -5
  20. pkgs/serialization/yaml.py +1 -6
  21. pkgs/serialization_util/__init__.py +1 -2
  22. pkgs/serialization_util/serialization_helpers.py +2 -2
  23. pkgs/type_spec/actions_registry/__main__.py +10 -1
  24. pkgs/type_spec/actions_registry/emit_python.py +92 -0
  25. pkgs/type_spec/emit_python.py +6 -0
  26. pkgs/type_spec/load_types.py +35 -25
  27. pkgs/type_spec/non_discriminated_union_exceptions.py +2 -1
  28. pkgs/type_spec/open_api_util.py +5 -5
  29. pkgs/type_spec/type_info/emit_type_info.py +2 -2
  30. pkgs/type_spec/value_spec/__main__.py +53 -21
  31. pkgs/type_spec/value_spec/emit_python.py +6 -1
  32. uncountable/core/client.py +34 -1
  33. uncountable/integration/cli.py +1 -2
  34. uncountable/integration/http_server/__init__.py +5 -3
  35. uncountable/integration/queue_runner/job_scheduler.py +4 -1
  36. uncountable/integration/telemetry.py +29 -11
  37. uncountable/types/__init__.py +2 -0
  38. uncountable/types/api/batch/execute_batch.py +2 -0
  39. uncountable/types/api/batch/execute_batch_load_async.py +2 -0
  40. uncountable/types/api/chemical/convert_chemical_formats.py +2 -0
  41. uncountable/types/api/condition_parameters/upsert_condition_match.py +2 -0
  42. uncountable/types/api/condition_parameters/upsert_condition_matches.py +2 -0
  43. uncountable/types/api/entity/create_entities.py +2 -0
  44. uncountable/types/api/entity/create_entity.py +2 -0
  45. uncountable/types/api/entity/create_or_update_entities.py +2 -0
  46. uncountable/types/api/entity/create_or_update_entity.py +2 -0
  47. uncountable/types/api/entity/export_entities.py +2 -0
  48. uncountable/types/api/entity/get_entities_data.py +2 -0
  49. uncountable/types/api/entity/grant_entity_permissions.py +2 -0
  50. uncountable/types/api/entity/list_aggregate.py +2 -0
  51. uncountable/types/api/entity/list_entities.py +2 -0
  52. uncountable/types/api/entity/lock_entity.py +2 -0
  53. uncountable/types/api/entity/lookup_entity.py +2 -0
  54. uncountable/types/api/entity/resolve_entity_ids.py +2 -0
  55. uncountable/types/api/entity/set_barcode.py +2 -0
  56. uncountable/types/api/entity/set_entities_field_values.py +2 -0
  57. uncountable/types/api/entity/set_entity_field_values.py +2 -0
  58. uncountable/types/api/entity/set_values.py +2 -0
  59. uncountable/types/api/entity/transition_entity_phase.py +2 -0
  60. uncountable/types/api/entity/unlock_entity.py +2 -0
  61. uncountable/types/api/equipment/associate_equipment_input.py +2 -0
  62. uncountable/types/api/field_options/upsert_field_options.py +2 -0
  63. uncountable/types/api/file_folders/add_file_to_folder.py +2 -0
  64. uncountable/types/api/file_folders/modify_file_system.py +2 -0
  65. uncountable/types/api/files/download_file.py +2 -0
  66. uncountable/types/api/id_source/list_id_source.py +2 -0
  67. uncountable/types/api/id_source/match_id_source.py +2 -0
  68. uncountable/types/api/input_groups/get_input_group_names.py +2 -0
  69. uncountable/types/api/inputs/create_inputs.py +2 -0
  70. uncountable/types/api/inputs/get_input_data.py +2 -0
  71. uncountable/types/api/inputs/get_input_names.py +2 -0
  72. uncountable/types/api/inputs/get_inputs_data.py +2 -0
  73. uncountable/types/api/inputs/set_input_attribute_values.py +2 -0
  74. uncountable/types/api/inputs/set_input_category.py +2 -0
  75. uncountable/types/api/inputs/set_input_subcategories.py +2 -0
  76. uncountable/types/api/inputs/set_intermediate_type.py +2 -0
  77. uncountable/types/api/integrations/publish_realtime_data.py +2 -0
  78. uncountable/types/api/integrations/push_notification.py +2 -0
  79. uncountable/types/api/integrations/register_sockets_token.py +2 -0
  80. uncountable/types/api/listing/export_listing.py +2 -0
  81. uncountable/types/api/listing/fetch_listing.py +2 -0
  82. uncountable/types/api/material_families/update_entity_material_families.py +2 -0
  83. uncountable/types/api/notebooks/add_notebook_content.py +4 -0
  84. uncountable/types/api/notebooks/get_notebook_content.py +2 -0
  85. uncountable/types/api/output_parameters/swap_output_condition_parameters.py +2 -0
  86. uncountable/types/api/outputs/get_output_data.py +2 -0
  87. uncountable/types/api/outputs/get_output_names.py +2 -0
  88. uncountable/types/api/outputs/get_output_organization.py +2 -0
  89. uncountable/types/api/outputs/resolve_output_conditions.py +2 -0
  90. uncountable/types/api/outputs/update_output_condition_parameter.py +2 -0
  91. uncountable/types/api/permissions/set_core_permissions.py +2 -0
  92. uncountable/types/api/permissions/set_entity_permission.py +2 -0
  93. uncountable/types/api/project/get_projects.py +2 -0
  94. uncountable/types/api/project/get_projects_data.py +2 -0
  95. uncountable/types/api/recipe_links/create_recipe_link.py +2 -0
  96. uncountable/types/api/recipe_links/create_recipe_links.py +57 -0
  97. uncountable/types/api/recipe_links/remove_recipe_link.py +2 -0
  98. uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +2 -0
  99. uncountable/types/api/recipes/add_recipe_to_project.py +2 -0
  100. uncountable/types/api/recipes/add_time_series_data.py +2 -0
  101. uncountable/types/api/recipes/archive_recipes.py +2 -0
  102. uncountable/types/api/recipes/associate_recipe_as_input.py +2 -0
  103. uncountable/types/api/recipes/associate_recipe_as_lot.py +2 -0
  104. uncountable/types/api/recipes/clear_recipe_outputs.py +2 -0
  105. uncountable/types/api/recipes/create_mix_order.py +2 -0
  106. uncountable/types/api/recipes/create_recipe.py +2 -0
  107. uncountable/types/api/recipes/create_recipes.py +2 -0
  108. uncountable/types/api/recipes/disassociate_recipe_as_input.py +2 -0
  109. uncountable/types/api/recipes/edit_recipe_inputs.py +61 -2
  110. uncountable/types/api/recipes/get_column_calculation_values.py +2 -0
  111. uncountable/types/api/recipes/get_curve.py +2 -0
  112. uncountable/types/api/recipes/get_recipe_calculations.py +2 -0
  113. uncountable/types/api/recipes/get_recipe_links.py +2 -0
  114. uncountable/types/api/recipes/get_recipe_names.py +2 -0
  115. uncountable/types/api/recipes/get_recipe_output_metadata.py +2 -0
  116. uncountable/types/api/recipes/get_recipes_data.py +16 -0
  117. uncountable/types/api/recipes/lock_recipes.py +2 -0
  118. uncountable/types/api/recipes/remove_recipe_from_project.py +2 -0
  119. uncountable/types/api/recipes/set_recipe_inputs.py +2 -0
  120. uncountable/types/api/recipes/set_recipe_metadata.py +2 -0
  121. uncountable/types/api/recipes/set_recipe_output_annotations.py +2 -0
  122. uncountable/types/api/recipes/set_recipe_output_file.py +2 -0
  123. uncountable/types/api/recipes/set_recipe_outputs.py +2 -0
  124. uncountable/types/api/recipes/set_recipe_tags.py +2 -0
  125. uncountable/types/api/recipes/set_recipe_total.py +2 -0
  126. uncountable/types/api/recipes/unarchive_recipes.py +2 -0
  127. uncountable/types/api/recipes/unlock_recipes.py +2 -0
  128. uncountable/types/api/recipes/upsert_recipe_workflow_step.py +2 -0
  129. uncountable/types/api/recipes/upsert_step_relationships.py +2 -0
  130. uncountable/types/api/runsheet/complete_async_upload.py +2 -0
  131. uncountable/types/api/runsheet/export_default_runsheet.py +2 -0
  132. uncountable/types/api/triggers/run_trigger.py +2 -0
  133. uncountable/types/api/uploader/complete_async_parse.py +2 -0
  134. uncountable/types/api/uploader/invoke_uploader.py +2 -0
  135. uncountable/types/api/user/get_current_user_info.py +2 -0
  136. uncountable/types/async_batch_processor.py +1 -1
  137. uncountable/types/client_base.py +29 -2
  138. uncountable/types/entity_t.py +18 -0
  139. uncountable/types/recipe_workflow_step_types_t.py +2 -2
  140. uncountable/types/request_headers.py +1 -0
  141. uncountable/types/request_headers_t.py +6 -0
  142. {uncountablepythonsdk-0.0.171.dist-info → uncountablepythonsdk-0.0.172.dist-info}/METADATA +6 -8
  143. {uncountablepythonsdk-0.0.171.dist-info → uncountablepythonsdk-0.0.172.dist-info}/RECORD +145 -137
  144. pkgs/argument_parser/_is_enum.py +0 -11
  145. pkgs/argument_parser/_is_namedtuple.py +0 -14
  146. {uncountablepythonsdk-0.0.171.dist-info → uncountablepythonsdk-0.0.172.dist-info}/WHEEL +0 -0
  147. {uncountablepythonsdk-0.0.171.dist-info → uncountablepythonsdk-0.0.172.dist-info}/top_level.txt +0 -0
@@ -14,7 +14,7 @@ from uncountable.types.integration_session_t import IntegrationSessionInstrument
14
14
  from websockets.sync.client import connect
15
15
  from websockets.typing import Data
16
16
 
17
- from pkgs.argument_parser.argument_parser import CachedParser
17
+ from pkgs.argument_parser import CachedParser
18
18
  from pkgs.serialization_util import serialize_for_api
19
19
 
20
20
 
@@ -5,19 +5,18 @@ name = "integration-server-testing"
5
5
  # end_project_name
6
6
  dynamic = ["version"]
7
7
  dependencies = [
8
- "mypy == 1.*",
8
+ "mypy >= 2.1.0, < 3",
9
9
  "ruff == 0.*",
10
10
  "openpyxl == 3.*",
11
11
  "more_itertools ==11.*",
12
- "types-paramiko ==4.0.0.20260402",
12
+ "types-paramiko ==4.0.0.20260518",
13
13
  "types-openpyxl == 3.*",
14
- "types-pysftp == 0.*",
15
14
  "types-pytz ==2026.*",
16
15
  "types-requests == 2.*",
17
16
  "types-simplejson == 3.*",
18
17
  "pandas-stubs",
19
18
  "xlrd == 2.*",
20
- "msgspec ==0.20.*",
19
+ "msgspec ==0.21.*",
21
20
  "websockets==16.0",
22
21
  ]
23
22
 
@@ -1,17 +1,47 @@
1
- from ._is_enum import is_string_enum_class as is_string_enum_class
2
- from .argument_parser import CachedParser as CachedParser
3
- from .argument_parser import ParserBase as ParserBase
4
- from .argument_parser import ParserFunction as ParserFunction
5
- from .argument_parser import ParserOptions as ParserOptions
6
- from .argument_parser import SourceEncoding as SourceEncoding
7
- from .argument_parser import build_parser as build_parser
8
- from .argument_parser import is_missing as is_missing
9
- from .argument_parser import is_optional as is_optional
10
- from .argument_parser import is_union as is_union
11
- from .case_convert import camel_to_snake_case as camel_to_snake_case
12
- from .case_convert import kebab_to_pascal_case as kebab_to_pascal_case
13
- from .case_convert import snake_to_camel_case as snake_to_camel_case
14
- from .parser_error import ParserEnumError as ParserEnumError
15
- from .parser_error import ParserError as ParserError
16
- from .parser_error import ParserExtraFieldsError as ParserExtraFieldsError
17
- from .parser_error import ParserTypeError as ParserTypeError
1
+ # CLOSED MODULE
2
+
3
+ __all__ = [
4
+ "CachedParser",
5
+ "ParserBase",
6
+ "ParserEnumError",
7
+ "ParserError",
8
+ "ParserExtraFieldsError",
9
+ "ParserFunction",
10
+ "ParserOptions",
11
+ "ParserTypeError",
12
+ "SourceEncoding",
13
+ "build_parser",
14
+ "camel_to_snake_case",
15
+ "is_missing",
16
+ "is_optional",
17
+ "is_string_enum_class",
18
+ "is_union",
19
+ "kebab_to_pascal_case",
20
+ "snake_to_camel_case",
21
+ ]
22
+
23
+ from .cached_parser import CachedParser as CachedParser
24
+ from .case_convert import (
25
+ camel_to_snake_case as camel_to_snake_case,
26
+ kebab_to_pascal_case as kebab_to_pascal_case,
27
+ snake_to_camel_case as snake_to_camel_case,
28
+ )
29
+ from .parser_base import ParserBase as ParserBase
30
+ from .parser_builder import build_parser as build_parser
31
+ from .parser_error import (
32
+ ParserEnumError as ParserEnumError,
33
+ ParserError as ParserError,
34
+ ParserExtraFieldsError as ParserExtraFieldsError,
35
+ ParserTypeError as ParserTypeError,
36
+ )
37
+ from .parser_function_type import ParserFunction as ParserFunction
38
+ from .parser_options import (
39
+ ParserOptions as ParserOptions,
40
+ SourceEncoding as SourceEncoding,
41
+ )
42
+ from .type_predicates import (
43
+ is_missing as is_missing,
44
+ is_optional as is_optional,
45
+ is_string_enum_class as is_string_enum_class,
46
+ is_union as is_union,
47
+ )
@@ -0,0 +1,56 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+
5
+ from pkgs.serialization import SerialType
6
+
7
+ from .parser_base import ParserBase
8
+ from .parser_builder import build_parser
9
+ from .parser_function_type import ParserFunction
10
+ from .parser_options import ParserOptions
11
+
12
+ T = typing.TypeVar("T")
13
+
14
+
15
+ class CachedParser(ParserBase[T], typing.Generic[T]):
16
+ def __init__(
17
+ self,
18
+ args: SerialType[T],
19
+ strict_property_parsing: bool = False,
20
+ ):
21
+ self.arguments = args
22
+ self.parser_api: ParserFunction[T] | None = None
23
+ self.parser_storage: ParserFunction[T] | None = None
24
+ self.strict_property_parsing = strict_property_parsing
25
+
26
+ def parse_api(self, args: typing.Any) -> T:
27
+ """
28
+ Parses data coming from an API/Endpoint
29
+
30
+ NOTE: Some places use this to parse storage data due to backwards
31
+ compatibility. If your data is coming from the DB or a file, it is
32
+ preferred to use parse_storage.
33
+ """
34
+ if self.parser_api is None:
35
+ self.parser_api = build_parser(
36
+ self.arguments,
37
+ ParserOptions.Api(
38
+ strict_property_parsing=self.strict_property_parsing,
39
+ ),
40
+ )
41
+ assert self.parser_api is not None
42
+ return self.parser_api(args)
43
+
44
+ def parse_storage(self, args: typing.Any) -> T:
45
+ """
46
+ Parses data coming from the database or file.
47
+ """
48
+ if self.parser_storage is None:
49
+ self.parser_storage = build_parser(
50
+ self.arguments,
51
+ ParserOptions.Storage(
52
+ strict_property_parsing=self.strict_property_parsing,
53
+ ),
54
+ )
55
+ assert self.parser_storage is not None
56
+ return self.parser_storage(args)
@@ -0,0 +1,42 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ from abc import ABC, abstractmethod
5
+ from importlib import resources
6
+
7
+ import msgspec.yaml
8
+
9
+ from .parser_options import SourceEncoding
10
+
11
+ T = typing.TypeVar("T")
12
+
13
+
14
+ class ParserBase(ABC, typing.Generic[T]):
15
+ def parse_from_encoding(
16
+ self,
17
+ args: typing.Any,
18
+ *,
19
+ source_encoding: SourceEncoding,
20
+ ) -> T:
21
+ match source_encoding:
22
+ case SourceEncoding.API:
23
+ return self.parse_api(args)
24
+ case SourceEncoding.STORAGE:
25
+ return self.parse_storage(args)
26
+ case _:
27
+ typing.assert_never(source_encoding)
28
+
29
+ # IMPROVE: Args would be better typed as "object"
30
+ @abstractmethod
31
+ def parse_storage(self, args: typing.Any) -> T: ...
32
+
33
+ @abstractmethod
34
+ def parse_api(self, args: typing.Any) -> T: ...
35
+
36
+ def parse_yaml_file(self, path: str) -> T:
37
+ with open(path, encoding="utf-8") as data_in:
38
+ return self.parse_storage(msgspec.yaml.decode(data_in.read()))
39
+
40
+ def parse_yaml_resource(self, package: resources.Package, resource: str) -> T:
41
+ with resources.open_text(package, resource) as fp:
42
+ return self.parse_storage(msgspec.yaml.decode(fp.read()))
@@ -0,0 +1,52 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ from collections import defaultdict
5
+
6
+ from pkgs.serialization import SerialType
7
+
8
+ from .parser_cache import ParserCache, ParserCacheManager
9
+ from .parser_function_type import ParserFunction
10
+ from .parser_inner import build_parser_inner
11
+ from .parser_options import ParserContext, ParserOptions
12
+
13
+ T = typing.TypeVar("T")
14
+
15
+ _CACHE_MAP: dict[ParserOptions, ParserCache] = defaultdict(ParserCache)
16
+
17
+
18
+ def build_parser(
19
+ parsed_type: SerialType[T],
20
+ options: ParserOptions,
21
+ ) -> ParserFunction[T]:
22
+ """
23
+ Consider using CachedParser to provide a cleaner API for storage and API
24
+ data parsing.
25
+ """
26
+
27
+ # Keep a cache per ParserOptions type, as they produce distinct parsers
28
+ global_cache = _CACHE_MAP[options]
29
+
30
+ context = ParserContext(options=options, cache=ParserCacheManager(global_cache))
31
+
32
+ # PEP 695 ``type X[T] = ...`` aliases (and their subscripted forms) are not
33
+ # ``type`` instances, so they can't share the top-level cache keyed on
34
+ # ``type[Any]``. Skip the top-level cache for them; ``build_parser_inner``
35
+ # caches parameterized dataclasses internally by ``(type, type_var_map)``.
36
+ is_alias_type = isinstance(parsed_type, typing.TypeAliasType) or isinstance(
37
+ typing.get_origin(parsed_type), typing.TypeAliasType
38
+ )
39
+
40
+ if not is_alias_type:
41
+ cur_parser = global_cache.get(parsed_type)
42
+ if cur_parser is not None:
43
+ return cur_parser
44
+
45
+ built_parser = build_parser_inner(parsed_type, context, {})
46
+
47
+ if not is_alias_type:
48
+ context.cache.put(parsed_type, built_parser)
49
+
50
+ global_cache.update(context.cache.local_cache())
51
+
52
+ return built_parser
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ import types
4
+ import typing
5
+
6
+ from .parser_function_type import ParserFunction
7
+
8
+ T = typing.TypeVar("T")
9
+ ParserCacheKey = type[typing.Any] | typing.TypeAliasType | types.UnionType
10
+ ParserCache = dict[
11
+ ParserCacheKey,
12
+ ParserFunction[typing.Any],
13
+ ]
14
+
15
+
16
+ class ParserCacheManager:
17
+ def __init__(self, global_cache: ParserCache) -> None:
18
+ self._global_cache = global_cache
19
+ self._local_cache: ParserCache = dict()
20
+
21
+ def get(self, key: ParserCacheKey) -> ParserFunction[typing.Any] | None:
22
+ if key in self._local_cache:
23
+ return self._local_cache[key]
24
+ return self._global_cache.get(key)
25
+
26
+ def put(self, key: ParserCacheKey, value: ParserFunction[typing.Any]) -> None:
27
+ self._local_cache[key] = value
28
+
29
+ def local_cache(self) -> ParserCache:
30
+ return self._local_cache
@@ -0,0 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+
5
+ T = typing.TypeVar("T")
6
+ ParserFunction = typing.Callable[[typing.Any], T]