pyPreservica 3.0.1__tar.gz → 3.0.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.

Potentially problematic release.


This version of pyPreservica might be problematic. Click here for more details.

Files changed (48) hide show
  1. {pypreservica-3.0.1 → pypreservica-3.0.2}/PKG-INFO +13 -2
  2. {pypreservica-3.0.1 → pypreservica-3.0.2}/pyPreservica/__init__.py +1 -1
  3. {pypreservica-3.0.1 → pypreservica-3.0.2}/pyPreservica/common.py +3 -0
  4. {pypreservica-3.0.1 → pypreservica-3.0.2}/pyPreservica/entityAPI.py +40 -4
  5. {pypreservica-3.0.1 → pypreservica-3.0.2}/pyPreservica/uploadAPI.py +27 -2
  6. {pypreservica-3.0.1 → pypreservica-3.0.2}/pyPreservica.egg-info/PKG-INFO +13 -2
  7. {pypreservica-3.0.1 → pypreservica-3.0.2}/setup.py +2 -2
  8. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_bitstream.py +8 -0
  9. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_users.py +32 -4
  10. {pypreservica-3.0.1 → pypreservica-3.0.2}/LICENSE.txt +0 -0
  11. {pypreservica-3.0.1 → pypreservica-3.0.2}/README.md +0 -0
  12. {pypreservica-3.0.1 → pypreservica-3.0.2}/pyPreservica/adminAPI.py +0 -0
  13. {pypreservica-3.0.1 → pypreservica-3.0.2}/pyPreservica/authorityAPI.py +0 -0
  14. {pypreservica-3.0.1 → pypreservica-3.0.2}/pyPreservica/contentAPI.py +0 -0
  15. {pypreservica-3.0.1 → pypreservica-3.0.2}/pyPreservica/mdformsAPI.py +0 -0
  16. {pypreservica-3.0.1 → pypreservica-3.0.2}/pyPreservica/monitorAPI.py +0 -0
  17. {pypreservica-3.0.1 → pypreservica-3.0.2}/pyPreservica/opex.py +0 -0
  18. {pypreservica-3.0.1 → pypreservica-3.0.2}/pyPreservica/parAPI.py +0 -0
  19. {pypreservica-3.0.1 → pypreservica-3.0.2}/pyPreservica/retentionAPI.py +0 -0
  20. {pypreservica-3.0.1 → pypreservica-3.0.2}/pyPreservica/webHooksAPI.py +0 -0
  21. {pypreservica-3.0.1 → pypreservica-3.0.2}/pyPreservica/workflowAPI.py +0 -0
  22. {pypreservica-3.0.1 → pypreservica-3.0.2}/pyPreservica.egg-info/SOURCES.txt +0 -0
  23. {pypreservica-3.0.1 → pypreservica-3.0.2}/pyPreservica.egg-info/dependency_links.txt +0 -0
  24. {pypreservica-3.0.1 → pypreservica-3.0.2}/pyPreservica.egg-info/requires.txt +0 -0
  25. {pypreservica-3.0.1 → pypreservica-3.0.2}/pyPreservica.egg-info/top_level.txt +0 -0
  26. {pypreservica-3.0.1 → pypreservica-3.0.2}/setup.cfg +0 -0
  27. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_authority_records.py +0 -0
  28. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_children.py +0 -0
  29. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_content_api.py +0 -0
  30. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_crawl_fs.py +0 -0
  31. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_delete.py +0 -0
  32. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_download.py +0 -0
  33. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_entity.py +0 -0
  34. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_export_opex.py +0 -0
  35. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_groups.py +0 -0
  36. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_identifier.py +0 -0
  37. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_ingest.py +0 -0
  38. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_integrity_check.py +0 -0
  39. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_metadata.py +0 -0
  40. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_par.py +0 -0
  41. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_replace.py +0 -0
  42. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_retention.py +0 -0
  43. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_schema.py +0 -0
  44. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_security.py +0 -0
  45. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_thumbnail.py +0 -0
  46. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_upload.py +0 -0
  47. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_workflow.py +0 -0
  48. {pypreservica-3.0.1 → pypreservica-3.0.2}/tests/test_xml_metadata.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: pyPreservica
3
- Version: 3.0.1
3
+ Version: 3.0.2
4
4
  Summary: Python library for the Preservica API
5
5
  Home-page: https://pypreservica.readthedocs.io/
6
6
  Author: James Carr
