pyPreservica 3.0.1__tar.gz → 3.0.5__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 (48) hide show
  1. {pypreservica-3.0.1 → pypreservica-3.0.5}/PKG-INFO +14 -3
  2. {pypreservica-3.0.1 → pypreservica-3.0.5}/pyPreservica/__init__.py +1 -1
  3. {pypreservica-3.0.1 → pypreservica-3.0.5}/pyPreservica/common.py +3 -0
  4. {pypreservica-3.0.1 → pypreservica-3.0.5}/pyPreservica/entityAPI.py +40 -4
  5. {pypreservica-3.0.1 → pypreservica-3.0.5}/pyPreservica/uploadAPI.py +35 -13
  6. {pypreservica-3.0.1 → pypreservica-3.0.5}/pyPreservica.egg-info/PKG-INFO +14 -3
  7. {pypreservica-3.0.1 → pypreservica-3.0.5}/pyPreservica.egg-info/requires.txt +1 -1
  8. {pypreservica-3.0.1 → pypreservica-3.0.5}/setup.py +3 -3
  9. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_bitstream.py +8 -0
  10. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_users.py +32 -4
  11. {pypreservica-3.0.1 → pypreservica-3.0.5}/LICENSE.txt +0 -0
  12. {pypreservica-3.0.1 → pypreservica-3.0.5}/README.md +0 -0
  13. {pypreservica-3.0.1 → pypreservica-3.0.5}/pyPreservica/adminAPI.py +0 -0
  14. {pypreservica-3.0.1 → pypreservica-3.0.5}/pyPreservica/authorityAPI.py +0 -0
  15. {pypreservica-3.0.1 → pypreservica-3.0.5}/pyPreservica/contentAPI.py +0 -0
  16. {pypreservica-3.0.1 → pypreservica-3.0.5}/pyPreservica/mdformsAPI.py +0 -0
  17. {pypreservica-3.0.1 → pypreservica-3.0.5}/pyPreservica/monitorAPI.py +0 -0
  18. {pypreservica-3.0.1 → pypreservica-3.0.5}/pyPreservica/opex.py +0 -0
  19. {pypreservica-3.0.1 → pypreservica-3.0.5}/pyPreservica/parAPI.py +0 -0
  20. {pypreservica-3.0.1 → pypreservica-3.0.5}/pyPreservica/retentionAPI.py +0 -0
  21. {pypreservica-3.0.1 → pypreservica-3.0.5}/pyPreservica/webHooksAPI.py +0 -0
  22. {pypreservica-3.0.1 → pypreservica-3.0.5}/pyPreservica/workflowAPI.py +0 -0
  23. {pypreservica-3.0.1 → pypreservica-3.0.5}/pyPreservica.egg-info/SOURCES.txt +0 -0
  24. {pypreservica-3.0.1 → pypreservica-3.0.5}/pyPreservica.egg-info/dependency_links.txt +0 -0
  25. {pypreservica-3.0.1 → pypreservica-3.0.5}/pyPreservica.egg-info/top_level.txt +0 -0
  26. {pypreservica-3.0.1 → pypreservica-3.0.5}/setup.cfg +0 -0
  27. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_authority_records.py +0 -0
  28. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_children.py +0 -0
  29. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_content_api.py +0 -0
  30. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_crawl_fs.py +0 -0
  31. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_delete.py +0 -0
  32. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_download.py +0 -0
  33. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_entity.py +0 -0
  34. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_export_opex.py +0 -0
  35. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_groups.py +0 -0
  36. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_identifier.py +0 -0
  37. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_ingest.py +0 -0
  38. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_integrity_check.py +0 -0
  39. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_metadata.py +0 -0
  40. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_par.py +0 -0
  41. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_replace.py +0 -0
  42. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_retention.py +0 -0
  43. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_schema.py +0 -0
  44. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_security.py +0 -0
  45. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_thumbnail.py +0 -0
  46. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_upload.py +0 -0
  47. {pypreservica-3.0.1 → pypreservica-3.0.5}/tests/test_workflow.py +0 -0
  48. {pypreservica-3.0.1 → pypreservica-3.0.5}/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.5
4
4
  Summary: Python library for the Preservica API
5
5
  Home-page: https://pypreservica.readthedocs.io/
6
6
  Author: James Carr
@@ -24,12 +24,23 @@ License-File: LICENSE.txt
24
24
  Requires-Dist: requests
25
25
  Requires-Dist: urllib3
26
26
  Requires-Dist: certifi
27
- Requires-Dist: boto3
27
+ Requires-Dist: boto3<1.36.0
28
28
  Requires-Dist: botocore
29
29
  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.5"
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
@@ -1661,30 +1661,52 @@ class UploadAPI(AuthenticatedAPI):
1661
1661
  security_tag: str = "open",
1662
1662
  delete_after_upload: bool = True, max_MB_ingested: int = -1):
1663
1663
 
