dcor-shared 0.7.4__tar.gz → 0.9.0__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 (32) hide show
  1. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/CHANGELOG +10 -0
  2. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/PKG-INFO +1 -1
  3. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/dcor_shared/__init__.py +3 -1
  4. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/dcor_shared/_version_save.py +1 -1
  5. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/dcor_shared/ckan.py +37 -1
  6. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/dcor_shared/s3.py +22 -9
  7. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/dcor_shared/s3cc.py +59 -19
  8. dcor_shared-0.9.0/dcor_shared/testing.py +286 -0
  9. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/dcor_shared.egg-info/PKG-INFO +1 -1
  10. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/dcor_shared.egg-info/SOURCES.txt +1 -0
  11. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/dcor_shared.egg-info/requires.txt +1 -1
  12. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/setup.py +1 -1
  13. dcor_shared-0.9.0/tests/test_ckan.py +53 -0
  14. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/tests/test_data.py +2 -18
  15. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/tests/test_s3cc.py +70 -23
  16. dcor_shared-0.9.0/tests/test_testing.py +85 -0
  17. dcor_shared-0.7.4/dcor_shared/testing.py +0 -145
  18. dcor_shared-0.7.4/tests/test_testing.py +0 -46
  19. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/LICENSE +0 -0
  20. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/MANIFEST.in +0 -0
  21. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/README.rst +0 -0
  22. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/dcor_shared/_version.py +0 -0
  23. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/dcor_shared/data.py +0 -0
  24. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/dcor_shared/dcinst.py +0 -0
  25. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/dcor_shared/mime.py +0 -0
  26. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/dcor_shared/parse.py +0 -0
  27. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/dcor_shared/paths.py +0 -0
  28. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/dcor_shared.egg-info/dependency_links.txt +0 -0
  29. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/dcor_shared.egg-info/top_level.txt +0 -0
  30. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/setup.cfg +0 -0
  31. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/tests/test_basic.py +0 -0
  32. {dcor_shared-0.7.4 → dcor_shared-0.9.0}/tests/test_s3.py +0 -0
@@ -1,3 +1,13 @@
1
+ 0.9.0
2
+ - feat: introduce `testing.make_dataset_via_s3` and
3
+ `testing.make_resource_via_s3` which uploads resources via S3
4
+ - enh: make sha256 optional in `s3.upload_file`
5
+ 0.8.0
6
+ - feat: introduce `get_resource_dc_config`, `get_resource_info`, and
7
+ `s3cc.get_s3_dc_handle_basin_based`
8
+ - enh: return S3 URL in s3cc.upload_artifact
9
+ - enh: make specifying owner organization and create context in
10
+ `testing.make_dataset` optional
1
11
  0.7.4
2
12
  - fix: disable basins when loading a dataset instance
3
13
  - enh: implement s3cc.upload_artifact
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dcor_shared
3
- Version: 0.7.4
3
+ Version: 0.9.0
4
4
  Summary: Functionalities shared by the DCOR CKAN extensions
5
5
  Home-page: https://github.com/DCOR-dev/dcor_shared
6
6
  Author: Paul Müller
@@ -1,5 +1,7 @@
1
1
  # flake8: noqa: F401
2
- from .ckan import get_ckan_config_option, get_resource_path
2
+ from .ckan import (
3
+ get_ckan_config_option, get_resource_dc_config, get_resource_path, get_resource_info
4
+ )
3
5
  from .data import DUMMY_BYTES, sha256sum, wait_for_resource
4
6
  from .dcinst import get_dc_instance
5
7
  from .mime import DC_MIME_TYPES, VALID_FORMATS
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env python
2
2
  # This file was created automatically
3
- longversion = '0.7.4'
3
+ longversion = '0.9.0'
@@ -41,7 +41,43 @@ def get_ckan_webassets_path():
41
41
  return pathlib.Path(get_ckan_config_option("ckan.webassets.path"))
42
42
 
43
43
 
44
- def get_resource_path(rid, create_dirs=False):
44
+ def get_resource_dc_config(resource_id):
45
+ """Return the DC metadata for a resource identifier
46
+
47
+ For this to work, the dcor_schemas CKAN extension must be active.
48
+ """
49
+ _, res_dict = get_resource_info(resource_id)
50
+ # build metadata dictionary from resource metadata
51
+ meta = {}
52
+ for item in res_dict:
53
+ if item.startswith("dc:"):
54
+ _, sec, key = item.split(":", 2)
55
+ meta.setdefault(sec, {})
56
+ meta[sec][key] = res_dict[item]
57
+ return meta
58
+
59
+
60
+ def get_resource_info(resource_id):
61
+ """Return resource and dataset dictionaries for a resource identifier
62
+
63
+ Return the dataset dictionary and the resource dictionary.
64
+ """
65
+ from ckan import logic
66
+ res_dict = logic.get_action("resource_show")(
67
+ context={'ignore_auth': True, 'user': 'default'},
68
+ data_dict={"id": resource_id})
69
+ ds_dict = logic.get_action("package_show")(
70
+ context={'ignore_auth': True, 'user': 'default'},
71
+ data_dict={"id": res_dict["package_id"]})
72
+ return ds_dict, res_dict
73
+
74
+
75
+ def get_resource_path(resource_id, create_dirs=False):
76
+ """Return the expected local path for a resource identifier
77
+
78
+ If `create_dirs` is True, create the parent directory tree.
79
+ """
80
+ rid = resource_id
45
81
  resources_path = get_ckan_storage_path() / "resources"
46
82
  pdir = resources_path / rid[:3] / rid[3:6]
47
83
  path = pdir / rid[6:]
@@ -1,4 +1,6 @@
1
1
  """Basic S3 utility functions"""
2
+ from __future__ import annotations
3
+
2
4
  import functools
3
5
  import hashlib
4
6
  import json
@@ -10,6 +12,7 @@ import boto3
10
12
  import botocore.exceptions
11
13
 
12
14
  from .ckan import get_ckan_config_option
15
+ from .data import sha256sum
13
16
 
14
17
 
15
18
  def compute_checksum(bucket_name, object_name, max_size=None):
