zipher-sdk 0.3.9__tar.gz → 0.4.2__tar.gz

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 (25) hide show
  1. {zipher-sdk-0.3.9/zipher_sdk.egg-info → zipher-sdk-0.4.2}/PKG-INFO +1 -1
  2. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/setup.py +2 -2
  3. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/tests/test_acl/test_acl.py +63 -0
  4. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/tests/test_client.py +0 -3
  5. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher/acl/models.py +1 -0
  6. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher/acl/zipher_installer.py +28 -2
  7. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher/cli.py +9 -1
  8. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2/zipher_sdk.egg-info}/PKG-INFO +1 -1
  9. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher_sdk.egg-info/requires.txt +1 -1
  10. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/LICENSE +0 -0
  11. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/README.md +0 -0
  12. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/setup.cfg +0 -0
  13. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/tests/__init__.py +0 -0
  14. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/tests/test_acl/__init__.py +0 -0
  15. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/tests/test_acl/sample_job.py +0 -0
  16. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher/__init__.py +0 -0
  17. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher/acl/__init__.py +0 -0
  18. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher/client.py +0 -0
  19. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher/exceptions.py +0 -0
  20. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher/models.py +0 -0
  21. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher/utils.py +0 -0
  22. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher_sdk.egg-info/SOURCES.txt +0 -0
  23. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher_sdk.egg-info/dependency_links.txt +0 -0
  24. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher_sdk.egg-info/entry_points.txt +0 -0
  25. {zipher-sdk-0.3.9 → zipher-sdk-0.4.2}/zipher_sdk.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zipher-sdk
3
- Version: 0.3.9
3
+ Version: 0.4.2
4
4
  Summary: Python SDK for programmatic access to the Zipher API
5
5
  Home-page: UNKNOWN
6
6
  Author: Zipher Inc.
@@ -8,7 +8,7 @@ with open('README.md', encoding='utf-8') as f:
8
8
 