@@ -30,6 +30,17 @@ Requires-Dist: s3transfer
30
30
  Requires-Dist: azure-storage-blob
31
31
  Requires-Dist: tqdm
32
32
  Requires-Dist: pyotp
33
+ Dynamic: author
34
+ Dynamic: author-email
35
+ Dynamic: classifier
36
+ Dynamic: description
37
+ Dynamic: description-content-type
38
+ Dynamic: home-page
39
+ Dynamic: keywords
40
+ Dynamic: license
41
+ Dynamic: project-url
42
+ Dynamic: requires-dist
43
+ Dynamic: summary
33
44
 
34
45
 
35
46
  # pyPreservica
@@ -23,6 +23,6 @@ from .mdformsAPI import MetadataGroupsAPI, Group, GroupField, GroupFieldType
23
23
  __author__ = "James Carr (drjamescarr@gmail.com)"
24
24
 
25
25
  # Version of the pyPreservica package
26
- __version__ = "3.0.1"
26
+ __version__ = "3.0.2"
27
27
 
28
28
  __license__ = "Apache License Version 2.0"
@@ -405,6 +405,9 @@ class Bitstream:
405
405
  self.length = int(length)
406
406
  self.fixity = fixity
407
407
  self.content_url = content_url
408
+ self.bs_index = None
409
+ self.gen_index = None
410
+ self.co_ref = None
408
411
 
409
412
  def __str__(self):
410
413
  return f"""
@@ -8,7 +8,7 @@ author: James Carr
8
8
  licence: Apache License 2.0
9
9
 
10
10
  """
11
- import hashlib
11
+
12
12
  import os.path
13
13
  import uuid
14
14
  import xml.etree.ElementTree
@@ -118,6 +118,38 @@ class EntityAPI(AuthenticatedAPI):
118
118
  logger.error(exception)
119
119
  raise exception
120
120
 
121
+ def bitstream_location(self, bitstream: Bitstream):
122
+ """"
123
+ Retrieves information about a bitstreams storage locations
124
+ """
125
+ if not isinstance(bitstream, Bitstream):
126
+ logger.error("bitstream argument is not a Bitstream object")
127
+ raise RuntimeError("bitstream argument is not a Bitstream object")
128
+
129
+ storage_locations = []
130
+
131
+ url: str = f'{self.protocol}://{self.server}/api/entity/content-objects/{bitstream.co_ref}/generations/{bitstream.gen_index}/bitstreams/{bitstream.bs_index}/storage-locations'
132
+
133
+ with self.session.get(url, headers={HEADER_TOKEN: self.token}, stream=True) as request:
134
+ if request.status_code == requests.codes.unauthorized:
135
+ self.token = self.__token__()
136
+ return self.bitstream_location(bitstream)
137
+ elif request.status_code == requests.codes.ok:
138
+ xml_response = str(request.content.decode('utf-8'))
139
+ entity_response = xml.etree.ElementTree.fromstring(xml_response)
140
+ logger.debug(xml_response)
141
+ locations = entity_response.find(f'.//{{{self.entity_ns}}}StorageLocation')
142
+ for adapter in locations:
143
+ storage_locations.append(adapter.attrib['name'])
144
+ else:
145
+ exception = HTTPException(bitstream.filename, request.status_code, request.url, "bitstream_location",
146
+ request.content.decode('utf-8'))
147
+ logger.error(exception)
148
+ raise exception
149
+
150
+ return storage_locations
151
+
152
+
121
153
  def bitstream_content(self, bitstream: Bitstream, filename: str, chunk_size: int = CHUNK_SIZE) -> Union[int, None]:
122
154
  """
123
155
  Download a file represented as a Bitstream to a local filename
@@ -1474,7 +1506,7 @@ class EntityAPI(AuthenticatedAPI):
1474
1506
  logger.error(exception)
1475
1507
  raise exception
1476
1508
 
1477
- def generation(self, url: str) -> Generation:
1509
+ def generation(self, url: str, content_ref: str = None) -> Generation:
1478
1510
  """
1479
1511
  Retrieve a list of generation objects
1480
1512
 
@@ -1524,7 +1556,11 @@ class EntityAPI(AuthenticatedAPI):
1524
1556
  bitstreams = entity_response.findall(f'./{{{self.entity_ns}}}Bitstreams/{{{self.entity_ns}}}Bitstream')
1525
1557
  bitstream_list = []
1526
1558
  for bit in bitstreams:
