vbase-api 0.1.2__tar.gz → 0.1.3__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.
@@ -0,0 +1 @@
1
+ include requirements.in
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vbase-api
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: vBase REST API Python Client
5
5
  Author-email: vBase <tech@vbase.com>
6
6
  License-Expression: Apache-2.0
@@ -13,10 +13,10 @@ Requires-Python: >=3.8
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE.txt
15
15
  License-File: NOTICE.txt
16
- Requires-Dist: requests>=2.7.0
16
+ Requires-Dist: requests<3,>=2.7.0
17
17
  Dynamic: license-file
18
18
 
19
- # vbase-py
19
+ # vbase-api-py
20
20
 
21
21
  vBase REST API Python Client
22
22
 
@@ -1,4 +1,4 @@
1
- # vbase-py
1
+ # vbase-api-py
2
2
 
3
3
  vBase REST API Python Client
4
4
 
@@ -1,38 +1,35 @@
1
- [build-system]
2
- requires = ["setuptools >= 77.0.3"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "vbase-api" # This is the name on PyPI
7
- dynamic = ["version"]
8
- description = "vBase REST API Python Client"
9
- readme = "README.md"
10
- requires-python = ">=3.8"
11
- dependencies = [
12
- "requests>=2.7.0"
13
- ]
14
- license = "Apache-2.0"
15
- authors = [
16
- { name = "vBase", email = "tech@vbase.com" }
17
- ]
18
-
19
- keywords = ["vbase", "stamping", "web3", "blockchain", "validation"]
20
- classifiers = [
21
- "Programming Language :: Python :: 3",
22
- "Operating System :: OS Independent",
23
- ]
24
-
25
- [project.urls]
26
- Homepage = "https://github.com/validityBase/vbase-api-py"
27
- Documentation = "https://docs.vbase.com/"
28
-
29
- # This tells setuptools where to find the code
30
- [tool.setuptools]
31
- package-dir = {"" = "src"}
32
-
33
- [tool.setuptools.packages.find]
34
- where = ["src"]
35
-
36
- [tool.setuptools.dynamic]
37
- version = {attr = "vbase_api._version.__version__"}
38
-
1
+ [build-system]
2
+ requires = ["setuptools >= 77.0.3"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "vbase-api" # This is the name on PyPI
7
+ dynamic = ["version", "dependencies"]
8
+ description = "vBase REST API Python Client"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = "Apache-2.0"
12
+ authors = [
13
+ { name = "vBase", email = "tech@vbase.com" }
14
+ ]
15
+
16
+ keywords = ["vbase", "stamping", "web3", "blockchain", "validation"]
17
+ classifiers = [
18
+ "Programming Language :: Python :: 3",
19
+ "Operating System :: OS Independent",
20
+ ]
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/validityBase/vbase-api-py"
24
+ Documentation = "https://docs.vbase.com/"
25
+
26
+ # This tells setuptools where to find the code
27
+ [tool.setuptools]
28
+ package-dir = {"" = "src"}
29
+
30
+ [tool.setuptools.packages.find]
31
+ where = ["src"]
32
+
33
+ [tool.setuptools.dynamic]
34
+ version = {attr = "vbase_api._version.__version__"}
35
+ dependencies = {file = ["requirements.in"]}
@@ -0,0 +1 @@
1
+ requests>=2.7.0,<3
@@ -0,0 +1 @@
1
+ __version__ = "0.1.3"
@@ -49,9 +49,11 @@ class VBaseAPIClient:
49
49
  timeout: Request timeout in seconds (default: 30)
50
50
 
51
51
  Example:
52
- >>> client = VBaseAPIClient(api_key="your-bearer-token")
53
- >>> collections = client.list_collections()
54
- >>> stamp = client.create_stamp(data={"hello": "world"})
52
+ .. code-block:: python
53
+
54
+ client = VBaseAPIClient(api_key="your-bearer-token")
55
+ collections = client.list_collections()
56
+ stamp = client.create_stamp(data={"hello": "world"})
55
57
  """
56
58
 
57
59
  DEFAULT_BASE_URL = "https://app.vbase.com"
@@ -146,9 +148,11 @@ class VBaseAPIClient:
146
148
  VBaseAPIError: If the request fails
147
149
 
148
150
  Example:
149
- >>> collections = client.get_collections(is_pinned=True)
150
- >>> for collection in collections:
151
- ... print(f"{collection.name}: {collection.cid}")
151
+ .. code-block:: python
152
+
153
+ collections = client.get_collections(is_pinned=True)
154
+ for collection in collections:
155
+ print(f"{collection.name}: {collection.cid}")
152
156
  """
153
157
  params = {}
154
158
  if user_address is not None:
@@ -187,13 +191,15 @@ class VBaseAPIClient:
187
191
  VBaseAPIError: If the request fails or collection already exists
188
192
 
189
193
  Example:
190
- >>> collection = client.create_collection(
191
- ... name="My Collection",
192
- ... cid="0x1234567890abcdef...",
193
- ... description="A sample collection",
194
- ... is_pinned=True
195
- ... )
196
- >>> print(f"Created: {collection.name}")
194
+ .. code-block:: python
195
+
196
+ collection = client.create_collection(
197
+ name="My Collection",
198
+ cid="0x1234567890abcdef...",
199
+ description="A sample collection",
200
+ is_pinned=True
201
+ )
202
+ print(f"Created: {collection.name}")
197
203
  """
198
204
  data = {
199
205
  'name': name,
@@ -252,17 +258,19 @@ class VBaseAPIClient:
252
258
  ValueError: If invalid parameters are provided
253
259
 
254
260
  Example:
255
- >>> # Stamp inline data
256
- >>> stamp = client.create_stamp(data={"hello": "world"})
257
- >>> print(f"Object CID: {stamp.commitment_receipt.object_cid}")
258
-
259
- >>> # Stamp a file
260
- >>> stamp = client.create_stamp(file="document.pdf", collection_name="Documents")
261
- >>> if stamp.file_object:
262
- ... print(f"File: {stamp.file_object.file_name}")
263
-
264
- >>> # Stamp an existing CID
265
- >>> stamp = client.create_stamp(data_cid="Qm...")
261
+ .. code-block:: python
262
+
263
+ # Stamp inline data
264
+ stamp = client.create_stamp(data={"hello": "world"})
265
+ print(f"Object CID: {stamp.commitment_receipt.object_cid}")
266
+
267
+ # Stamp a file
268
+ stamp = client.create_stamp(file="document.pdf", collection_name="Documents")
269
+ if stamp.file_object:
270
+ print(f"File: {stamp.file_object.file_name}")
271
+
272
+ # Stamp an existing CID
273
+ stamp = client.create_stamp(data_cid="Qm...")
266
274
  """
267
275
  if not any([file, data, data_cid]):
268
276
  raise ValueError("At least one of 'file', 'data', or 'data_cid' must be provided")
@@ -337,11 +345,13 @@ class VBaseAPIClient:
337
345
  VBaseAPIError: If validation fails or file not found in blockchain
338
346
 
339
347
  Example:
340
- >>> result = client.upload_stamped_file(
341
- ... collection_name="My Collection",
342
- ... file="stamped_document.pdf"
343
- ... )
344
- >>> print(f"Uploaded: {result.file_object.file_name}")
348
+ .. code-block:: python
349
+
350
+ result = client.upload_stamped_file(
351
+ collection_name="My Collection",
352
+ file="stamped_document.pdf"
353
+ )
354
+ print(f"Uploaded: {result.file_object.file_name}")
345
355
  """
346
356
  form_data = {
347
357
  'collection_name': collection_name
@@ -386,12 +396,14 @@ class VBaseAPIClient:
386
396
  VBaseAPIError: If the request fails
387
397
 
388
398
  Example:
389
- >>> result = client.verify_stamps(
390
- ... cids=["0xbd...1", "0xcd...2"],
391
- ... filter_by_user=True
392
- ... )
393
- >>> for stamp in result.stamp_list:
394
- ... print(f"Found stamp at {stamp.timestamp}")
399
+ .. code-block:: python
400
+
401
+ result = client.verify_stamps(
402
+ cids=["0xbd...1", "0xcd...2"],
403
+ filter_by_user=True
404
+ )
405
+ for stamp in result.stamp_list:
406
+ print(f"Found stamp at {stamp.timestamp}")
395
407
  """
396
408
  data = {
397
409
  'cids': cids,
@@ -421,8 +433,10 @@ class VBaseAPIClient:
421
433
  VBaseAPIError: If the request fails
422
434
 
423
435
  Example:
424
- >>> user = client.get_current_user()
425
- >>> print(f"User email: {user.email}")
436
+ .. code-block:: python
437
+
438
+ user = client.get_current_user()
439
+ print(f"User email: {user.email}")
426
440
  """
427
441
  response = self.session.get(
428
442
  self._get_url('users/me'),
@@ -445,8 +459,10 @@ class VBaseAPIClient:
445
459
  VBaseAPIError: If the request fails or user not found
446
460
 
447
461
  Example:
448
- >>> user = client.get_user("0x...")
449
- >>> print(f"User name: {user.name}")
462
+ .. code-block:: python
463
+
464
+ user = client.get_user("0x...")
465
+ print(f"User name: {user.name}")
450
466
  """
451
467
  response = self.session.get(
452
468
  self._get_url(f'users/{user_address}'),
@@ -486,7 +502,9 @@ def create_client(
486
502
  Configured VBaseAPIClient instance
487
503
 
488
504
  Example:
489
- >>> client = create_client(api_key="your-bearer-token")
490
- >>> collections = client.get_collections()
505
+ .. code-block:: python
506
+
507
+ client = create_client(api_key="your-bearer-token")
508
+ collections = client.get_collections()
491
509
  """
492
510
  return VBaseAPIClient(api_key=api_key, base_url=base_url, timeout=timeout)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vbase-api
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: vBase REST API Python Client
5
5
  Author-email: vBase <tech@vbase.com>
6
6
  License-Expression: Apache-2.0
@@ -13,10 +13,10 @@ Requires-Python: >=3.8
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE.txt
15
15
  License-File: NOTICE.txt
16
- Requires-Dist: requests>=2.7.0
16
+ Requires-Dist: requests<3,>=2.7.0
17
17
  Dynamic: license-file
18
18
 
19
- # vbase-py
19
+ # vbase-api-py
20
20
 
21
21
  vBase REST API Python Client
22
22
 
@@ -1,7 +1,9 @@
1
1
  LICENSE.txt
2
+ MANIFEST.in
2
3
  NOTICE.txt
3
4
  README.md
4
5
  pyproject.toml
6
+ requirements.in
5
7
  src/vbase_api/__init__.py
6
8
  src/vbase_api/_version.py
7
9
  src/vbase_api/vbase_api_client.py
@@ -10,4 +12,5 @@ src/vbase_api.egg-info/PKG-INFO
10
12
  src/vbase_api.egg-info/SOURCES.txt
11
13
  src/vbase_api.egg-info/dependency_links.txt
12
14
  src/vbase_api.egg-info/requires.txt
13
- src/vbase_api.egg-info/top_level.txt
15
+ src/vbase_api.egg-info/top_level.txt
16
+ tests/test_file_integrity_e2e.py
@@ -0,0 +1 @@
1
+ requests<3,>=2.7.0
@@ -0,0 +1,355 @@
1
+ """Live file-integrity E2E coverage for the vBase API Python client.
2
+
3
+ These tests call the public ``VBaseAPIClient`` stamping methods and then read
4
+ the stored object from S3 to verify the final bytes. They are skipped unless
5
+ live E2E credentials are supplied through GitHub Actions secrets or equivalent
6
+ environment variables.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import os
13
+ import tempfile
14
+ import time
15
+ import unittest
16
+ import uuid
17
+ from dataclasses import dataclass
18
+ from pathlib import Path
19
+ from typing import Iterable, Optional
20
+
21
+ try:
22
+ import boto3
23
+ from botocore.exceptions import BotoCoreError, ClientError
24
+ except ImportError as exc: # pragma: no cover
25
+ raise unittest.SkipTest(
26
+ "Install requirements/e2e.txt to run live file-integrity E2E tests."
27
+ ) from exc
28
+
29
+ from vbase_api import VBaseAPIClient, VBaseAPIError
30
+
31
+
32
+ def _sha3_256_cid(payload: bytes) -> str:
33
+ return "0x" + hashlib.sha3_256(payload).hexdigest()
34
+
35
+
36
+ def _first_env(*names: str) -> Optional[str]:
37
+ for name in names:
38
+ value = os.environ.get(name)
39
+ if value:
40
+ return value
41
+ return None
42
+
43
+
44
+ def _int_env(name: str, default: int) -> int:
45
+ value = _first_env(name)
46
+ if value is None:
47
+ return default
48
+
49
+ try:
50
+ return int(value)
51
+ except ValueError:
52
+ raise ValueError(
53
+ f"{name} must be an integer number of seconds; got {value!r}."
54
+ ) from None
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class LiveE2EConfig:
59
+ """Configuration needed for live app/API file-integrity tests."""
60
+
61
+ api_key: str
62
+ base_url: str
63
+ s3_bucket: str
64
+ aws_access_key_id: str
65
+ aws_secret_access_key: str
66
+ aws_session_token: Optional[str]
67
+ aws_region: str
68
+ timeout: int
69
+
70
+ @classmethod
71
+ def from_env(cls) -> "LiveE2EConfig":
72
+ api_key = _first_env("VBASE_API_KEY")
73
+ if not api_key:
74
+ raise unittest.SkipTest(
75
+ "Set VBASE_API_KEY to run live file-integrity E2E tests."
76
+ )
77
+
78
+ s3_bucket = _first_env("S3_VALIDATION_BUCKET")
79
+ if not s3_bucket:
80
+ raise unittest.SkipTest(
81
+ "Set S3_VALIDATION_BUCKET to run live file-integrity E2E tests."
82
+ )
83
+
84
+ aws_access_key_id = _first_env("AWS_ACCESS_KEY_ID")
85
+ aws_secret_access_key = _first_env("AWS_SECRET_ACCESS_KEY")
86
+ if not aws_access_key_id or not aws_secret_access_key:
87
+ raise unittest.SkipTest(
88
+ "Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY "
89
+ "to download stored files from S3."
90
+ )
91
+
92
+ return cls(
93
+ api_key=api_key,
94
+ base_url=_first_env("BASE_URL") or "https://staging.app.vbase.com",
95
+ s3_bucket=s3_bucket,
96
+ aws_access_key_id=aws_access_key_id,
97
+ aws_secret_access_key=aws_secret_access_key,
98
+ aws_session_token=_first_env("AWS_SESSION_TOKEN"),
99
+ aws_region=_first_env("AWS_REGION", "AWS_DEFAULT_REGION") or "us-east-1",
100
+ timeout=_int_env("FILE_INTEGRITY_E2E_TIMEOUT", 60),
101
+ )
102
+
103
+
104
+ @dataclass(frozen=True)
105
+ class FileCase:
106
+ """Source bytes for one integrity scenario."""
107
+
108
+ label: str
109
+ file_name: str
110
+ content: bytes
111
+
112
+ @property
113
+ def object_cid(self) -> str:
114
+ return _sha3_256_cid(self.content)
115
+
116
+
117
+ class VBaseAPIClientFileIntegrityE2ETests(unittest.TestCase):
118
+ """Verify vbase-api-py stamped-file storage preserves exact bytes."""
119
+
120
+ @classmethod
121
+ def setUpClass(cls) -> None:
122
+ cls.config = LiveE2EConfig.from_env()
123
+ cls.client = VBaseAPIClient(
124
+ api_key=cls.config.api_key,
125
+ base_url=cls.config.base_url.rstrip("/"),
126
+ timeout=cls.config.timeout,
127
+ )
128
+ cls.s3_client = boto3.session.Session(
129
+ aws_access_key_id=cls.config.aws_access_key_id,
130
+ aws_secret_access_key=cls.config.aws_secret_access_key,
131
+ aws_session_token=cls.config.aws_session_token,
132
+ region_name=cls.config.aws_region,
133
+ ).client("s3")
134
+
135
+ @classmethod
136
+ def tearDownClass(cls) -> None:
137
+ client = getattr(cls, "client", None)
138
+ if client is not None:
139
+ client.close()
140
+
141
+ s3_client = getattr(cls, "s3_client", None)
142
+ if s3_client is not None:
143
+ s3_client.close()
144
+
145
+ @staticmethod
146
+ def _file_cases(prefix: str) -> Iterable[FileCase]:
147
+ suffix = uuid.uuid4().hex
148
+ yield FileCase(
149
+ label="text",
150
+ file_name=f"{prefix}-{suffix}.txt",
151
+ content=f"vbase-api-py {prefix} text payload {suffix}\n".encode("utf-8"),
152
+ )
153
+ yield FileCase(
154
+ label="binary",
155
+ file_name=f"{prefix}-{suffix}.bin",
156
+ content=(
157
+ bytes([0, 1, 2, 3, 4, 127, 128, 254, 255])
158
+ + f" vbase-api-py {prefix} binary payload {suffix} ".encode("utf-8")
159
+ + bytes([255, 0, 64, 32, 16, 8])
160
+ ),
161
+ )
162
+
163
+ def _create_collection(self, name_prefix: str):
164
+ collection_name = f"{name_prefix}-{uuid.uuid4().hex[:12]}"
165
+ deadline = time.monotonic() + _int_env(
166
+ "FILE_INTEGRITY_E2E_API_WAIT_SECONDS", 60
167
+ )
168
+ last_error = None
169
+
170
+ while True:
171
+ try:
172
+ return self.client.create_collection(
173
+ name=collection_name,
174
+ description="vbase-api-py file-integrity E2E collection",
175
+ is_pinned=False,
176
+ )
177
+ except VBaseAPIError as exc:
178
+ if not exc.status_code or exc.status_code < 500:
179
+ raise
180
+ last_error = exc
181
+
182
+ if time.monotonic() >= deadline:
183
+ raise AssertionError(
184
+ f"Timed out creating collection {collection_name}; "
185
+ f"last error: {last_error}"
186
+ ) from last_error
187
+
188
+ time.sleep(5)
189
+
190
+ @staticmethod
191
+ def _write_temp_file(directory: Path, file_case: FileCase) -> Path:
192
+ path = directory / file_case.file_name
193
+ path.write_bytes(file_case.content)
194
+ return path
195
+
196
+ def _download_stored_file(self, file_path: str) -> bytes:
197
+ deadline = time.monotonic() + _int_env(
198
+ "FILE_INTEGRITY_E2E_STORAGE_WAIT_SECONDS", 120
199
+ )
200
+ last_error = None
201
+
202
+ while time.monotonic() < deadline:
203
+ try:
204
+ response = self.s3_client.get_object(
205
+ Bucket=self.config.s3_bucket,
206
+ Key=file_path,
207
+ )
208
+ body = response["Body"]
209
+ try:
210
+ payload = body.read()
211
+ finally:
212
+ body.close()
213
+
214
+ content_length = response.get("ContentLength")
215
+ if content_length is not None and content_length != len(payload):
216
+ raise AssertionError(
217
+ f"S3 ContentLength mismatch for {file_path}: "
218
+ f"metadata={content_length}, bytes={len(payload)}"
219
+ )
220
+ return payload
221
+ except (BotoCoreError, ClientError, KeyError) as exc:
222
+ last_error = str(exc)
223
+
224
+ time.sleep(5)
225
+
226
+ raise AssertionError(
227
+ f"Stored file was not downloadable before timeout: {file_path}; "
228
+ f"last error: {last_error}"
229
+ )
230
+
231
+ def _assert_file_object_path(self, file_path: str, collection_name: str) -> None:
232
+ self.assertIn(
233
+ f"/collections/{collection_name}/stamped/",
234
+ file_path,
235
+ )
236
+
237
+ def _assert_download_integrity(self, file_path: str, file_case: FileCase) -> None:
238
+ downloaded = self._download_stored_file(file_path)
239
+
240
+ self.assertEqual(
241
+ len(downloaded),
242
+ len(file_case.content),
243
+ f"{file_case.label} downloaded byte size",
244
+ )
245
+ self.assertEqual(
246
+ _sha3_256_cid(downloaded),
247
+ file_case.object_cid,
248
+ f"{file_case.label} downloaded SHA3-256",
249
+ )
250
+ self.assertEqual(
251
+ downloaded,
252
+ file_case.content,
253
+ f"{file_case.label} downloaded bytes",
254
+ )
255
+
256
+ def _wait_for_indexed_stamp(self, object_cid: str, collection_cid: str) -> None:
257
+ deadline = time.monotonic() + _int_env(
258
+ "FILE_INTEGRITY_E2E_INDEX_WAIT_SECONDS", 120
259
+ )
260
+ while time.monotonic() < deadline:
261
+ result = self.client.verify_stamps([object_cid], filter_by_user=True)
262
+ for receipt in result.stamp_list:
263
+ if (
264
+ receipt.object_cid.lower() == object_cid.lower()
265
+ and receipt.set_cid.lower() == collection_cid.lower()
266
+ ):
267
+ return
268
+ time.sleep(5)
269
+
270
+ raise AssertionError(
271
+ f"Timed out waiting for indexed stamp: object_cid={object_cid}, "
272
+ f"collection_cid={collection_cid}"
273
+ )
274
+
275
+ def test_create_stamp_with_storage_preserves_downloaded_file_bytes(self):
276
+ """UC-A: create_stamp stores text and binary files without byte drift."""
277
+ with tempfile.TemporaryDirectory() as tmpdir:
278
+ temp_dir = Path(tmpdir)
279
+ for file_case in self._file_cases("create-stamp"):
280
+ with self.subTest(format=file_case.label):
281
+ collection = self._create_collection(
282
+ f"vapi-create-{file_case.label}"
283
+ )
284
+ source_path = self._write_temp_file(temp_dir, file_case)
285
+
286
+ response = self.client.create_stamp(
287
+ file=source_path,
288
+ collection_name=collection.name,
289
+ store_stamped_file=True,
290
+ idempotent=False,
291
+ )
292
+
293
+ self.assertEqual(
294
+ response.commitment_receipt.object_cid.lower(),
295
+ file_case.object_cid.lower(),
296
+ )
297
+ self.assertEqual(
298
+ response.commitment_receipt.set_cid.lower(),
299
+ collection.cid.lower(),
300
+ )
301
+ self.assertIsNotNone(response.file_object)
302
+
303
+ file_path = response.file_object.file_path
304
+ self._assert_file_object_path(file_path, collection.name)
305
+ self._assert_download_integrity(file_path, file_case)
306
+
307
+ def test_upload_stamped_file_preserves_downloaded_file_bytes(self):
308
+ """UC-B: upload_stamped_file stores text and binary files without byte drift."""
309
+ with tempfile.TemporaryDirectory() as tmpdir:
310
+ temp_dir = Path(tmpdir)
311
+ for file_case in self._file_cases("upload-stamped-file"):
312
+ with self.subTest(format=file_case.label):
313
+ collection = self._create_collection(
314
+ f"vapi-upload-{file_case.label}"
315
+ )
316
+ source_path = self._write_temp_file(temp_dir, file_case)
317
+
318
+ stamp = self.client.create_stamp(
319
+ data_cid=file_case.object_cid,
320
+ collection_cid=collection.cid,
321
+ store_stamped_file=False,
322
+ idempotent=False,
323
+ )
324
+ self.assertEqual(
325
+ stamp.commitment_receipt.object_cid.lower(),
326
+ file_case.object_cid.lower(),
327
+ )
328
+ self.assertEqual(
329
+ stamp.commitment_receipt.set_cid.lower(),
330
+ collection.cid.lower(),
331
+ )
332
+ self._wait_for_indexed_stamp(file_case.object_cid, collection.cid)
333
+
334
+ upload = self.client.upload_stamped_file(
335
+ collection_name=collection.name,
336
+ file=source_path,
337
+ )
338
+
339
+ self.assertEqual(
340
+ upload.commitment_receipt.object_cid.lower(),
341
+ file_case.object_cid.lower(),
342
+ )
343
+ self.assertEqual(
344
+ upload.commitment_receipt.set_cid.lower(),
345
+ collection.cid.lower(),
346
+ )
347
+ self.assertIsNotNone(upload.file_object)
348
+
349
+ file_path = upload.file_object.file_path
350
+ self._assert_file_object_path(file_path, collection.name)
351
+ self._assert_download_integrity(file_path, file_case)
352
+
353
+
354
+ if __name__ == "__main__":
355
+ unittest.main()
@@ -1 +0,0 @@
1
- __version__ = "0.1.2"
@@ -1 +0,0 @@
1
- requests>=2.7.0
File without changes
File without changes
File without changes