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

@@ -0,0 +1,61 @@
1
+ Metadata-Version: 2.1
2
+ Name: UncountablePythonSDK
3
+ Version: 0.0.87
4
+ Summary: Uncountable SDK
5
+ Project-URL: Homepage, https://github.com/uncountableinc/uncountable-python-sdk
6
+ Project-URL: Repository, https://github.com/uncountableinc/uncountable-python-sdk.git
7
+ Project-URL: Issues, https://github.com/uncountableinc/uncountable-python-sdk/issues
8
+ Keywords: uncountable,sdk,api,uncountable-sdk
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Software Development
15
+ Classifier: Topic :: Utilities
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: aiotus==1.*
20
+ Requires-Dist: aiohttp==3.*
21
+ Requires-Dist: requests==2.*
22
+ Requires-Dist: SQLAlchemy>=1.4.0
23
+ Requires-Dist: APScheduler==3.*
24
+ Requires-Dist: python-dateutil==2.*
25
+ Requires-Dist: shelljob==0.*
26
+ Requires-Dist: PyYAML==6.*
27
+ Requires-Dist: google-api-python-client==2.*
28
+ Requires-Dist: tqdm==4.*
29
+ Requires-Dist: pysftp==0.*
30
+ Requires-Dist: opentelemetry-api==1.*
31
+ Requires-Dist: opentelemetry-exporter-otlp-proto-common==1.*
32
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.*
33
+ Requires-Dist: opentelemetry-sdk==1.*
34
+ Requires-Dist: paramiko==3.*
35
+ Requires-Dist: boto3==1.*
36
+ Requires-Dist: flask==3.*
37
+ Requires-Dist: simplejson==3.*
38
+ Requires-Dist: grpcio==1.67.1
39
+ Requires-Dist: protobuf>=4.21.1
40
+ Requires-Dist: azure-storage-blob==12.*
41
+ Provides-Extra: test
42
+ Requires-Dist: mypy==1.*; extra == "test"
43
+ Requires-Dist: ruff==0.*; extra == "test"
44
+ Requires-Dist: pytest==8.*; extra == "test"
45
+ Requires-Dist: coverage[toml]==7.*; extra == "test"
46
+ Requires-Dist: pytest-cov==5.*; extra == "test"
47
+ Requires-Dist: pytest-xdist==3.*; extra == "test"
48
+
49
+ # Uncountable Python SDK
50
+
51
+ ## Documentation
52
+ [https://uncountableinc.github.io/uncountable-python-sdk/](https://uncountableinc.github.io/uncountable-python-sdk/)
53
+
54
+ ## Installation
55
+
56
+ Install from PyPI:
57
+
58
+ ```console
59
+ pip install UncountablePythonSDK
60
+ ```
61
+
@@ -21,7 +21,7 @@ examples/invoke_uploader.py,sha256=rEvmVY5TjigN_-4PTQdkjY-bC5DrYMcJgquyZ4Tt5FM,7
21
21
  examples/set_recipe_metadata_file.py,sha256=oPBhMo9T17zj4YpJRkmVH9lknYcT8livph0KssxYtg4,1048
22
22
  examples/set_recipe_output_file_sdk.py,sha256=Lz1amqppnWTX83z-C090wCJ4hcKmCD3kb-4v0uBRi0Y,782
23
23
  examples/upload_files.py,sha256=tUfKFqiqwnw08OL5Y8_e4j5pSRhp94cFex8XTuVa_ig,487
24
- examples/integration-server/pyproject.toml,sha256=mB0uj-_Wo8WRmhdMwOwcdOB5lAhiW_8Kf-epMFrOq34,9133
24
+ examples/integration-server/pyproject.toml,sha256=ziyEbSiTYlOk784euVJjuZ3UqIEENkPXuug5VTrkjms,9131
25
25
  examples/integration-server/jobs/materials_auto/example_cron.py,sha256=7VVQ-UJsq3DbGpN3XPnorRVZYo-vCwbfSU3VVDluIzA,699
26
26
  examples/integration-server/jobs/materials_auto/example_wh.py,sha256=Hx5nonavOh2L3JykDpI5bPqPu8L1wwhwekTUfTRgq9g,479
27
27
  examples/integration-server/jobs/materials_auto/profile.yaml,sha256=XlOXSRplMJ13T6900pv1wDKxeE9V1hZZTMuvup1MiBM,896
@@ -33,16 +33,17 @@ pkgs/argument_parser/_is_namedtuple.py,sha256=Rjc1bKanIPPogl3qG5JPBxglG1TqWYOo1n
33
33
  pkgs/argument_parser/argument_parser.py,sha256=XjWQdcWanfaCGdLx7c_yQtqbZp7db-KAWzw8sTpXOsY,17713
34
34
  pkgs/argument_parser/case_convert.py,sha256=NuJLJUJRbyVb6_Slen4uqaStEHbcOS1d-hBBfDrrw-c,605
35
35
  pkgs/filesystem_utils/__init__.py,sha256=NSsQrUCoGISBCqCCyq6_583sYHTVEQeDjDO8hvZn3ag,1261
36
+ pkgs/filesystem_utils/_blob_session.py,sha256=CtoB7PIocuZo8vvFIS_Rc-YR6KwzFB0rHUVPKFEbRAI,4862
36
37
  pkgs/filesystem_utils/_gdrive_session.py,sha256=GJuZYJq1W4QQ_7OLvZIMK99FgRq8FxJHg6cMUx9prtA,11077
37
38
  pkgs/filesystem_utils/_local_session.py,sha256=xFEYhAvNqrOYqwt4jrEYOuYkjJn0zclZhTelW_Q1-rw,2325
38
39
  pkgs/filesystem_utils/_s3_session.py,sha256=_jLK5C-UElT5Sl5teM2pFR7fQe11NlhUd04EgwN9FRs,3962
39
40
  pkgs/filesystem_utils/_sftp_session.py,sha256=6zoF7YsEUp0GpyFb-BeIhUAWvbTK7IUjvPNJ1B0vEyI,4743
40
- pkgs/filesystem_utils/file_type_utils.py,sha256=Xd-mg35mAENUgNJVz5uK8nEfrUp-NQld_gnXFEq3K-8,1487
41
+ pkgs/filesystem_utils/file_type_utils.py,sha256=Au79J66Rh_Yyt_dXctsygPAg1Y4QRawqwRhbvnCpDIY,1933
41
42
  pkgs/filesystem_utils/filesystem_session.py,sha256=BQ2Go8Mu9-GcnaWh2Pm4x7ugLVsres6XrOQ8RoiEpcE,1045
42
43
  pkgs/serialization/__init__.py,sha256=LifasRW0a50A3qRFmo2bf3FQ6TXhZWOTz2-CVTgPjcQ,753
43
44
  pkgs/serialization/missing_sentry.py,sha256=aM_9KxbCk9dVvXvcOKgkIQBqFWvLhv8QlIUCiuFEXMo,806
44
45
  pkgs/serialization/opaque_key.py,sha256=FIfXEE0DA1U8R_taFbQ1RCoTSgehrPjP06-qvo-GeNQ,177
45
- pkgs/serialization/serial_class.py,sha256=anGUNEArLXjiEX3GoMNk7tn15LQWANXjxNrb5DqNK18,6252
46
+ pkgs/serialization/serial_class.py,sha256=fEceYVIV8dc3Ium-okYxp9cCNFg05HcliXqoCcV5Wcc,6151
46
47
  pkgs/serialization/serial_union.py,sha256=xpdeqCrRd0sNCaUwBQRzje6V40ndCbJpZrLX2K0d5xo,2741
47
48
  pkgs/serialization/yaml.py,sha256=yoJtu7_ixnJV6uTxA_U1PpK5F_ixT08AKVh5ocyYwXM,1466
48
49
  pkgs/serialization_util/__init__.py,sha256=MVKqHTUl2YnWZAFG9xCxu1SgmkQ5xPofrAGlYg6h7rI,330
@@ -67,7 +68,7 @@ pkgs/type_spec/test.py,sha256=4ueujBq-pEgnX3Z69HyPmD-bullFXmpixcpVzfOkhP4,489
67
68
  pkgs/type_spec/util.py,sha256=79SLJsSPVnBe2_3CTF6J-7-QD9nRr6o8MKvfjyx53eI,4864
68
69
  pkgs/type_spec/actions_registry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
69
70
  pkgs/type_spec/actions_registry/__main__.py,sha256=JGwKxcAmrQdbpVR2vwknoimN1Q-r5h4SADw1cYLYzgk,4331
70
- pkgs/type_spec/actions_registry/emit_typescript.py,sha256=Z1ZM4zOw26tvLspvW6Emg79-jxjhNBse-8yaionbmeo,6066
71
+ pkgs/type_spec/actions_registry/emit_typescript.py,sha256=nzqnZbI2OyFSQtyWxINfEsUBF-oLcfUJJnxxeIbzlLE,5990
71
72
  pkgs/type_spec/parts/base.py.prepart,sha256=St6P21VP8Um_nagZFuSyNzYiKDSrsK3TNOuGY_1O8cg,2186
72
73
  pkgs/type_spec/parts/base.ts.prepart,sha256=2FJJvpg2olCcavxj0nbYWdwKl6KeScour2JjSvN42l8,1001
73
74
  pkgs/type_spec/type_info/__main__.py,sha256=pmVjVqXyVh8vKTNCTFgz80Sg74C5BKToP3E6GS-X_So,857
@@ -99,14 +100,14 @@ uncountable/integration/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
99
100
  uncountable/integration/db/connect.py,sha256=mE3bdV0huclH2iT_dXCQdRL4LkjIuf_myAR64RTWXEs,498
100
101
  uncountable/integration/db/session.py,sha256=96cGQXpe6IugBTdSsjdP0S5yhJ6toSmbVB6qhc3FJzE,693
101
102
  uncountable/integration/executors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
102
- uncountable/integration/executors/executors.py,sha256=2OXFUJiG6hlkIB2NUZldobiBk9xSWuk7evvr5wlesG4,5119
103
+ uncountable/integration/executors/executors.py,sha256=Kzisp1eKufGCWrHIw4mmAj-l1UQ2oJsJR7I-_mksnVs,5441
103
104
  uncountable/integration/executors/generic_upload_executor.py,sha256=0vs9NVk1UL2FBhiMCH6o8p4KtVXNFNvv861QCOD3UU4,10375
104
105
  uncountable/integration/executors/script_executor.py,sha256=BBQ9f0l7uH2hgKf60jtm-pONzwk-EeOhM2qBAbv_URo,846
105
106
  uncountable/integration/queue_runner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
106
107
  uncountable/integration/queue_runner/job_scheduler.py,sha256=lhvcl11jyk_wg9BSc1Xyh5u8iEtsdqQFW83FPLupjQI,6189
107
108
  uncountable/integration/queue_runner/queue_runner.py,sha256=0BmYu5zHdothTevGsB-nXg6MBd1UD-WkP3h1WCKMdQg,710
108
109
  uncountable/integration/queue_runner/types.py,sha256=8qTq29BTSa5rmW6CBlBntP0pNIiDcwu1wHa78pjroS0,219
109
- uncountable/integration/queue_runner/worker.py,sha256=WRh_EeGyCPr72v4QL17m6Iu-ipB3T0mhc7nulILf59E,4535
110
+ uncountable/integration/queue_runner/worker.py,sha256=5bcfg_1tVlAenRDCD89gJI3Ru7ev-Sk7Gok5wjLYxnI,4588
110
111
  uncountable/integration/queue_runner/command_server/__init__.py,sha256=gQPVILGpWzCr2i5GJyoqna7AOSFvtn4tav69gB78mTQ,571
111
112
  uncountable/integration/queue_runner/command_server/command_client.py,sha256=DJb0TUVFkiiLBEQzHSN94sTRnuEbutNEgdN39XmnOXI,2046
112
113
  uncountable/integration/queue_runner/command_server/command_server.py,sha256=yyXryhiEC2eGS0yFElLGsVzSKwOuYvj-zp22jQorkv0,2138
@@ -143,7 +144,7 @@ uncountable/types/client_config_t.py,sha256=6dStfR0IEHiPW8f9_aF3DD_tHmXXw2rEVrgp
143
144
  uncountable/types/curves.py,sha256=W6uMpG5SyW1MS82szNpxkFEn1MnxNpBFyFbQb2Ysfng,366
144
145
  uncountable/types/curves_t.py,sha256=lKhRM-2cZ_sFaW7pa_I_Ipz_pJhm3_yTFehRXI79pKk,1416
145
146
  uncountable/types/entity.py,sha256=ECvhswTj9xp4gUEKTZoZYyxHvx1oyvE5FNiGNfSyUgk,528
146
- uncountable/types/entity_t.py,sha256=_a7maOh51nJPigkfdDipJ0XB9h8G4N6-VAWIQHNbh3o,16491
147
+ uncountable/types/entity_t.py,sha256=MPLE0CCrROH89JUE9qyI3MLL0DtVzsuXlJ8bG-x-K4A,16731
147
148
  uncountable/types/experiment_groups.py,sha256=_0OXcPzSAbkE-rfKt5tPx178YJ4pcEKZvrCxUHgDnvw,309
148
149
  uncountable/types/experiment_groups_t.py,sha256=qEs8YW0eJOJ_sCOObT5v9QRx9wsjLYpJqJhCJXa-vNA,721
149
150
  uncountable/types/field_values.py,sha256=uuIWX-xmfvcinYPdfkWJeb56zzQY01mc9rmotMPMh24,503
@@ -161,7 +162,7 @@ uncountable/types/input_attributes_t.py,sha256=mD9JIagE8TQ0KVwGkl-hinKz_gcunV3y3
161
162
  uncountable/types/inputs.py,sha256=jFZHyo0ZOGJ3bb4TOPXovhE3Fo1-kf7B7T3usk4Sqg8,467
162
163
  uncountable/types/inputs_t.py,sha256=CpuuKRduZGET_wvkGUpUFN6rbZCHsdOIp1veEM-hspI,2143
163
164
  uncountable/types/integration_server.py,sha256=61NuGs1pbgovU5Vuje7oN9HpLwOGCCw9Q_CcUvt_0qI,385
164
- uncountable/types/integration_server_t.py,sha256=wa45RWChRsj2oAvWcOfZXA2xZxyEEkzltuJqZwKTna8,1048
165
+ uncountable/types/integration_server_t.py,sha256=SSNM53lHyHpTjHUkXZowIs2hB7jSsNZyuJsiXldaISE,1234
165
166
  uncountable/types/job_definition.py,sha256=DEma_s-0oBo2tPI5u9IU_UDw-9MWbn4mTZsd_RHiYGE,1667
166
167
  uncountable/types/job_definition_t.py,sha256=whsat5825Jou1-67dbOkqiibpoavGcsaOhJ3APTUh2Q,7948
167
168
  uncountable/types/outputs.py,sha256=sUZx_X-TKCZtLm1YCEH8OISX9DdPlv9ZuUfM3-askCc,281
@@ -264,7 +265,7 @@ uncountable/types/api/recipes/clear_recipe_outputs.py,sha256=sZ6sWtdfOYp8ORQD9GA
264
265
  uncountable/types/api/recipes/create_recipe.py,sha256=Gh6Z_7wBfYBMGUgUSixw57ucRjkBhjScIjDg1__4rVU,1570
265
266
  uncountable/types/api/recipes/create_recipes.py,sha256=kmTDi0nF5OK5wYIErg_4CY3YsF3pDbrj4LLFqgDNRoU,1940
266
267
  uncountable/types/api/recipes/disassociate_recipe_as_input.py,sha256=YcLCle-yQ8A7hPmFg8wPfW4dyJwpMQXNKzJxCEr8xlw,1127
267
- uncountable/types/api/recipes/edit_recipe_inputs.py,sha256=7TBjzA8-yXYIko_jx0AvKMs8SnIkiQukhQFWyf2fkxk,7815
268
+ uncountable/types/api/recipes/edit_recipe_inputs.py,sha256=s6PdxXGPDZfsSxa8pEt4KD1f0Lg29Ka1k1r2pSZ0WOg,9949
268
269
  uncountable/types/api/recipes/get_column_calculation_values.py,sha256=u_KBiGpXnVLwRruEG-FmopiR8UDkuX6i47B4OY-IYWU,1702
269
270
  uncountable/types/api/recipes/get_curve.py,sha256=SPD9kx4m95KPXAD0MawX52IFl8W7gVKj-WmA4Wx2YtU,1126
270
271
  uncountable/types/api/recipes/get_recipe_calculations.py,sha256=_sBE5M2xzwagh1beTW32D_HTxqu9OrZTPZBGMba6Myk,1730
@@ -286,7 +287,7 @@ uncountable/types/api/triggers/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr
286
287
  uncountable/types/api/triggers/run_trigger.py,sha256=-oZgPyn43xEKSCs81DVNzwaYMCdRJxbM9GY6fsqKwf4,1090
287
288
  uncountable/types/api/uploader/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
288
289
  uncountable/types/api/uploader/invoke_uploader.py,sha256=6mwVG136oLp9JcbB2I-kZnrcm3aeZzYZB-SFjEImY2o,1314
289
- UncountablePythonSDK-0.0.85.dist-info/METADATA,sha256=zLE9vBAH4mB5W3w5LRqn-CkNRJk4KzJ3JoeSVQeeLSw,2054
290
- UncountablePythonSDK-0.0.85.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
291
- UncountablePythonSDK-0.0.85.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
292
- UncountablePythonSDK-0.0.85.dist-info/RECORD,,
290
+ UncountablePythonSDK-0.0.87.dist-info/METADATA,sha256=DIWpmh2PIjuruJvh55KC8Bu0uj7BmEYU83OvZWDpOQs,2064
291
+ UncountablePythonSDK-0.0.87.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
292
+ UncountablePythonSDK-0.0.87.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
293
+ UncountablePythonSDK-0.0.87.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.5.0)
2
+ Generator: setuptools (75.6.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -197,7 +197,7 @@ lint.ignore = [
197
197
  # ## FROM RUFF UPGRADE
198
198
  "PLC2701", # private name imports. should add
199
199
  "PLR1702", # too many nested blocks -- add with config. skip
200
- "RUF025", # unnecessary dict comprehension. skip
200
+ "C420", # unnecessary dict comprehension. skip
201
201
  "SIM113", # index variable should use enumerate. should add.
202
202
  "PLR0914" # too many local variables. configure below. should add with config
203
203
  ]
@@ -0,0 +1,137 @@
1
+ from io import BytesIO
2
+
3
+ from azure.storage.blob import BlobServiceClient, ContainerClient
4
+
5
+ from pkgs.filesystem_utils.file_type_utils import (
6
+ FileObjectData,
7
+ FileSystemBlobConfig,
8
+ FileSystemFileReference,
9
+ FileSystemObject,
10
+ FileTransfer,
11
+ IncompatibleFileReference,
12
+ )
13
+
14
+ from .filesystem_session import FileSystemSession
15
+
16
+
17
+ def _add_slash(prefix: str) -> str:
18
+ if len(prefix) > 0 and prefix[-1] != "/":
19
+ prefix = prefix + "/"
20
+ return prefix
21
+
22
+
23
+ class BlobSession(FileSystemSession):
24
+ config: FileSystemBlobConfig
25
+
26
+ def __init__(self, blob_config: FileSystemBlobConfig) -> None:
27
+ super().__init__()
28
+ self.config = blob_config
29
+
30
+ def start(self) -> None:
31
+ self.service_client: BlobServiceClient | None = BlobServiceClient(
32
+ self.config.account_url, credential=self.config.credential
33
+ )
34
+ self.container_client: ContainerClient | None = (
35
+ self.service_client.get_container_client(self.config.container)
36
+ )
37
+
38
+ def __enter__(self) -> "BlobSession":
39
+ self.start()
40
+ return self
41
+
42
+ def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None:
43
+ self.service_client = None
44
+ self.container_client = None
45
+
46
+ def list_files(
47
+ self,
48
+ dir_path: FileSystemObject,
49
+ *,
50
+ recursive: bool = False,
51
+ valid_extensions: list[str] | None = None,
52
+ ) -> list[FileSystemObject]:
53
+ if not isinstance(dir_path, FileSystemFileReference):
54
+ raise IncompatibleFileReference()
55
+
56
+ assert self.service_client is not None and self.container_client is not None, (
57
+ "call to list_files on uninitialized blob session"
58
+ )
59
+
60
+ filesystem_file_references: list[FileSystemObject] = []
61
+ prefix = _add_slash(dir_path.filepath)
62
+ for blob in self.container_client.list_blobs(name_starts_with=prefix):
63
+ if not recursive and (
64
+ blob.name == prefix or "/" in blob.name[len(prefix) :]
65
+ ):
66
+ continue
67
+ if valid_extensions is None or any(
68
+ blob.name.endswith(valid_extension)
69
+ for valid_extension in valid_extensions
70
+ ):
71
+ filesystem_file_references.append(
72
+ FileSystemFileReference(
73
+ filepath=blob.name,
74
+ )
75
+ )
76
+
77
+ return filesystem_file_references
78
+
79
+ def download_files(
80
+ self,
81
+ filepaths: list[FileSystemObject],
82
+ ) -> list[FileObjectData]:
83
+ downloaded_files: list[FileObjectData] = []
84
+ assert self.service_client is not None and self.container_client is not None, (
85
+ "call to download_files on uninitialized blob session"
86
+ )
87
+
88
+ for file_object in filepaths:
89
+ if (
90
+ not isinstance(file_object, FileSystemFileReference)
91
+ or file_object.filename is None
92
+ ):
93
+ raise IncompatibleFileReference()
94
+
95
+ blob_client = self.container_client.get_blob_client(file_object.filepath)
96
+ download_stream = blob_client.download_blob()
97
+ file_data = download_stream.readall()
98
+ downloaded_files.append(
99
+ FileObjectData(
100
+ file_data=file_data,
101
+ file_IO=BytesIO(file_data),
102
+ filename=file_object.filename,
103
+ filepath=file_object.filepath,
104
+ )
105
+ )
106
+
107
+ return downloaded_files
108
+
109
+ def move_files(self, file_mappings: list[FileTransfer]) -> None:
110
+ assert self.service_client is not None and self.container_client is not None, (
111
+ "call to move_files on uninitialized blob session"
112
+ )
113
+
114
+ for src_file, dest_file in file_mappings:
115
+ if not isinstance(src_file, FileSystemFileReference) or not isinstance(
116
+ dest_file, FileSystemFileReference
117
+ ):
118
+ raise IncompatibleFileReference()
119
+
120
+ source_blob_client = self.container_client.get_blob_client(
121
+ src_file.filepath
122
+ )
123
+ dest_blob_client = self.container_client.get_blob_client(dest_file.filepath)
124
+
125
+ dest_blob_client.start_copy_from_url(source_blob_client.url)
126
+ source_blob_client.delete_blob()
127
+
128
+ def delete_files(self, filepaths: list[FileSystemObject]) -> None:
129
+ assert self.service_client is not None and self.container_client is not None, (
130
+ "call to delete_files on uninitialized blob session"
131
+ )
132
+ for file_object in filepaths:
133
+ if not isinstance(file_object, FileSystemFileReference):
134
+ raise IncompatibleFileReference()
135
+
136
+ blob_client = self.container_client.get_blob_client(file_object.filepath)
137
+ blob_client.delete_blob()
@@ -4,6 +4,12 @@ from io import BytesIO
4
4
  from typing import Optional, Union
5
5
 
6
6
  import paramiko
7
+ from azure.core.credentials import (
8
+ AzureNamedKeyCredential,
9
+ AzureSasCredential,
10
+ TokenCredential,
11
+ )
12
+ from azure.storage.blob import ContainerProperties
7
13
 
8
14
 
9
15
  @dataclass
@@ -69,3 +75,17 @@ class FileSystemS3Config:
69
75
  access_key_id: Optional[str]
70
76
  secret_access_key: Optional[str]
71
77
  session_token: Optional[str]
78
+
79
+
80
+ @dataclass(kw_only=True)
81
+ class FileSystemBlobConfig:
82
+ account_url: str
83
+ credential: (
84
+ str
85
+ | dict[str, str]
86
+ | AzureNamedKeyCredential
87
+ | AzureSasCredential
88
+ | TokenCredential
89
+ | None
90
+ )
91
+ container: ContainerProperties | str
@@ -114,8 +114,6 @@ def _get_merged_serial_class_data(type_class: type[Any]) -> _SerialClassData | N
114
114
  for base in type_class.__bases__:
115
115
  curr_base_class_data = _get_merged_serial_class_data(base)
116
116
  if curr_base_class_data is not None:
117
- if base_class_data is None:
118
- base_class_data = _SerialClassData()
119
117
  base_class_data.unconverted_keys |= (
120
118
  curr_base_class_data.unconverted_keys
121
119
  )
@@ -111,9 +111,7 @@ def _get_argument_type(arguments: str | None) -> str | None:
111
111
  argument_parts = arguments.split(".")
112
112
  namespace = argument_parts[0]
113
113
  type_name = argument_parts[1]
114
- if namespace is not None and type_name is not None:
115
- return f"{_action_namespace_name(namespace)}.{type_name}"
116
- return None
114
+ return f"{_action_namespace_name(namespace)}.{type_name}"
117
115
 
118
116
 
119
117
  def _emit_action_definition(
@@ -9,6 +9,7 @@ from uncountable.types import (
9
9
  entity_t,
10
10
  field_values_t,
11
11
  identifier_t,
12
+ integration_server_t,
12
13
  job_definition_t,
13
14
  transition_entity_phase_t,
14
15
  )
@@ -33,7 +34,10 @@ def resolve_executor(
33
34
 
34
35
 
35
36
  def _create_run_entity(
36
- *, client: Client, logging_settings: job_definition_t.JobLoggingSettings
37
+ *,
38
+ client: Client,
39
+ logging_settings: job_definition_t.JobLoggingSettings,
40
+ job_uuid: str,
37
41
  ) -> entity_t.Entity:
38
42
  run_entity = client.create_entity(
39
43
  entity_type=entity_t.EntityType.ASYNC_JOB,
@@ -49,6 +53,10 @@ def _create_run_entity(
49
53
  field_ref_name=async_jobs_t.ASYNC_JOB_STATUS_FIELD_REF_NAME,
50
54
  value=async_jobs_t.AsyncJobStatus.IN_PROGRESS,
51
55
  ),
56
+ field_values_t.FieldRefNameValue(
57
+ field_ref_name=integration_server_t.INTEGRATION_SERVER_RUN_UUID_FIELD_REF_NAME,
58
+ value=job_uuid,
59
+ ),
52
60
  ],
53
61
  ).entity
54
62
  client.transition_entity_phase(
@@ -80,6 +88,7 @@ def execute_job(
80
88
  job_definition: job_definition_t.JobDefinition,
81
89
  profile_metadata: job_definition_t.ProfileMetadata,
82
90
  args: JobArguments,
91
+ job_uuid: str,
83
92
  ) -> job_definition_t.JobResult:
84
93
  with args.logger.push_scope(job_definition.name) as job_logger:
85
94
  job = resolve_executor(job_definition.executor, profile_metadata)
@@ -93,7 +102,9 @@ def execute_job(
93
102
  and job_definition.logging_settings.enabled
94
103
  ):
95
104
  run_entity = _create_run_entity(
96
- client=args.client, logging_settings=job_definition.logging_settings
105
+ client=args.client,
106
+ logging_settings=job_definition.logging_settings,
107
+ job_uuid=job_uuid,
97
108
  )
98
109
  result = job.run_outer(args=args)
99
110
  except Exception as e:
@@ -109,6 +109,7 @@ def run_queued_job(
109
109
  args=args,
110
110
  profile_metadata=job_details.profile_metadata,
111
111
  job_definition=job_details.job_definition,
112
+ job_uuid=queued_job.queued_job_uuid,
112
113
  )
113
114
  except Exception as e:
114
115
  job_logger.log_exception(e)
@@ -22,6 +22,9 @@ __all__: list[str] = [
22
22
  "Data",
23
23
  "ENDPOINT_METHOD",
24
24
  "ENDPOINT_PATH",
25
+ "PlaceholderBase",
26
+ "PlaceholderDisplayMode",
27
+ "PlaceholderType",
25
28
  "RecipeInputEdit",
26
29
  "RecipeInputEditAddInput",
27
30
  "RecipeInputEditAddInstructions",
@@ -29,11 +32,14 @@ __all__: list[str] = [
29
32
  "RecipeInputEditChangeBasisViewed",
30
33
  "RecipeInputEditClearInputs",
31
34
  "RecipeInputEditInputBase",
35
+ "RecipeInputEditPlaceholder",
32
36
  "RecipeInputEditSetLot",
33
37
  "RecipeInputEditSetRole",
34
38
  "RecipeInputEditType",
35
39
  "RecipeInputEditUpdateAnnotations",
36
40
  "RecipeInputEditUpsertInput",
41
+ "RecipeInputPlaceholder",
42
+ "SubcategoryPlaceholder",
37
43
  ]
38
44
 
39
45
  ENDPOINT_METHOD = "POST"
@@ -50,6 +56,7 @@ class RecipeInputEditType(StrEnum):
50
56
  CHANGE_BASIS = "change_basis"
51
57
  ADD_INSTRUCTIONS = "add_instructions"
52
58
  SET_ROLE = "set_role"
59
+ SET_PLACEHOLDER = "set_placeholder"
53
60
 
54
61
 
55
62
  # DO NOT MODIFY -- This file is generated by type_spec
@@ -177,9 +184,66 @@ class RecipeInputEditSetRole(RecipeInputEditBase):
177
184
  ingredient_role_key: typing.Optional[identifier_t.IdentifierKey]
178
185
 
179
186
 
187
+ # DO NOT MODIFY -- This file is generated by type_spec
188
+ class PlaceholderType(StrEnum):
189
+ SUBCATEGORY = "subcategory"
190
+
191
+
192
+ # DO NOT MODIFY -- This file is generated by type_spec
193
+ class PlaceholderDisplayMode(StrEnum):
194
+ INLINE = "inline"
195
+
196
+
197
+ # DO NOT MODIFY -- This file is generated by type_spec
198
+ @serial_class(
199
+ named_type_path="sdk.api.recipes.edit_recipe_inputs.PlaceholderBase",
200
+ )
201
+ @dataclasses.dataclass(kw_only=True)
202
+ class PlaceholderBase:
203
+ type: PlaceholderType
204
+ require_for_creation: bool = True
205
+ display_mode: PlaceholderDisplayMode = PlaceholderDisplayMode.INLINE
206
+
207
+
208
+ # DO NOT MODIFY -- This file is generated by type_spec
209
+ @serial_class(
210
+ named_type_path="sdk.api.recipes.edit_recipe_inputs.SubcategoryPlaceholder",
211
+ parse_require={"type"},
212
+ )
213
+ @dataclasses.dataclass(kw_only=True)
214
+ class SubcategoryPlaceholder(PlaceholderBase):
215
+ type: typing.Literal[PlaceholderType.SUBCATEGORY] = PlaceholderType.SUBCATEGORY
216
+ subcategory_identifier: identifier_t.IdentifierKey
217
+
218
+
219
+ # DO NOT MODIFY -- This file is generated by type_spec
220
+ RecipeInputPlaceholder = typing.Annotated[
221
+ typing.Union[SubcategoryPlaceholder],
222
+ serial_union_annotation(
223
+ named_type_path="sdk.api.recipes.edit_recipe_inputs.RecipeInputPlaceholder",
224
+ discriminator="type",
225
+ discriminator_map={
226
+ "subcategory": SubcategoryPlaceholder,
227
+ },
228
+ ),
229
+ ]
230
+
231
+
232
+ # DO NOT MODIFY -- This file is generated by type_spec
233
+ @serial_class(
234
+ named_type_path="sdk.api.recipes.edit_recipe_inputs.RecipeInputEditPlaceholder",
235
+ parse_require={"type"},
236
+ )
237
+ @dataclasses.dataclass(kw_only=True)
238
+ class RecipeInputEditPlaceholder(RecipeInputEditBase):
239
+ type: typing.Literal[RecipeInputEditType.SET_PLACEHOLDER] = RecipeInputEditType.SET_PLACEHOLDER
240
+ ingredient_key: identifier_t.IdentifierKey
241
+ placeholder: RecipeInputPlaceholder
242
+
243
+
180
244
  # DO NOT MODIFY -- This file is generated by type_spec
181
245
  RecipeInputEdit = typing.Annotated[
182
- typing.Union[RecipeInputEditClearInputs, RecipeInputEditUpsertInput, RecipeInputEditAddInput, RecipeInputEditUpdateAnnotations, RecipeInputEditSetLot, RecipeInputEditChangeBasisViewed, RecipeInputEditAddInstructions, RecipeInputEditSetRole],
246
+ typing.Union[RecipeInputEditClearInputs, RecipeInputEditUpsertInput, RecipeInputEditAddInput, RecipeInputEditUpdateAnnotations, RecipeInputEditSetLot, RecipeInputEditChangeBasisViewed, RecipeInputEditAddInstructions, RecipeInputEditSetRole, RecipeInputEditPlaceholder],
183
247
  serial_union_annotation(
184
248
  named_type_path="sdk.api.recipes.edit_recipe_inputs.RecipeInputEdit",
185
249
  discriminator="type",
@@ -192,6 +256,7 @@ RecipeInputEdit = typing.Annotated[
192
256
  "change_basis": RecipeInputEditChangeBasisViewed,
193
257
  "add_instructions": RecipeInputEditAddInstructions,
194
258
  "set_role": RecipeInputEditSetRole,
259
+ "set_placeholder": RecipeInputEditPlaceholder,
195
260
  },
196
261
  ),
197
262
  ]
@@ -60,6 +60,7 @@ __all__: list[str] = [
60
60
  "equipment_maintenance": "Equipment Maintenance",
61
61
  "equipment_type": "Equipment Type",
62
62
  "experiment_group": "Experiment Group",
63
+ "experiment_group_member": "Experiment Group Member",
63
64
  "experiment_group_type": "Experiment Group Type",
64
65
  "favorite_link": "Favorite Link",
65
66
  "field_option_set": "Field Option Set",
@@ -89,6 +90,7 @@ __all__: list[str] = [
89
90
  "license": "License",
90
91
  "maintenance_schedule": "Maintenance Schedule",
91
92
  "material_family": "Material Family",
93
+ "measurement_group_recipe": "Measurement Group Recipe",
92
94
  "ml_job": "Batch Processing Job",
93
95
  "naming_scheme": "Naming Scheme",
94
96
  "naming_counter": "Naming Counter",
@@ -217,6 +219,7 @@ class EntityType(StrEnum):
217
219
  EQUIPMENT_MAINTENANCE = "equipment_maintenance"
218
220
  EQUIPMENT_TYPE = "equipment_type"
219
221
  EXPERIMENT_GROUP = "experiment_group"
222
+ EXPERIMENT_GROUP_MEMBER = "experiment_group_member"
220
223
  EXPERIMENT_GROUP_TYPE = "experiment_group_type"
221
224
  FAVORITE_LINK = "favorite_link"
222
225
  FIELD_OPTION_SET = "field_option_set"
@@ -246,6 +249,7 @@ class EntityType(StrEnum):
246
249
  LICENSE = "license"
247
250
  MAINTENANCE_SCHEDULE = "maintenance_schedule"
248
251
  MATERIAL_FAMILY = "material_family"
252
+ MEASUREMENT_GROUP_RECIPE = "measurement_group_recipe"
249
253
  ML_JOB = "ml_job"
250
254
  NAMING_SCHEME = "naming_scheme"
251
255
  NAMING_COUNTER = "naming_counter"
@@ -15,6 +15,7 @@ from . import client_config_t
15
15
 
16
16
  __all__: list[str] = [
17
17
  "EnvironmentConfig",
18
+ "INTEGRATION_SERVER_RUN_UUID_FIELD_REF_NAME",
18
19
  "IntegrationEnvironment",
19
20
  ]
20
21
 
@@ -34,4 +35,8 @@ class EnvironmentConfig:
34
35
  auth_retrieval: auth_retrieval_t.AuthRetrieval
35
36
  base_url: str
36
37
  client_options: typing.Optional[client_config_t.ClientConfigOptions] = None
38
+
39
+
40
+ # DO NOT MODIFY -- This file is generated by type_spec
41
+ INTEGRATION_SERVER_RUN_UUID_FIELD_REF_NAME = "unc_integration_server_run_uuid"
37
42
  # DO NOT MODIFY -- This file is generated by type_spec
@@ -1,60 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: UncountablePythonSDK
3
- Version: 0.0.85
4
- Summary: Uncountable SDK
5
- Project-URL: Homepage, https://github.com/uncountableinc/uncountable-python-sdk
6
- Project-URL: Repository, https://github.com/uncountableinc/uncountable-python-sdk.git
7
- Project-URL: Issues, https://github.com/uncountableinc/uncountable-python-sdk/issues
8
- Keywords: uncountable,sdk,api,uncountable-sdk
9
- Classifier: Development Status :: 4 - Beta
10
- Classifier: Environment :: Console
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Operating System :: OS Independent
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: Topic :: Software Development
15
- Classifier: Topic :: Utilities
16
- Classifier: Typing :: Typed
17
- Requires-Python: >=3.11
18
- Description-Content-Type: text/markdown
19
- Requires-Dist: aiotus ==1.*
20
- Requires-Dist: aiohttp ==3.*
21
- Requires-Dist: requests ==2.*
22
- Requires-Dist: SQLAlchemy >=1.4.0
23
- Requires-Dist: APScheduler ==3.*
24
- Requires-Dist: python-dateutil ==2.*
25
- Requires-Dist: shelljob ==0.*
26
- Requires-Dist: PyYAML ==6.*
27
- Requires-Dist: google-api-python-client ==2.*
28
- Requires-Dist: tqdm ==4.*
29
- Requires-Dist: pysftp ==0.*
30
- Requires-Dist: opentelemetry-api ==1.*
31
- Requires-Dist: opentelemetry-exporter-otlp-proto-common ==1.*
32
- Requires-Dist: opentelemetry-exporter-otlp-proto-http ==1.*
33
- Requires-Dist: opentelemetry-sdk ==1.*
34
- Requires-Dist: paramiko ==3.*
35
- Requires-Dist: boto3 ==1.*
36
- Requires-Dist: flask ==3.*
37
- Requires-Dist: simplejson ==3.*
38
- Requires-Dist: grpcio ==1.67.1
39
- Requires-Dist: protobuf ==4.*
40
- Provides-Extra: test
41
- Requires-Dist: mypy ==1.* ; extra == 'test'
42
- Requires-Dist: ruff ==0.* ; extra == 'test'
43
- Requires-Dist: pytest ==8.* ; extra == 'test'
44
- Requires-Dist: coverage[toml] ==7.* ; extra == 'test'
45
- Requires-Dist: pytest-cov ==5.* ; extra == 'test'
46
- Requires-Dist: pytest-xdist ==3.* ; extra == 'test'
47
-
48
- # Uncountable Python SDK
49
-
50
- ## Documentation
51
- [https://uncountableinc.github.io/uncountable-python-sdk/](https://uncountableinc.github.io/uncountable-python-sdk/)
52
-
53
- ## Installation
54
-
55
- Install from PyPI:
56
-
57
- ```console
58
- pip install UncountablePythonSDK
59
- ```
60
-