1527
- bitstream_list.append(self.bitstream(bit.text))
1559
+ bs: Bitstream = self.bitstream(bit.text)
1560
+ bs.gen_index = index
1561
+ if content_ref is not None:
1562
+ bs.co_ref = content_ref
1563
+ bitstream_list.append(bs)
1528
1564
  generation = Generation(strtobool(ge.attrib['original']), strtobool(ge.attrib['active']),
1529
1565
  format_group.text if hasattr(format_group, 'text') else None,
1530
1566
  effective_date.text if hasattr(effective_date, 'text') else None,
@@ -1741,7 +1777,7 @@ class EntityAPI(AuthenticatedAPI):
1741
1777
  result = []
1742
1778
  for g in generations:
1743
1779
  if hasattr(g, 'text'):
1744
- generation = self.generation(g.text)
1780
+ generation = self.generation(g.text, content_object.reference)
1745
1781
  generation.asset = content_object.asset
1746
1782
  generation.content_object = content_object
1747
1783
  generation.representation_type = content_object.representation_type
@@ -1659,7 +1659,22 @@ class UploadAPI(AuthenticatedAPI):
1659
1659
 
1660
1660
  def crawl_filesystem(self, filesystem_path, bucket_name, preservica_parent, callback: bool = False,
1661
1661
  security_tag: str = "open",
1662
- delete_after_upload: bool = True, max_MB_ingested: int = -1):
1662
+ delete_after_upload: bool = True, max_MB_ingested: int = -1, max_workflows = 8):
1663
+ """
1664
+ Crawl a filesystem and upload the contents to Preservica, the filesystem structure is mirrored in Preservica
1665
+
1666
+
1667
+ :param str filesystem_path: The path to the filesystem to crawl
1668
+ :param str bucket_name: The name of the bucket to upload to, use None to send directly to Preservica
1669
+ :param str preservica_parent: The parent folder in Preservica to upload to
1670
+ :param bool callback: Show upload progress bar
1671
+ :param str security_tag: The security tag to apply to the uploaded assets
1672
+ :param bool delete_after_upload: Delete the local copy of the package after the upload has completed
1673
+ :param int max_MB_ingested: The maximum number of MB to ingest
1674
+ :param int max_workflows: The maximum number of workflows to run concurrently
1675
+
1676
+
1677
+ """
1663
1678
 
1664
1679
  def get_parent(client, identifier, parent_reference):
1665
1680
  id = str(os.path.dirname(identifier))
@@ -1684,12 +1699,17 @@ class UploadAPI(AuthenticatedAPI):
1684
1699
  folder = entities.pop()
1685
1700
  return folder
1686
1701
 
1687
- from pyPreservica import EntityAPI
1702
+ from pyPreservica import EntityAPI, WorkflowAPI
1688
1703
  entity_client = EntityAPI(username=self.username, password=self.password, server=self.server,
1689
1704
  tenant=self.tenant,
1690
1705
  two_fa_secret_key=self.two_fa_secret_key, use_shared_secret=self.shared_secret,
1691
1706
  protocol=self.protocol)
1692
1707
 
1708
+ workflow_client = WorkflowAPI(username=self.username, password=self.password, server=self.server,
1709
+ tenant=self.tenant,
1710
+ two_fa_secret_key=self.two_fa_secret_key, use_shared_secret=self.shared_secret,
1711
+ protocol=self.protocol)
1712
+
1693
1713
  if preservica_parent:
1694
1714
  parent = entity_client.folder(preservica_parent)
1695
1715
  logger.info(f"Folders will be created inside Preservica collection {parent.title}")
@@ -1732,6 +1752,11 @@ class UploadAPI(AuthenticatedAPI):
1732
1752
  else:
1733
1753
  progress_display = None
1734
1754
 
1755
+ workflow_queue_length = len(list(workflow_client.workflow_instances(workflow_state="Active", workflow_type="Ingest")))
1756
+ while workflow_queue_length > max_workflows:
1757
+ sleep(30)
1758
+ workflow_queue_length = len(list(workflow_client.workflow_instances(workflow_state="Active", workflow_type="Ingest")))
1759
+
1735
1760
  if bucket_name is None:
1736
1761
  self.upload_zip_package(path_to_zip_package=package, callback=progress_display,
1737
1762
  delete_after_upload=delete_after_upload)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: pyPreservica
3
- Version: 3.0.1
3
+ Version: 3.0.2
4
4
  Summary: Python library for the Preservica API
