dcor-shared 0.9.0__tar.gz → 0.9.1__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.
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/CHANGELOG +2 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/PKG-INFO +1 -1
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/dcor_shared/_version_save.py +1 -1
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/dcor_shared/s3cc.py +75 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/dcor_shared.egg-info/PKG-INFO +1 -1
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/tests/test_s3cc.py +19 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/LICENSE +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/MANIFEST.in +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/README.rst +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/dcor_shared/__init__.py +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/dcor_shared/_version.py +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/dcor_shared/ckan.py +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/dcor_shared/data.py +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/dcor_shared/dcinst.py +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/dcor_shared/mime.py +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/dcor_shared/parse.py +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/dcor_shared/paths.py +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/dcor_shared/s3.py +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/dcor_shared/testing.py +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/dcor_shared.egg-info/SOURCES.txt +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/dcor_shared.egg-info/dependency_links.txt +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/dcor_shared.egg-info/requires.txt +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/dcor_shared.egg-info/top_level.txt +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/setup.cfg +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/setup.py +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/tests/test_basic.py +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/tests/test_ckan.py +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/tests/test_data.py +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/tests/test_s3.py +0 -0
- {dcor_shared-0.9.0 → dcor_shared-0.9.1}/tests/test_testing.py +0 -0
|
@@ -61,6 +61,81 @@ def create_presigned_url(
|
|
|
61
61
|
filename=filename)
|
|
62
62
|
|
|
63
63
|
|
|
64
|
+
def get_s3_attributes_for_artifact(
|
|
65
|
+
resource_id: str,
|
|
66
|
+
artifact: Literal["condensed", "preview", "resource"] = "resource"):
|
|
67
|
+
"""Return all attribute for an artifact in the S3 object store
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
meta: dict
|
|
72
|
+
Metadata dictionary with the keys "etag", "server", "size",
|
|
73
|
+
and "success".
|
|
74
|
+
"""
|
|
75
|
+
bucket_name, object_name = get_s3_bucket_object_for_artifact(
|
|
76
|
+
resource_id=resource_id, artifact=artifact)
|
|
77
|
+
s3_client, _, _ = s3.get_s3()
|
|
78
|
+
attr_info = s3_client.head_object(Bucket=bucket_name, Key=object_name)
|
|
79
|
+
# Example output from MinIO::
|
|
80
|
+
#
|
|
81
|
+
# {'AcceptRanges': 'bytes',
|
|
82
|
+
# 'ContentLength': 904729,
|
|
83
|
+
# 'ContentType': 'application/octet-stream',
|
|
84
|
+
# 'ETag': '"108d47e80f3e5f35110493b1fdcd30d5"',
|
|
85
|
+
# 'LastModified': datetime.datetime(2024, 3, 7, 8, 15,
|
|
86
|
+
# tzinfo=tzutc()),
|
|
87
|
+
# 'Metadata': {},
|
|
88
|
+
# 'ResponseMetadata': {
|
|
89
|
+
# 'HTTPHeaders': {
|
|
90
|
+
# 'accept-ranges': 'bytes',
|
|
91
|
+
# 'content-length': '904729',
|
|
92
|
+
# 'content-type': 'application/octet-stream',
|
|
93
|
+
# 'date': 'Thu, 07 Mar 2024 08:15:02 GMT',
|
|
94
|
+
# 'etag': '"108d47e80f3e5f35110493b1fdcd30d5"',
|
|
95
|
+
# 'last-modified': 'Thu, 07 Mar 2024 '
|
|
96
|
+
# '08:15:00 GMT',
|
|
97
|
+
# 'server': 'MinIO',
|
|
98
|
+
# 'strict-transport-security': 'max-age=31536000; '
|
|
99
|
+
# 'includeSubDomains',
|
|
100
|
+
# 'vary': 'Origin, Accept-Encoding',
|
|
101
|
+
# 'x-amz-id-2': 'dd9025bab4ad464b049177c95eb6e...',
|
|
102
|
+
# 'x-amz-request-id': '17BA6D680CB67A2C',
|
|
103
|
+
# 'x-amz-tagging-count': '1',
|
|
104
|
+
# 'x-content-type-options': 'nosniff',
|
|
105
|
+
# 'x-xss-protection': '1; mode=block'},
|
|
106
|
+
# 'HTTPStatusCode': 200,
|
|
107
|
+
# 'HostId': 'dd9025bab4ad464b049177c95eb6ebf3...',
|
|
108
|
+
# 'RequestId': '17BA6D680CB67A2C',
|
|
109
|
+
# 'RetryAttempts': 0}
|
|
110
|
+
# }
|
|
111
|
+
meta = {}
|
|
112
|
+
for key, funcs in [
|
|
113
|
+
("etag", [lambda m: m.get("ETag"),
|
|
114
|
+
lambda m: m.get("ResponseMetadata",
|
|
115
|
+
{}).get("HTTPHeaders",
|
|
116
|
+
{}).get("etag"),
|
|
117
|
+
]),
|
|
118
|
+
("server", [lambda m: m.get("ResponseMetadata",
|
|
119
|
+
{}).get("HTTPHeaders",
|
|
120
|
+
{}).get("server", "unknown")
|
|
121
|
+
]),
|
|
122
|
+
("size", [lambda m: m.get("ContentLength"),
|
|
123
|
+
lambda m: m.get("ResponseMetadata",
|
|
124
|
+
{}).get("HTTPHeaders",
|
|
125
|
+
{}).get("content-length"),
|
|
126
|
+
]),
|
|
127
|
+
("success", [lambda m: m.get("ResponseMetadata",
|
|
128
|
+
{}).get("HTTPStatusCode", 404) == 200
|
|
129
|
+
]),
|
|
130
|
+
]:
|
|
131
|
+
for fn in funcs:
|
|
132
|
+
val = fn(attr_info)
|
|
133
|
+
if val is not None:
|
|
134
|
+
meta[key] = val
|
|
135
|
+
break
|
|
136
|
+
return meta
|
|
137
|
+
|
|
138
|
+
|
|
64
139
|
def get_s3_bucket_object_for_artifact(
|
|
65
140
|
resource_id: str,
|
|
66
141
|
artifact: Literal["condensed", "preview", "resource"] = "resource"):
|
|
@@ -115,6 +115,25 @@ def test_create_presigned_url(enqueue_job_mock, tmp_path):
|
|
|
115
115
|
"490efdf5d9bb4cd4b2a6bcf2fe54d4dc201c38530140bcb168980bf8bf846c73"
|
|
116
116
|
|
|
117
117
|
|
|
118
|
+
@pytest.mark.ckan_config('ckan.plugins', 'dcor_schemas')
|
|
119
|
+
@pytest.mark.usefixtures('clean_db', 'with_request_context')
|
|
120
|
+
@mock.patch('ckan.plugins.toolkit.enqueue_job',
|
|
121
|
+
side_effect=synchronous_enqueue_job)
|
|
122
|
+
def test_get_s3_attributes_for_artifact(enqueue_job_mock):
|
|
123
|
+
rid, _, _, org_dict = setup_s3_resource_on_ckan()
|
|
124
|
+
|
|
125
|
+
# Make sure the resource exists
|
|
126
|
+
res_dict = helpers.call_action("resource_show", id=rid)
|
|
127
|
+
assert res_dict["id"] == rid, "sanity check"
|
|
128
|
+
|
|
129
|
+
# get the size
|
|
130
|
+
meta = s3cc.get_s3_attributes_for_artifact(rid)
|
|
131
|
+
assert meta["size"] == 904729
|
|
132
|
+
assert meta["success"]
|
|
133
|
+
assert meta["etag"]
|
|
134
|
+
assert meta["server"]
|
|
135
|
+
|
|
136
|
+
|
|
118
137
|
@pytest.mark.ckan_config('ckan.plugins', 'dcor_schemas')
|
|
119
138
|
@pytest.mark.usefixtures('clean_db', 'with_request_context')
|
|
120
139
|
@mock.patch('ckan.plugins.toolkit.enqueue_job',
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|