1664
+ from pyPreservica import EntityAPI
1665
+
1666
+ def entity_value(client: EntityAPI, identifier: str) -> Entity:
1667
+ back_off: int = 5
1668
+ while True:
1669
+ try:
1670
+ entities = client.identifier("code", identifier)
1671
+ if bool(len(entities) > 0):
1672
+ return entities.pop()
1673
+ else:
1674
+ return None
1675
+ except HTTPException as e:
1676
+ sleep(back_off)
1677
+ back_off = back_off * 2
1678
+
1679
+ def entity_exists(client: EntityAPI, identifier: str) -> bool:
1680
+ back_off: int = 5
1681
+ while True:
1682
+ try:
1683
+ entities = client.identifier("code", identifier)
1684
+ return bool(len(entities) > 0)
1685
+ except HTTPException as e:
1686
+ sleep(back_off)
1687
+ back_off = back_off * 2
1688
+
1664
1689
  def get_parent(client, identifier, parent_reference):
1665
- id = str(os.path.dirname(identifier))
1666
- if not id:
1667
- id = identifier
1668
- entities = client.identifier("code", id)
1669
- if len(entities) > 0:
1670
- folder = entities.pop()
1690
+ dirname_id: str = str(os.path.dirname(identifier))
1691
+ if not dirname_id:
1692
+ dirname_id = identifier
1693
+ folder = entity_value(client, dirname_id)
1694
+ if folder is not None:
1671
1695
  folder = client.folder(folder.reference)
1672
1696
  return folder.reference
1673
1697
  else:
1674
1698
  return parent_reference
1675
1699
 
1676
1700
  def get_folder(client, name, tag, parent_reference, identifier):
1677
- entities = client.identifier("code", identifier)
1678
- if len(entities) == 0:
1701
+ folder = entity_value(client, identifier)
1702
+ if folder is None:
1679
1703
  logger.info(f"Creating new folder with name {name}")
1680
1704
  folder = client.create_folder(name, name, tag, parent_reference)
1681
1705
  client.add_identifier(folder, "code", identifier)
1682
1706
  else:
1683
1707
  logger.info(f"Found existing folder with name {name}")
1684
- folder = entities.pop()
1685
1708
  return folder
1686
1709
 
1687
- from pyPreservica import EntityAPI
1688
1710
  entity_client = EntityAPI(username=self.username, password=self.password, server=self.server,
1689
1711
  tenant=self.tenant,
1690
1712
  two_fa_secret_key=self.two_fa_secret_key, use_shared_secret=self.shared_secret,
@@ -1714,7 +1736,7 @@ class UploadAPI(AuthenticatedAPI):
1714
1736
  files.remove(file)
1715
1737
  continue
1716
1738
  asset_code = os.path.join(code, file)
1717
- if len(entity_client.identifier("code", asset_code)) == 0:
1739
+ if not entity_exists(entity_client, asset_code):
1718
1740
  bytes_ingested = bytes_ingested + os.stat(full_path).st_size
1719
1741
  logger.info(f"Adding new file: {file} to package ready for upload")
1720
1742
  file_identifiers = {"code": asset_code}
@@ -1737,8 +1759,8 @@ class UploadAPI(AuthenticatedAPI):
1737
1759
  delete_after_upload=delete_after_upload)
1738
1760
  else:
1739
1761
  self.upload_zip_to_Source(path_to_zip_package=package, container_name=bucket_name,
1740
- show_progress= bool(progress_display is not None),
1741
- delete_after_upload=delete_after_upload)
1762
+ show_progress=bool(progress_display is not None),
1763
+ delete_after_upload=delete_after_upload)
1742
1764
 
1743
1765
  logger.info(f"Uploaded " + "{:.1f}".format(bytes_ingested / (1024 * 1024)) + " MB")
1744
1766
 
@@ -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.5
4
4
  Summary: Python library for the Preservica API
5
5
  Home-page: https://pypreservica.readthedocs.io/
6
6
  Author: James Carr
@@ -24,12 +24,23 @@ License-File: LICENSE.txt
24
24
  Requires-Dist: requests
25
25
  Requires-Dist: urllib3
26
26
  Requires-Dist: certifi
27
- Requires-Dist: boto3
27
+ Requires-Dist: boto3<1.36.0
28
28
  Requires-Dist: botocore
29
29
  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
@@ -1,7 +1,7 @@
1
1
  requests
2
2
  urllib3
3
3
  certifi
4
- boto3
4
+ boto3<1.36.0
5
5
  botocore
6
6
  s3transfer
7
7
  azure-storage-blob
@@ -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.5",
25
25
  description="Python library for the Preservica API",
26
26
  long_description=README,
27
27
  long_description_content_type="text/markdown",
@@ -42,7 +42,7 @@ setup(
42
42
  "Topic :: System :: Archiving",
43
43
  ],
44
44
  keywords='Preservica API Preservation',
45
- install_requires=["requests", "urllib3", "certifi", "boto3", "botocore", "s3transfer", "azure-storage-blob", "tqdm", "pyotp"],
45
+ install_requires=["requests", "urllib3", "certifi", "boto3 < 1.36.0", "botocore", "s3transfer", "azure-storage-blob", "tqdm", "pyotp"],
46
46
  project_urls={
47
47
  'Documentation': 'https://pypreservica.readthedocs.io',
48
48
  'Source': 'https://github.com/carj/pyPreservica',
@@ -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