5
5
  Home-page: https://pypreservica.readthedocs.io/
6
6
  Author: James Carr
@@ -30,6 +30,17 @@ Requires-Dist: s3transfer
30
30
  Requires-Dist: azure-storage-blob
31
31
  Requires-Dist: tqdm
32
32
  Requires-Dist: pyotp
33
+ Dynamic: author
34
+ Dynamic: author-email
35
+ Dynamic: classifier
36
+ Dynamic: description
37
+ Dynamic: description-content-type
38
+ Dynamic: home-page
39
+ Dynamic: keywords
40
+ Dynamic: license
41
+ Dynamic: project-url
42
+ Dynamic: requires-dist
43
+ Dynamic: summary
33
44
 
34
45
 
35
46
  # pyPreservica
@@ -13,7 +13,7 @@ PKG = "pyPreservica"
13
13
 
14
14
  # 'setup.py publish' shortcut.
15
15
  if sys.argv[-1] == 'publish':
16
- os.system('python setup.py sdist bdist_wheel')
16
+ os.system('python -m build')
17
17
  os.system('twine upload dist/*')
18
18
  sys.exit()
19
19
 
@@ -21,7 +21,7 @@ if sys.argv[-1] == 'publish':
21
21
  # This call to setup() does all the work
22
22
  setup(
23
23
  name=PKG,
24
- version="3.0.1",
24
+ version="3.0.2",
25
25
  description="Python library for the Preservica API",
26
26
  long_description=README,
27
27
  long_description_content_type="text/markdown",
@@ -97,3 +97,11 @@ def test_get_bitstream_content(setup_data):
97
97
  assert os.path.isfile(bitstream.filename) is True
98
98
  assert Path(bitstream.filename).stat().st_size == 1942466
99
99
  os.remove(bitstream.filename)
100
+
101
+
102
+ def test_get_bitstream_locations(setup_data):
103
+ client = EntityAPI()
104
+ asset = client.asset(ASSET_ID)
105
+ for bs in client.bitstreams_for_asset(asset):
106
+ locations = client.bitstream_location(bs)
107
+ assert "Primary Adapter" in locations
@@ -1,7 +1,35 @@
1
+ import pytest
1
2
  from pyPreservica import *
2
3
 
3
4
 
4
- def test_get_all_users():
5
+ def setup():
6
+ client = AdminAPI()
7
+ users = client.all_users()
8
+ for u in users:
9
+ if u == "pypreservica@gmail.com":
10
+ client.delete_user(u)
11
+
12
+
13
+
14
+ def tear_down():
15
+ pass
16
+
17
+
18
+ @pytest.fixture
19
+ def setup_data():
20
+ print("\nSetting up resources...")
21
+
22
+ setup()
23
+
24
+ yield
25
+
26
+ print("\nTearing down resources...")
27
+
28
+ tear_down()
29
+
30
+
31
+
32
+ def test_get_all_users(setup_data):
5
33
  client = AdminAPI()
6
34
  users = client.all_users()
7
35
  assert type(users) is list
@@ -10,7 +38,7 @@ def test_get_all_users():
10
38
  assert client.username in users
11
39
 
12
40
 
13
- def test_get_user():
41
+ def test_get_user(setup_data):
14
42
  client = AdminAPI()
15
43
  user = client.user_details(client.username)
16
44
  assert type(user) is dict
@@ -19,7 +47,7 @@ def test_get_user():
19
47
  assert 'SDB_MANAGER_USER' in user['Roles']
20
48
 
21
49
 
22
- def test_add_user():
50
+ def test_add_user(setup_data):
23
51
  client = AdminAPI()
24
52
  user = client.add_user("pypreservica@gmail.com", "pypreservica", ['SDB_MANAGER_USER', 'SDB_INGEST_USER'])
25
53
  assert user['UserName'] == "pypreservica@gmail.com"
@@ -30,7 +58,7 @@ def test_add_user():
30
58
  assert "pypreservica@gmail.com" not in client.all_users()
31
59
 
32
60
 
33
- def test_change_display_name():
61
+ def test_change_display_name(setup_data):
34
62
  client = AdminAPI()
35
63
  user = client.add_user("pypreservica@gmail.com", "pypreservica", ['SDB_MANAGER_USER', 'SDB_INGEST_USER'])
36
64
  assert user['UserName'] == "pypreservica@gmail.com"
File without changes
File without changes
File without changes