@@ -124,8 +127,11 @@ def create_presigned_upload_url(bucket_name, object_name,
124
127
  return response["url"], response["fields"]
125
128
 
126
129
 
127
- def create_presigned_url(bucket_name, object_name, expiration=3600,
128
- filename=None):
130
+ def create_presigned_url(
131
+ bucket_name: str,
132
+ object_name: str,
133
+ expiration: int = 3600,
134
+ filename: str | None = None):
129
135
  """Generate a presigned URL to share an S3 object
130
136
 
131
137
  Parameters
@@ -164,8 +170,11 @@ def create_presigned_url(bucket_name, object_name, expiration=3600,
164
170
 
165
171
 
166
172
  @functools.lru_cache()
167
- def create_presigned_url_until(bucket_name, object_name, expires_at,
168
- filename=None):
173
+ def create_presigned_url_until(
174
+ bucket_name: str,
175
+ object_name: str,
176
+ expires_at: int,
177
+ filename: str | None = None) -> str:
169
178
  """Cached `create_presigned_url` with expiry time point
170
179
 
171
180
  Parameters
@@ -182,7 +191,7 @@ def create_presigned_url_until(bucket_name, object_name, expires_at,
182
191
 
183
192
  Returns
184
193
  -------
185
- psurl: str
194
+ ps_url: str
186
195
  Presigned URL as string.
187
196
  """
188
197
  # Generate a presigned URL for the S3 object
@@ -192,12 +201,12 @@ def create_presigned_url_until(bucket_name, object_name, expires_at,
192
201
  if filename is not None:
193
202
  params["ResponseContentDisposition"] = \
194
203
  f"attachment; filename = {filename}"
195
- psurl = s3_client.generate_presigned_url(
204
+ ps_url = s3_client.generate_presigned_url(
196
205
  'get_object',
197
206
  Params=params,
198
207
  ExpiresIn=expires_at - create_time())
199
208
  # The response contains the presigned URL
200
- return psurl
209
+ return ps_url
201
210
 
202
211
 
203
212
  def create_time():
@@ -367,7 +376,7 @@ def require_bucket(bucket_name):
367
376
  return s3_bucket
368
377
 
369
378
 
370
- def upload_file(bucket_name, object_name, path, sha256, private=True,
379
+ def upload_file(bucket_name, object_name, path, sha256=None, private=True,
371
380
  override=False):
372
381
  """Upload a file to a bucket
373
382
 
@@ -380,7 +389,8 @@ def upload_file(bucket_name, object_name, path, sha256, private=True,
380
389
  path: str or pathlib.Path
381
390
  Local path of the file to be uploaded
382
391
  sha256: str
383
- SHA256 checksum of the file to be uploaded
392
+ SHA256 checksum of the file to be uploaded, will be computed
393
+ if not provided
384
394
  private: bool
385
395
  Whether the object should remain private. If set to False,
386
396
  a tag "public:true" is added to the object which is picket up
@@ -393,6 +403,9 @@ def upload_file(bucket_name, object_name, path, sha256, private=True,
393
403
  s3_url: str
394
404
  URL to the S3 object
395
405
  """
406
+ if not sha256:
407
+ sha256 = sha256sum(path)
408
+
396
409
  path_size = pathlib.Path(path).stat().st_size
397
410
  s3_client, _, _ = get_s3()
398
411
  s3_bucket = require_bucket(bucket_name)
@@ -4,15 +4,21 @@ Contains methods to directly interact with CKAN resources that are on S3
4
4
  via just the resource ID.
5
5
  """
6
6
  from __future__ import annotations
7
+ import io
7
8
  import functools
8
9
  import pathlib
9
10
  from typing import Literal
11
+ from urllib.parse import urlparse
10
12
  import warnings
11
13
 
12
- from dclab.rtdc_dataset import fmt_s3
14
+ import dclab
15
+ from dclab.rtdc_dataset import fmt_hdf5, fmt_s3
16
+ import h5py
17
+
18
+ from .ckan import (
19
+ get_ckan_config_option, get_resource_dc_config, get_resource_info
20
+ )
13
21
 
14
- from .ckan import get_ckan_config_option
15
- from .data import sha256sum
16
22
  from . import s3
17
23
 
18
24
 
@@ -81,13 +87,7 @@ def get_s3_bucket_name_for_resource(resource_id):
81
87
  The resource with the identifier `resource_id` must exist in the
82
88
  CKAN database.
83
89
  """
84
- import ckan.logic
85
- res_dict = ckan.logic.get_action('resource_show')(
86
- context={'ignore_auth': True, 'user': 'default'},
87
- data_dict={"id": resource_id})
88
- ds_dict = ckan.logic.get_action('package_show')(
89
- context={'ignore_auth': True, 'user': 'default'},
90
- data_dict={'id': res_dict["package_id"]})
90
+ ds_dict, _ = get_resource_info(resource_id)
91
91
  bucket_name = get_ckan_config_option(
92
92
  "dcor_object_store.bucket_name").format(
93
93
  organization_id=ds_dict["organization"]["id"])
@@ -97,6 +97,9 @@ def get_s3_bucket_name_for_resource(resource_id):
97
97
  def get_s3_dc_handle(resource_id):
98
98
  """Return an instance of :class:`RTDC_S3`
99
99
 
100
+ The data are accessed directly via S3 using DCOR's access credentials.
101
+ Use this if you need to access the original raw file.
102
+
100
103
  The resource with the identifier `resource_id` must exist in the
101
104
  CKAN database.
102
105
  """
@@ -114,6 +117,48 @@ def get_s3_dc_handle(resource_id):
114
117
  return ds
115
118
 
116
119
 
120
+ def get_s3_dc_handle_basin_based(resource_id):
121
+ """Return a :class:`RTDC_HTTP`-basin-backed instance of :class:`RTDC_HDF5`
122
+
123
+ The returned instance does not contain any feature data, but has
124
+ basins defined that link to the original data on S3. The upside
125
+ over :func:`get_s3_dc_handle` is that the returned dataset
126
+ includes the basin with the condensed data and that the returned
127
+ instance does not contain the DCOR S3 credentials. The downside is
128
+ that initialization takes slightly longer and that, if private
129
+ resources are accessed, the presigned URLs in the basins are only
130
+ valid for a fixed time period.
131
+ """
132
+ ds_dict, res_dict = get_resource_info(resource_id)
133
+ basin_paths = []
134
+ for artifact in ["resource", "condensed"]:
135
+ if ds_dict["private"]:
136
+ bp = create_presigned_url(resource_id, artifact=artifact)
137
+ else:
138
+ bp = get_s3_url_for_artifact(resource_id, artifact=artifact)
139
+ basin_paths.append(bp)
140
+
141
+ fd = io.BytesIO()
142
+ with h5py.File(fd, "w", libver="latest") as hv:
143
+ # We don't use RTDCWriter as a context manager to avoid overhead
144
+ # during __exit__, but then we have to make sure "events" is there.
145
+ hv.require_group("events")
146
+ hw = dclab.RTDCWriter(hv)
147
+ hw.store_metadata(get_resource_dc_config(resource_id))
148
+ for bp in basin_paths:
149
+ urlp = urlparse(bp) # e.g. http://localhost/bucket/resource/id
150
+ hw.store_basin(
151
+ basin_name=urlp.path.split("/")[2], # e.g. "resource"
152
+ basin_format="http",
153
+ basin_type="remote",
154
+ basin_locs=[bp],
155
+ # Don't verify anything. This would only cost time,
156
+ # and we know these objects exist.
157
+ verify=False,
158
+ )
159
+ return fmt_hdf5.RTDC_HDF5(fd)
160
+
161
+
117
162
  def get_s3_url_for_artifact(
118
163
  resource_id: str,
119
164
  artifact: Literal["condensed", "preview", "resource"] = "resource"):
@@ -193,20 +238,15 @@ def upload_artifact(
193
238
  if private is None:
194
239
  # User did not say whether the resource is private. We have to
195
240
  # find out ourselves.
196
- import ckan.logic
197
- res_dict = ckan.logic.get_action('resource_show')(
198
- context={'ignore_auth': True, 'user': 'default'},
199
- data_dict={"id": resource_id})
200
- ds_dict = ckan.logic.get_action('package_show')(
201
- context={'ignore_auth': True, 'user': 'default'},
202
- data_dict={'id': res_dict["package_id"]})
241
+ ds_dict, _ = get_resource_info(resource_id)
203
242
  private = ds_dict["private"]
204
243
 
205
244
  rid = resource_id
206
- s3.upload_file(
245
+ s3_url = s3.upload_file(
207
246
  bucket_name=bucket_name,
208
247
  object_name=f"{artifact}/{rid[:3]}/{rid[3:6]}/{rid[6:]}",
209
248
  path=path_artifact,
210
- sha256=sha256 or sha256sum(path_artifact),
249
+ sha256=sha256,
211
250
  private=private,
212
251
  override=override)
252
+ return s3_url
@@ -0,0 +1,286 @@
1
+ from __future__ import annotations
2
+
3
+ from io import BytesIO
4
+ import numbers
5
+ import pathlib
6
+ from typing import Dict
7
+ import uuid
8
+
9
+ import ckan.authz
10
+ import ckan.tests.factories as factories
11
+ import ckan.tests.helpers as helpers
12
+ from ckan.tests.pytest_ckan.fixtures import FakeFileStorage
13
+ import requests
14
+ from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
15
+
16
+ import pytest
17
+
18
+ from .ckan import get_ckan_config_option
19
+ from . import s3
20
+
21
+
22
+ @pytest.fixture
23
+ def create_with_upload_no_temp(clean_db, ckan_config, monkeypatch):
24
+ """
25
+ Create upload without tempdir
26
+ """
27
+
28
+ def factory(data, filename, context=None, **kwargs):
29
+ if context is None:
30
+ context = {}
31
+ action = kwargs.pop("action", "resource_create")
32
+ field = kwargs.pop("upload_field_name", "upload")
33
+ test_file = BytesIO()
34
+ if type(data) is not bytes:
35
+ data = bytes(data, encoding="utf-8")
36
+ test_file.write(data)
37
+ test_file.seek(0)
38
+ test_resource = FakeFileStorage(test_file, filename)
39
+
40
+ params = {
41
+ field: test_resource,
42
+ }
43
+ params.update(kwargs)
44
+ return helpers.call_action(action, context, **params)
45
+ return factory
46
+
47
+
48
+ def make_dataset(create_context=None, owner_org=None, create_with_upload=None,
49
+ resource_path=None, activate=False, **kwargs):
50
+ """Create a dataset with a resource for testing"""
51
+ if create_context is None:
52
+ user = factories.User()
53
+ create_context = {'ignore_auth': False,
54
+ 'user': user['name'],
55
+ 'api_version': 3}
56
+ user_id = user["id"]
57
+ else:
58
+ # get user ID from create_context
59
+ user_id = ckan.authz.get_user_id_for_username(
60
+ create_context["user"], allow_none=True)
61
+
62
+ if owner_org is None:
63
+ owner_org = factories.Organization(users=[{
64
+ 'name': user_id,
65
+ 'capacity': 'admin'
66
+ }])
67
+
68
+ if "title" not in kwargs:
69
+ kwargs["title"] = "test-dataset"
70
+ if "authors" not in kwargs:
71
+ kwargs["authors"] = "Peter Pan"
72
+ if "license_id" not in kwargs:
73
+ kwargs["license_id"] = "CC-BY-4.0"
74
+ assert "state" not in kwargs, "must not be set"
75
+ assert "owner_org" not in kwargs, "must not be set"
76
+ # create a dataset
77
+ ds = helpers.call_action("package_create", create_context,
78
+ owner_org=owner_org["name"],
79
+ state="draft",
80
+ **kwargs
81
+ )
82
+
83
+ if create_with_upload is not None:
84
+ rs = make_resource(resource_path=resource_path,
85
+ create_with_upload=create_with_upload,
86
+ create_context=create_context,
87
+ dataset_id=ds["id"])
88
+
89
+ if activate:
90
+ revise_dict = {
91
+ "match": {"id": ds["id"]},
92
+ "update": {"state": "active"}
93
+ }
94
+ helpers.call_action("package_revise", create_context, **revise_dict)
95
+
96
+ ds_dict = helpers.call_action("package_show", id=ds["id"])
97
+
98
+ if create_with_upload is not None:
99
+ # updated resource dictionary
100
+ rs_dict = helpers.call_action("resource_show", id=rs["id"])
101
+ return ds_dict, rs_dict
102
+ else:
103
+ return ds_dict
104
+
105
+
106
+ def make_dataset_via_s3(
107
+ create_context: Dict | None = None,
108
+ owner_org: Dict | None = None,
109
+ resource_path: str | pathlib.Path | None = None,
110
+ activate: bool = False,
111
+ **kwargs: str | numbers.Number):
112
+ """Create a dataset and upload the given resource directly via S3
113
+
114
+ For this action to work, the "ckanext-dcor_schemas" extension
115
+ must be loaded.
116
+
117
+ Parameters
118
+ ----------
119
+ create_context: dict
120
+ CKAN context for creating the dataset
121
+ owner_org: dict
122
+ CKAN dictionary of the owner organization
123
+ resource_path: str or pathlib.Path
124
+ path to the resource file to upload. If not specified, then
125
+ not resource is created for the dataset
126
+ activate: bool
127
+ whether to activate the dataset
128
+ kwargs: dict
129
+ keyword arguments passed to `package_create`
130
+ """
131
+ if create_context is None:
132
+ user = factories.User()
133
+ create_context = {'ignore_auth': False,
134
+ 'user': user['name'],
135
+ 'api_version': 3}
136
+ user_id = user["id"]
137
+ else:
138
+ # get user ID from create_context
139
+ user_id = ckan.authz.get_user_id_for_username(
140
+ create_context["user"], allow_none=True)
141
+
142
+ if owner_org is None:
143
+ owner_org = factories.Organization(users=[{
144
+ 'name': user_id,
145
+ 'capacity': 'admin'
146
+ }])
147
+
148
+ if "title" not in kwargs:
149
+ kwargs["title"] = "test-dataset-S3"
150
+ if "authors" not in kwargs:
151
+ kwargs["authors"] = "Peter Fly Pan"
152
+ if "license_id" not in kwargs:
153
+ kwargs["license_id"] = "CC-BY-4.0"
154
+ assert "state" not in kwargs, "must not be set"
155
+ assert "owner_org" not in kwargs, "must not be set"
156
+ # create a dataset
157
+ ds_dict = helpers.call_action("package_create", create_context,
158
+ owner_org=owner_org["name"],
159
+ state="draft",
160
+ **kwargs
161
+ )
162
+
163
+ if resource_path is not None:
164
+ rid = make_resource_via_s3(resource_path=resource_path,
165
+ organization_id=owner_org["id"],
166
+ dataset_id=ds_dict["id"],
167
+ create_context=create_context,
168
+ private=ds_dict.get("private", False)
169
+ )
170
+ else:
171
+ rid = None
172
+
173
+ if activate:
174
+ revise_dict = {
175
+ "match": {"id": ds_dict["id"]},
176
+ "update": {"state": "active"}
177
+ }
178
+ helpers.call_action("package_revise", create_context, **revise_dict)
179
+
180
+ ds_dict = helpers.call_action("package_show", id=ds_dict["id"])
181
+
182
+ if rid is not None:
183
+ # fetch resource dictionary
184
+ rs_dict = helpers.call_action("resource_show", id=rid)
185
+ return ds_dict, rs_dict
186
+ else:
187
+ return ds_dict
188
+
189
+
190
+ def make_resource(resource_path, create_with_upload, create_context,
191
+ dataset_id):
192
+ content = resource_path.read_bytes()
193
+ rs = create_with_upload(
194
+ data=content,
195
+ filename='test.rtdc',
196
+ context=create_context,
197
+ package_id=dataset_id,
198
+ url="upload",
199
+ )
200
+ resource = helpers.call_action("resource_show", id=rs["id"])
201
+ return resource
202
+
203
+
204
+ def make_resource_via_s3(resource_path, organization_id, dataset_id,
205
+ create_context, private=False):
206
+ """Upload a resource to S3 and register it with CKAN"""
207
+ bucket_name = get_ckan_config_option(
208
+ "dcor_object_store.bucket_name").format(
209
+ organization_id=organization_id)
210
+ rid = str(uuid.uuid4())
211
+ object_name = f"resource/{rid[:3]}/{rid[3:6]}/{rid[6:]}"
212
+
213
+ s3.upload_file(bucket_name=bucket_name,
214
+ object_name=object_name,
215
+ path=resource_path,
216
+ private=private)
217
+
218
+ revise_dict = {
219
+ "match": {"id": dataset_id},
220
+ "update": {"resources": [{"id": rid,
221
+ "name": resource_path.name,
222
+ "s3_available": True,
223
+ }
224
+ ]
225
+ }
226
+ }
227
+ helpers.call_action("package_revise", create_context, **revise_dict)
228
+ return rid
229
+
230
+
231
+ def synchronous_enqueue_job(job_func, args=None, kwargs=None, title=None,
232
+ queue=None, rq_kwargs=None):
233
+ """
234
+ Synchronous mock for ``ckan.plugins.toolkit.enqueue_job``.
235
+
236
+ Due to the asynchronous nature of background jobs, code that uses them
237
+ needs to be handled specially when writing tests.
238
+
239
+ A common approach is to use the mock package to replace the
240
+ ckan.plugins.toolkit.enqueue_job function with a mock that executes jobs
241
+ synchronously instead of asynchronously
242
+
243
+ Also, since we are running the tests as root on a ckan instance that
244
+ is run by www-data, modifying files on disk in background jobs
245
+ (which were started by supervisor as www-data) does not work.
246
+ """
247
+ if rq_kwargs is None:
248
+ rq_kwargs = {}
249
+ args = args or []
250
+ kwargs = kwargs or {}
251
+ job_func(*args, **kwargs)
252
+
253
+
254
+ def upload_presigned_to_s3(psurl, fields, path_to_upload):
255
+ """Helper function for uploading data to S3
256
+
257
+ This is exactly how DCOR-Aid would be uploading things (with the
258
+ requests_toolbelt package). This could have been a little simpler,
259
+ but for the sake of reproducibility, we do it the DCOR-Aid way.
260
+ """
261
+ # callback function for monitoring the upload progress
262
+ # open the input file for streaming
263
+ with path_to_upload.open("rb") as fd:
264
+ fields["file"] = (fields["key"], fd)
265
+ e = MultipartEncoder(fields=fields)
266
+ m = MultipartEncoderMonitor(
267
+ e, lambda monitor: print(f"Bytes: {monitor.bytes_read}"))
268
+ # Increase the read size to speed-up upload (the default chunk
269
+ # size for uploads in urllib is 8k which results in a lot of
270
+ # Python code being involved in uploading a 20GB file; Setting
271
+ # the chunk size to 4MB should increase the upload speed):
272
+ # https://github.com/requests/toolbelt/issues/75
273
+ # #issuecomment-237189952
274
+ m._read = m.read
275
+ m.read = lambda size: m._read(4 * 1024 * 1024)
276
+ # perform the actual upload
277
+ hrep = requests.post(
278
+ psurl,
279
+ data=m,
280
+ headers={'Content-Type': m.content_type},
281
+ verify=True, # verify SSL connection
282
+ timeout=27.3, # timeout to avoid freezing
283
+ )
284
+ if hrep.status_code != 204:
285
+ raise ValueError(
286
+ f"Upload failed with {hrep.status_code}: {hrep.reason}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dcor-shared
3
- Version: 0.7.4
3
+ Version: 0.9.0
4
4
  Summary: Functionalities shared by the DCOR CKAN extensions
5
5
  Home-page: https://github.com/DCOR-dev/dcor_shared
6
6
  Author: Paul Müller
@@ -22,6 +22,7 @@ dcor_shared.egg-info/dependency_links.txt
22
22
  dcor_shared.egg-info/requires.txt
23
23
  dcor_shared.egg-info/top_level.txt
24
24
  tests/test_basic.py
25
+ tests/test_ckan.py
25
26
  tests/test_data.py
26
27
  tests/test_s3.py
27
28
  tests/test_s3cc.py
@@ -1,5 +1,5 @@
1
1
  boto3
2
- dclab[s3]>=0.57.3
2
+ dclab[s3]>=0.57.5
3
3
 
4
4
  [tests]
5
5
  requests
@@ -44,7 +44,7 @@ setup(
44
44
  long_description=open('README.rst').read() if exists('README.rst') else '',
45
45
  install_requires=[
46
46
  "boto3",
47
- "dclab[s3]>=0.57.3",
47
+ "dclab[s3]>=0.57.5",
48
48
  ],
49
49
  extras_require={
50
50
  # Required for the `dcor_shared.testing` submodule
@@ -0,0 +1,53 @@
1
+ import pathlib
2
+ from unittest import mock
3
+
4
+ import ckanext.dcor_schemas.plugin
5
+
6
+ from dcor_shared import get_resource_dc_config, get_resource_info
7
+
8
+ import pytest
9
+ from dcor_shared.testing import make_dataset, synchronous_enqueue_job
10
+
11
+ data_path = pathlib.Path(__file__).parent / "data"
12
+
13
+
14
+ @pytest.mark.ckan_config('ckan.plugins', 'dcor_schemas')
15
+ @pytest.mark.usefixtures('clean_db', 'with_request_context')
16
+ @mock.patch('ckan.plugins.toolkit.enqueue_job',
17
+ side_effect=synchronous_enqueue_job)
18
+ def test_get_resource_dc_config(enqueue_job_mock, create_with_upload,
19
+ monkeypatch):
20
+ monkeypatch.setattr(
21
+ ckanext.dcor_schemas.plugin,
22
+ 'DISABLE_AFTER_DATASET_CREATE_FOR_CONCURRENT_JOB_TESTS',
23
+ True)
24
+
25
+ _, res_dict = make_dataset(
26
+ create_with_upload=create_with_upload,
27
+ resource_path=data_path / "calibration_beads_47.rtdc",
28
+ activate=True)
29
+
30
+ dc_config = get_resource_dc_config(res_dict["id"])
31
+ assert dc_config["experiment"]["event count"] == 47
32
+ assert dc_config["experiment"]["date"] == "2018-12-11"
33
+ assert dc_config["fluorescence"]["laser count"] == 2
34
+
35
+
36
+ @pytest.mark.ckan_config('ckan.plugins', 'dcor_schemas')
37
+ @pytest.mark.usefixtures('clean_db', 'with_request_context')
38
+ @mock.patch('ckan.plugins.toolkit.enqueue_job',
39
+ side_effect=synchronous_enqueue_job)
40
+ def test_get_resource_info(enqueue_job_mock, create_with_upload, monkeypatch):
41
+ monkeypatch.setattr(
42
+ ckanext.dcor_schemas.plugin,
43
+ 'DISABLE_AFTER_DATASET_CREATE_FOR_CONCURRENT_JOB_TESTS',
44
+ True)
45
+
46
+ ds_dict, res_dict = make_dataset(
47
+ create_with_upload=create_with_upload,
48
+ resource_path=data_path / "calibration_beads_47.rtdc",
49
+ activate=True)
50
+
51
+ ds_dict_2, res_dict_2 = get_resource_info(res_dict["id"])
52
+ assert ds_dict == ds_dict_2
53
+ assert res_dict == res_dict_2
@@ -29,19 +29,11 @@ def test_get_dc_instance_file(enqueue_job_mock, create_with_upload,
29
29
  'DISABLE_AFTER_DATASET_CREATE_FOR_CONCURRENT_JOB_TESTS',
30
30
  True)
31
31
 
32
- user = factories.User()
33
- owner_org = factories.Organization(users=[{
34
- 'name': user['id'],
35
- 'capacity': 'admin'
36
- }])
37
- create_context = {'ignore_auth': False,
38
- 'user': user['name'],
39
- 'api_version': 3}
40
32
  ds_dict, _ = make_dataset(
41
- create_context, owner_org,
42
33
  create_with_upload=create_with_upload,
43
34
  resource_path=data_path / "calibration_beads_47.rtdc",
44
35
  activate=True)
36
+
45
37
  rid = ds_dict["resources"][0]["id"]
46
38
  resource_path = pathlib.Path(get_resource_path(rid))
47
39
  assert resource_path.exists(), "sanity check"
@@ -75,19 +67,11 @@ def test_get_dc_instance_s3(enqueue_job_mock, create_with_upload,
75
67
  'DISABLE_AFTER_DATASET_CREATE_FOR_CONCURRENT_JOB_TESTS',
76
68
  True)
77
69
 
78
- user = factories.User()
79
- owner_org = factories.Organization(users=[{
80
- 'name': user['id'],
81
- 'capacity': 'admin'
82
- }])
83
- create_context = {'ignore_auth': False,
84
- 'user': user['name'],
85
- 'api_version': 3}
86
70
  ds_dict, _ = make_dataset(
87
- create_context, owner_org,
88
71
  create_with_upload=create_with_upload,
89
72
  resource_path=data_path / "calibration_beads_47.rtdc",
90
73
  activate=True)
74
+
91
75
  res_dict = ds_dict["resources"][0]
92
76
  rid = res_dict["id"]
93
77
  resource_path = pathlib.Path(get_resource_path(rid))
@@ -1,4 +1,5 @@
1
1
  import pathlib
2
+ import shutil
2
3
  from unittest import mock
3
4
 
4
5
  import pytest
@@ -6,34 +7,21 @@ import pytest
6
7
  from ckan import model
7
8
  import ckan.tests.helpers as helpers
8
9
  import ckan.tests.factories as factories
10
+ import ckanext.dcor_schemas.plugin
9
11
 
10
- from dcor_shared.testing import synchronous_enqueue_job, upload_presigned_to_s3
12
+ from dcor_shared.testing import (
13
+ make_dataset, synchronous_enqueue_job, upload_presigned_to_s3
14
+ )
11
15
  from dcor_shared import s3, s3cc, sha256sum
12
16
 
17
+ import h5py
13
18
  import requests
14
19
 
15
20
 
16
21
  data_path = pathlib.Path(__file__).parent / "data"
17
22
 
18
23
 
19
- @pytest.mark.ckan_config('ckan.plugins', 'dcor_schemas')
20
- @pytest.mark.usefixtures('clean_db', 'with_request_context')
21
- @mock.patch('ckan.plugins.toolkit.enqueue_job',
22
- side_effect=synchronous_enqueue_job)
23
- def test_artifact_exists(enqueue_job_mock):
24
- rid, s3_url, _, org_dict = setup_s3_resource_on_ckan(private=True)
25
- assert s3cc.artifact_exists(rid)
26
- # Delete the object
27
- s3_client, _, _ = s3.get_s3()
28
- bucket_name, object_name = s3cc.get_s3_bucket_object_for_artifact(rid)
29
- s3_client.delete_object(
30
- Bucket=bucket_name,
31
- Key=object_name
32
- )
33
- assert not s3cc.artifact_exists(rid)
34
-
35
-
36
- def setup_s3_resource_on_ckan(private=False):
24
+ def setup_s3_resource_on_ckan(private=False, resource_path=None):
37
25
  """Create an S3 resource in CKAN"""
38
26
  user = factories.User()
39
27
  owner_org = factories.Organization(users=[{
@@ -51,10 +39,13 @@ def setup_s3_resource_on_ckan(private=False):
51
39
  organization_id=owner_org["id"],
52
40
  )
53
41
  rid = response["resource_id"]
42
+ if resource_path is None:
43
+ resource_path = data_path / "calibration_beads_47.rtdc"
44
+
54
45
  upload_presigned_to_s3(
55
46
  psurl=response["url"],
56
47
  fields=response["fields"],
57
- path_to_upload=data_path / "calibration_beads_47.rtdc")
48
+ path_to_upload=resource_path)
58
49
 
59
50
  # Create the dataset
60
51
  pkg_dict = helpers.call_action("package_create",
@@ -82,6 +73,23 @@ def setup_s3_resource_on_ckan(private=False):
82
73
  return rid, s3_url, new_pkg_dict, owner_org
83
74
 
84
75
 
76
+ @pytest.mark.ckan_config('ckan.plugins', 'dcor_schemas')
77
+ @pytest.mark.usefixtures('clean_db', 'with_request_context')
78
+ @mock.patch('ckan.plugins.toolkit.enqueue_job',
79
+ side_effect=synchronous_enqueue_job)
80
+ def test_artifact_exists(enqueue_job_mock):
81
+ rid, s3_url, _, org_dict = setup_s3_resource_on_ckan(private=True)
82
+ assert s3cc.artifact_exists(rid)
83
+ # Delete the object
84
+ s3_client, _, _ = s3.get_s3()
85
+ bucket_name, object_name = s3cc.get_s3_bucket_object_for_artifact(rid)
86
+ s3_client.delete_object(
87
+ Bucket=bucket_name,
88
+ Key=object_name
89
+ )
90
+ assert not s3cc.artifact_exists(rid)
91
+
92
+
85
93
  @pytest.mark.ckan_config('ckan.plugins', 'dcor_schemas')
86
94
  @pytest.mark.usefixtures('clean_db', 'with_request_context')
87
95
  @mock.patch('ckan.plugins.toolkit.enqueue_job',
@@ -134,6 +142,44 @@ def test_get_s3_handle(enqueue_job_mock):
134
142
  assert len(ds) == 47
135
143
 
136
144
 
145
+ @pytest.mark.ckan_config('ckan.plugins', 'dcor_depot dcor_schemas dc_serve')
146
+ @pytest.mark.usefixtures('clean_db', 'with_request_context')
147
+ @mock.patch('ckan.plugins.toolkit.enqueue_job',
148
+ side_effect=synchronous_enqueue_job)
149
+ def test_get_s3_dc_handle_basin_based(enqueue_job_mock, create_with_upload,
150
+ monkeypatch, tmp_path):
151
+ monkeypatch.setattr(
152
+ ckanext.dcor_schemas.plugin,
153
+ 'DISABLE_AFTER_DATASET_CREATE_FOR_CONCURRENT_JOB_TESTS',
154
+ True)
155
+
156
+ resource_path = tmp_path / "data.rtdc"
157
+ shutil.copy2(data_path / "calibration_beads_47.rtdc", resource_path)
158
+ with h5py.File(resource_path, "a") as h5:
159
+ del h5["events/volume"]
160
+
161
+ _, res_dict = make_dataset(
162
+ create_with_upload=create_with_upload,
163
+ resource_path=resource_path,
164
+ activate=True)
165
+ rid = res_dict["id"]
166
+
167
+ # sanity check with reference
168
+ with s3cc.get_s3_dc_handle(rid) as ds:
169
+ assert "volume" not in ds.features_innate
170
+ assert "image" in ds
171
+
172
+ # actual test, the condensed dataset containing "volume" should be
173
+ # in one of the basins.
174
+ with s3cc.get_s3_dc_handle_basin_based(rid) as ds2:
175
+ assert len(ds2.basins) == 2
176
+ assert "volume" in ds2
177
+ assert "volume" in ds2.features_basin
178
+ assert "volume" not in ds2.features_innate
179
+ assert "image" in ds2
180
+ assert len(ds2) == 47
181
+
182
+
137
183
  @pytest.mark.ckan_config('ckan.plugins', 'dcor_schemas')
138
184
  @pytest.mark.usefixtures('clean_db', 'with_request_context')
139
185
  @mock.patch('ckan.plugins.toolkit.enqueue_job',
@@ -173,9 +219,10 @@ def test_upload_artifact(enqueue_job_mock, tmp_path):
173
219
  path_fake_preview = tmp_path / "test_preview.jpg"
174
220
  path_fake_preview.write_text("This is not a real image!")
175
221
  # upload the preview
176
- s3cc.upload_artifact(rid,
177
- path_artifact=path_fake_preview,
178
- artifact="preview")
222
+ s3_url2 = s3cc.upload_artifact(rid,
223
+ path_artifact=path_fake_preview,
224
+ artifact="preview")
225
+ assert s3_url == s3_url2.replace("preview", "resource")
179
226
  # make sure that worked
180
227
  assert s3cc.artifact_exists(rid, "preview")
181
228
  # attempt to download the private artifact
@@ -0,0 +1,85 @@
1
+ import pathlib
2
+ import pytest
3
+
4
+ import ckan.tests.factories as factories
5
+ import requests
6
+
7
+ from dcor_shared.testing import make_dataset, make_dataset_via_s3
8
+ from dcor_shared import s3cc, sha256sum
9
+
10
+ data_path = pathlib.Path(__file__).parent / "data"
11
+
12
+
13
+ @pytest.mark.ckan_config('ckan.plugins', 'dcor_schemas')
14
+ @pytest.mark.usefixtures('clean_db', 'with_request_context')
15
+ def test_make_dataset():
16
+ user = factories.User()
17
+ owner_org = factories.Organization(users=[{
18
+ 'name': user['id'],
19
+ 'capacity': 'admin'
20
+ }])
21
+ # Note: `call_action` bypasses authorization!
22
+ create_context = {'ignore_auth': False,
23
+ 'user': user['name'], 'api_version': 3}
24
+ ds_dict = make_dataset(create_context, owner_org,
25
+ activate=False)
26
+ assert ds_dict["state"] == "draft"
27
+
28
+
29
+ @pytest.mark.ckan_config('ckan.plugins', 'dcor_schemas')
30
+ @pytest.mark.usefixtures('clean_db', 'with_request_context')
31
+ def test_make_dataset_no_user_specified():
32
+ ds_dict = make_dataset(activate=False)
33
+ assert ds_dict["state"] == "draft"
34
+
35
+
36
+ @pytest.mark.ckan_config('ckan.plugins', 'dcor_schemas')
37
+ @pytest.mark.usefixtures('clean_db', 'with_request_context')
38
+ def test_make_dataset_with_resource(create_with_upload):
39
+ # upload a *valid* [sic] .rtdc File (this is the control)
40
+ ds_dict, res_dict = make_dataset(
41
+ create_with_upload=create_with_upload,
42
+ resource_path=data_path / "calibration_beads_47.rtdc",
43
+ activate=True)
44
+ assert len(ds_dict["resources"]) == 1
45
+ assert "id" in res_dict
46
+ assert res_dict["package_id"] == ds_dict["id"]
47
+
48
+
49
+ @pytest.mark.ckan_config('ckan.plugins', 'dcor_schemas')
50
+ @pytest.mark.usefixtures('clean_db', 'with_request_context')
51
+ def test_make_dataset_via_s3():
52
+ user = factories.User()
53
+ owner_org = factories.Organization(users=[{
54
+ 'name': user['id'],
55
+ 'capacity': 'admin'
56
+ }])
57
+ # Note: `call_action` bypasses authorization!
58
+ create_context = {'ignore_auth': False,
59
+ 'user': user['name'], 'api_version': 3}
60
+ ds_dict = make_dataset_via_s3(create_context, owner_org, activate=False)
61
+ assert ds_dict["state"] == "draft"
62
+
63
+
64
+ @pytest.mark.ckan_config('ckan.plugins', 'dcor_schemas')
65
+ @pytest.mark.usefixtures('clean_db', 'with_request_context')
66
+ def test_make_dataset_with_resource_via_s3(tmp_path):
67
+ # upload a *valid* [sic] .rtdc File (this is the control)
68
+ ds_dict, res_dict = make_dataset_via_s3(
69
+ resource_path=data_path / "calibration_beads_47.rtdc",
70
+ activate=True,
71
+ private=False,
72
+ )
73
+ assert len(ds_dict["resources"]) == 1
74
+ assert "id" in res_dict
75
+ assert res_dict["package_id"] == ds_dict["id"]
76
+
77
+ # verify SHA256 sum of the resource
78
+ ps_url = s3cc.get_s3_url_for_artifact(res_dict["id"], artifact="resource")
79
+ response = requests.get(ps_url)
80
+ dl_path = tmp_path / "calbeads.rtdc"
81
+ with dl_path.open("wb") as fd:
82
+ fd.write(response.content)
83
+
84
+ assert sha256sum(dl_path) == \
85
+ "490efdf5d9bb4cd4b2a6bcf2fe54d4dc201c38530140bcb168980bf8bf846c73"
@@ -1,145 +0,0 @@
1
- from io import BytesIO
2
-
3
- import ckan.tests.helpers as helpers
4
- from ckan.tests.pytest_ckan.fixtures import FakeFileStorage
5
- import requests
6
- from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
7
-
8
- import pytest
9
-
10
-
11
- @pytest.fixture
12
- def create_with_upload_no_temp(clean_db, ckan_config, monkeypatch):
13
- """
14
- Create upload without tempdir
15
- """
16
-
17
- def factory(data, filename, context=None, **kwargs):
18
- if context is None:
19
- context = {}
20
- action = kwargs.pop("action", "resource_create")
21
- field = kwargs.pop("upload_field_name", "upload")
22
- test_file = BytesIO()
23
- if type(data) is not bytes:
24
- data = bytes(data, encoding="utf-8")
25
- test_file.write(data)
26
- test_file.seek(0)
27
- test_resource = FakeFileStorage(test_file, filename)
28
-
29
- params = {
30
- field: test_resource,
31
- }
32
- params.update(kwargs)
33
- return helpers.call_action(action, context, **params)
34
- return factory
35
-
36
-
37
- def make_dataset(create_context, owner_org, create_with_upload=None,
38
- resource_path=None, activate=False, **kwargs):
39
- if "title" not in kwargs:
40
- kwargs["title"] = "test-dataset"
41
- if "authors" not in kwargs:
42
- kwargs["authors"] = "Peter Pan"
43
- if "license_id" not in kwargs:
44
- kwargs["license_id"] = "CC-BY-4.0"
45
- assert "state" not in kwargs, "must not be set"
46
- assert "owner_org" not in kwargs, "must not be set"
47
- # create a dataset
48
- ds = helpers.call_action("package_create", create_context,
49
- owner_org=owner_org["name"],
50
- state="draft",
51
- **kwargs
52
- )
53
-
54
- if create_with_upload is not None:
55
- rs = make_resource(resource_path=resource_path,
56
- create_with_upload=create_with_upload,
57
- create_context=create_context,
58
- dataset_id=ds["id"])
59
-
60
- if activate:
61
- helpers.call_action("package_patch", create_context,
62
- id=ds["id"],
63
- state="active")
64
-
65
- ds_dict = helpers.call_action("package_show", id=ds["id"])
66
-
67
- if create_with_upload is not None:
68
- # updated resource dictionary
69
- rs_dict = helpers.call_action("resource_show", id=rs["id"])
70
- return ds_dict, rs_dict
71
- else:
72
- return ds_dict
73
-
74
-
75
- def make_resource(resource_path, create_with_upload, create_context,
76
- dataset_id):
77
- content = resource_path.read_bytes()
78
- rs = create_with_upload(
79
- data=content,
80
- filename='test.rtdc',
81
- context=create_context,
82
- package_id=dataset_id,
83
- url="upload",
84
- )
85
- resource = helpers.call_action("resource_show", id=rs["id"])
86
- return resource
87
-
88
-
89
- def synchronous_enqueue_job(job_func, args=None, kwargs=None, title=None,
90
- queue=None, rq_kwargs=None):
91
- """
92
- Synchronous mock for ``ckan.plugins.toolkit.enqueue_job``.
93
-
94
-
95
- Due to the asynchronous nature of background jobs, code that uses them
96
- needs to be handled specially when writing tests.
97
-
98
- A common approach is to use the mock package to replace the
99
- ckan.plugins.toolkit.enqueue_job function with a mock that executes jobs
100
- synchronously instead of asynchronously
101
-
102
- Also, since we are running the tests as root on a ckan instance that
103
- is run by www-data, modifying files on disk in background jobs
104
- (which were started by supervisor as www-data) does not work.
105
- """
106
- if rq_kwargs is None:
107
- rq_kwargs = {}
108
- args = args or []
109
- kwargs = kwargs or {}
110
- job_func(*args, **kwargs)
111
-
112
-
113
- def upload_presigned_to_s3(psurl, fields, path_to_upload):
114
- """Helper function for uploading data to S3
115
-
116
- This is exactly how DCOR-Aid would be uploading things (with the
117
- requests_toolbelt package). This could have been a little simpler,
118
- but for the sake of reproducibility, we do it the DCOR-Aid way.
119
- """
120
- # callback function for monitoring the upload progress
121
- # open the input file for streaming
122
- with path_to_upload.open("rb") as fd:
123
- fields["file"] = (fields["key"], fd)
124
- e = MultipartEncoder(fields=fields)
125
- m = MultipartEncoderMonitor(
126
- e, lambda monitor: print(f"Bytes: {monitor.bytes_read}"))
127
- # Increase the read size to speed-up upload (the default chunk
128
- # size for uploads in urllib is 8k which results in a lot of
129
- # Python code being involved in uploading a 20GB file; Setting
130
- # the chunk size to 4MB should increase the upload speed):
131
- # https://github.com/requests/toolbelt/issues/75
132
- # #issuecomment-237189952
133
- m._read = m.read
134
- m.read = lambda size: m._read(4 * 1024 * 1024)
135
- # perform the actual upload
136
- hrep = requests.post(
137
- psurl,
138
- data=m,
139
- headers={'Content-Type': m.content_type},
140
- verify=True, # verify SSL connection
141
- timeout=27.3, # timeout to avoid freezing
142
- )
143
- if hrep.status_code != 204:
144
- raise ValueError(
145
- f"Upload failed with {hrep.status_code}: {hrep.reason}")
@@ -1,46 +0,0 @@
1
- import pathlib
2
- import pytest
3
-
4
- import ckan.tests.factories as factories
5
-
6
- from dcor_shared.testing import make_dataset
7
-
8
- data_path = pathlib.Path(__file__).parent / "data"
9
-
10
-
11
- @pytest.mark.ckan_config('ckan.plugins', 'dcor_schemas')
12
- @pytest.mark.usefixtures('clean_db', 'with_request_context')
13
- def test_make_dataset():
14
- user = factories.User()
15
- owner_org = factories.Organization(users=[{
16
- 'name': user['id'],
17
- 'capacity': 'admin'
18
- }])
19
- # Note: `call_action` bypasses authorization!
20
- create_context = {'ignore_auth': False,
21
- 'user': user['name'], 'api_version': 3}
22
- ds_dict = make_dataset(create_context, owner_org,
23
- activate=False)
24
- assert ds_dict["state"] == "draft"
25
-
26
-
27
- @pytest.mark.ckan_config('ckan.plugins', 'dcor_schemas')
28
- @pytest.mark.usefixtures('clean_db', 'with_request_context')
29
- def test_make_dataset_with_resource(create_with_upload):
30
- user = factories.User()
31
- owner_org = factories.Organization(users=[{
32
- 'name': user['id'],
33
- 'capacity': 'admin'
34
- }])
35
- # Note: `call_action` bypasses authorization!
36
- create_context = {'ignore_auth': False,
37
- 'user': user['name'], 'api_version': 3}
38
- # upload a *valid* [sic] .rtdc File (this is the control)
39
- ds_dict, res_dict = make_dataset(
40
- create_context, owner_org,
41
- create_with_upload=create_with_upload,
42
- resource_path=data_path / "calibration_beads_47.rtdc",
43
- activate=True)
44
- assert len(ds_dict["resources"]) == 1
45
- assert "id" in res_dict
46
- assert res_dict["package_id"] == ds_dict["id"]
File without changes
File without changes
File without changes
File without changes