9
9
  setup(
10
10
  name='zipher-sdk',
11
- version='0.3.9',
11
+ version='0.4.2',
12
12
  description='Python SDK for programmatic access to the Zipher API',
13
13
  long_description=long_description,
14
14
  long_description_content_type='text/markdown',
@@ -16,7 +16,7 @@ setup(
16
16
  author_email='yoav@zipher.cloud',
17
17
  packages=find_packages(),
18
18
  install_requires=[
19
- "pydantic>=2.7.3,<3.0.0",
19
+ "pydantic==2.11.3",
20
20
  "requests==2.32.3",
21
21
  "databricks-sdk==0.52.0"
22
22
  ],
@@ -351,3 +351,66 @@ class TestACL(TestCase):
351
351
  cls.admin_dbx.dbfs.delete(path=f'dbfs:/{cls.dbfs_test_folder}', recursive=True)
352
352
  cls.admin_dbx.queries.delete(cls.query.id)
353
353
  cls.admin_dbx.warehouses.delete(cls.warehouse.id)
354
+
355
+
356
+ class TestExistingServicePrincipal(TestCase):
357
+ """Separate test class for testing existing_service_principal_id without running expensive setUpClass."""
358
+
359
+ @classmethod
360
+ def setUpClass(cls):
361
+ cls.host = os.environ['DATABRICKS_HOST_TEST']
362
+ cls.admin_dbx: WorkspaceClient = WorkspaceClient(host=cls.host, token=os.environ['DATABRICKS_TOKEN_TEST'])
363
+
364
+ cls.test_uuid = uuid4()
365
+ entitlements = [iam.ComplexValue(value=ent) for ent in ['allow-cluster-create']]
366
+ groups = [iam.ComplexValue(value=g.id) for g in cls.admin_dbx.groups.list() if g.display_name in ['users']]
367
+ cls.sp = cls.admin_dbx.service_principals.create(display_name=f"test_existing_sp_{cls.test_uuid}",
368
+ groups=groups,
369
+ entitlements=entitlements)
370
+
371
+ permissions_to_grant = [TokenAccessControlRequest(service_principal_name=cls.sp.application_id,
372
+ permission_level=TokenPermissionLevel.CAN_USE)]
373
+ cls.admin_dbx.token_management.update_permissions(access_control_list=permissions_to_grant)
374
+
375
+ def test_existing_service_principal(self):
376
+ """Test that setup uses existing service principal when existing_service_principal_id is provided and doesn't create a new one."""
377
+ test_uuid = uuid4()
378
+ zipher_sp_name = f'zipher_sp_existing_{test_uuid}'
379
+
380
+ installer_config = InstallerConfig(
381
+ zipher_group_name=f'zipher_existing_sp_{test_uuid}',
382
+ zipher_service_principal_name=zipher_sp_name,
383
+ zipher_workspace_dir_path=f'/zipher_script_existing_{test_uuid}',
384
+ zipher_secret_scope_name=f'zipher_existing_{test_uuid}'
385
+ )
386
+ installer = ZipherInstaller(host=self.host, token=os.environ['DATABRICKS_TOKEN_TEST'], config=installer_config)
387
+
388
+ user_config = InstallerUserConfig(
389
+ jobs_list=[],
390
+ existing_service_principal_id=self.sp.id
391
+ )
392
+ zipher_creds = installer.setup(user_config=user_config, skip_approval=True)
393
+
394
+ assert zipher_creds is not None, "Setup should return credentials"
395
+ zipher_sps = [sp for sp in self.admin_dbx.service_principals.list() if sp.display_name == zipher_sp_name]
396
+ assert len(zipher_sps) == 0, "Should not create new SP when using existing_service_principal_id"
397
+
398
+ # Verify existing SP is in the zipher group
399
+ group = [g for g in self.admin_dbx.groups.list() if g.display_name == installer_config.zipher_group_name][0]
400
+ group_details = self.admin_dbx.groups.get(id=group.id)
401
+ sp_in_group = any(member.value == self.sp.id for member in (group_details.members or []))
402
+ assert sp_in_group, f"Existing SP {self.sp.id} should be in group {installer_config.zipher_group_name}"
403
+
404
+ # Verify existing SP has token permissions
405
+ token_perms = self.admin_dbx.token_management.get_permissions()
406
+ sp_has_token_perms = any(
407
+ hasattr(perm, 'service_principal_name') and perm.service_principal_name == self.sp.application_id
408
+ for perm in token_perms.access_control_list
409
+ )
410
+ assert sp_has_token_perms, f"Existing SP {self.sp.application_id} should have token permissions"
411
+
412
+ installer.clean_resources()
413
+
414
+ @classmethod
415
+ def tearDownClass(cls):
416
+ cls.admin_dbx.service_principals.delete(id=cls.sp.id)
@@ -49,9 +49,6 @@ class TestClient(unittest.TestCase):
49
49
  res = self.client.update_existing_conf(job_id="1234", existing_conf=config_to_update)
50
50
  self.assertDictEqual(config_to_update, res)
51
51
 
52
- def test_pydantic_version(self):
53
- import pydantic
54
- print(pydantic.__version__)
55
52
 
56
53
  if __name__ == '__main__':
57
54
  unittest.main()
@@ -47,3 +47,4 @@ class InstallerUserConfig(BaseModel, extra='forbid', use_enum_values=True):
47
47
  jobs_list : Optional[List[str]] = None
48
48
  jobs_permission: JobPermissions = JobPermissions.CAN_MANAGE
49
49
  service_principal_token_type: ServicePrincipalTokenType = ServicePrincipalTokenType.OAUTH
50
+ existing_service_principal_id: Optional[str] = None
@@ -219,6 +219,29 @@ class ZipherInstaller:
219
219
 
220
220
  return service_principal
221
221
 
222
+ def _use_existing_service_principal(self, sp_id: str, zipher_group: Group) -> ServicePrincipal:
223
+ """Use an existing service principal and add it to the zipher group with token permissions."""
224
+ sp = self.dbx_client.service_principals.get(id=sp_id)
225
+ print(f"\nUsing existing service principal: {sp.display_name} (id={sp.id})")
226
+
227
+ # Add existing SP to the zipher group (same as in _create_service_principal)
228
+ existing_groups = [g.value for g in sp.groups] if sp.groups else []
229
+ if zipher_group.id not in existing_groups:
230
+ updated_groups = existing_groups + [zipher_group.id]
231
+ self.dbx_client.service_principals.update(
232
+ id=sp.id,
233
+ display_name=sp.display_name,
234
+ groups=[iam.ComplexValue(value=gid) for gid in updated_groups]
235
+ )
236
+
237
+ # Grant token permissions (same logic as _create_service_principal lines 214-218)
238
+ permissions_to_grant = [TokenAccessControlRequest(service_principal_name=sp.application_id,
239
+ permission_level=TokenPermissionLevel.CAN_USE)]
240
+ self.dbx_client.token_management.update_permissions(access_control_list=permissions_to_grant)
241
+ print(f"Granted token permissions to existing service principal\n")
242
+
243
+ return sp
244
+
222
245
  def _create_oauth_secret_for_sp(self, sp: ServicePrincipal):
223
246
  token = self.dbx_client.config.token or self.dbx_client.config.oauth_token().access_token
224
247
 
@@ -317,8 +340,11 @@ class ZipherInstaller:
317
340
 
318
341
  self._enable_job_permissions(user_config=user_config)
319
342
 
320
- sp = self._create_service_principal(name=self.config.zipher_service_principal_name,
321
- zipher_group=group)
343
+ if user_config.existing_service_principal_id:
344
+ sp = self._use_existing_service_principal(user_config.existing_service_principal_id, group)
345
+ else:
346
+ sp = self._create_service_principal(name=self.config.zipher_service_principal_name,
347
+ zipher_group=group)
322
348
 
323
349
  self._add_unity_catalog_permissions(sp)
324
350
 
@@ -106,6 +106,13 @@ def main():
106
106
  help="Skip user input approval."
107
107
  )
108
108
 
109
+ setup_parser.add_argument(
110
+ "--existing-sp-id",
111
+ type=str,
112
+ default=None,
113
+ help="Use existing service principal by ID instead of creating a new one."
114
+ )
115
+
109
116
  remove_parser = subparsers.add_parser("remove", help="Remove Zipher resources from the workspace.", parents=[parent_parser])
110
117
 
111
118
  args = parser.parse_args()
@@ -127,7 +134,8 @@ def main():
127
134
  max_runs=args.max_runs,
128
135
  jobs_list=args.jobs_list,
129
136
  jobs_permission=JobPermissions.CAN_VIEW if args.readonly else JobPermissions.CAN_MANAGE,
130
- service_principal_token_type=ServicePrincipalTokenType.PERSONAL_ACCESS_TOKEN if args.pat else ServicePrincipalTokenType.OAUTH
137
+ service_principal_token_type=ServicePrincipalTokenType.PERSONAL_ACCESS_TOKEN if args.pat else ServicePrincipalTokenType.OAUTH,
138
+ existing_service_principal_id=args.existing_sp_id
131
139
  )
132
140
 
133
141
  zipher_token = zipher_acl.setup(user_config=config, skip_approval=args.skip_approval)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zipher-sdk
3
- Version: 0.3.9
3
+ Version: 0.4.2
4
4
  Summary: Python SDK for programmatic access to the Zipher API
5
5
  Home-page: UNKNOWN
6
6
  Author: Zipher Inc.
@@ -1,3 +1,3 @@
1
- pydantic<3.0.0,>=2.7.3
1
+ pydantic==2.11.3
2
2
  requests==2.32.3
3
3
  databricks-sdk==0.52.0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes