qurl-python 0.5.0__tar.gz → 0.5.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.
Files changed (57) hide show
  1. {qurl_python-0.5.0 → qurl_python-0.5.1}/.github/workflows/ci.yml +4 -4
  2. {qurl_python-0.5.0 → qurl_python-0.5.1}/.github/workflows/claude-code-review.yml +1 -1
  3. {qurl_python-0.5.0 → qurl_python-0.5.1}/.github/workflows/claude.yml +1 -1
  4. {qurl_python-0.5.0 → qurl_python-0.5.1}/.github/workflows/release-please.yml +2 -2
  5. {qurl_python-0.5.0 → qurl_python-0.5.1}/.github/workflows/validate-issue-templates.yml +1 -1
  6. qurl_python-0.5.1/.release-please-manifest.json +1 -0
  7. {qurl_python-0.5.0 → qurl_python-0.5.1}/CHANGELOG.md +7 -0
  8. {qurl_python-0.5.0 → qurl_python-0.5.1}/PKG-INFO +1 -1
  9. {qurl_python-0.5.0 → qurl_python-0.5.1}/pyproject.toml +1 -1
  10. {qurl_python-0.5.0 → qurl_python-0.5.1}/src/layerv_qurl/async_client.py +18 -0
  11. {qurl_python-0.5.0 → qurl_python-0.5.1}/src/layerv_qurl/client.py +23 -0
  12. qurl_python-0.5.0/.release-please-manifest.json +0 -1
  13. {qurl_python-0.5.0 → qurl_python-0.5.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  14. {qurl_python-0.5.0 → qurl_python-0.5.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  15. {qurl_python-0.5.0 → qurl_python-0.5.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  16. {qurl_python-0.5.0 → qurl_python-0.5.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  17. {qurl_python-0.5.0 → qurl_python-0.5.1}/.github/dependabot.yml +0 -0
  18. {qurl_python-0.5.0 → qurl_python-0.5.1}/.github/workflows/codeql.yml +0 -0
  19. {qurl_python-0.5.0 → qurl_python-0.5.1}/.github/workflows/dependency-age-check-actions.yml +0 -0
  20. {qurl_python-0.5.0 → qurl_python-0.5.1}/.github/workflows/dependency-age-check-pip.yml +0 -0
  21. {qurl_python-0.5.0 → qurl_python-0.5.1}/.github/workflows/dependency-review.yml +0 -0
  22. {qurl_python-0.5.0 → qurl_python-0.5.1}/.github/workflows/issue-priority.yml +0 -0
  23. {qurl_python-0.5.0 → qurl_python-0.5.1}/.github/workflows/pr-title.yml +0 -0
  24. {qurl_python-0.5.0 → qurl_python-0.5.1}/.github/workflows/secrets-scan.yml +0 -0
  25. {qurl_python-0.5.0 → qurl_python-0.5.1}/.gitignore +0 -0
  26. {qurl_python-0.5.0 → qurl_python-0.5.1}/CLAUDE.md +0 -0
  27. {qurl_python-0.5.0 → qurl_python-0.5.1}/CONTRIBUTING.md +0 -0
  28. {qurl_python-0.5.0 → qurl_python-0.5.1}/LICENSE +0 -0
  29. {qurl_python-0.5.0 → qurl_python-0.5.1}/README.md +0 -0
  30. {qurl_python-0.5.0 → qurl_python-0.5.1}/SECURITY.md +0 -0
  31. {qurl_python-0.5.0 → qurl_python-0.5.1}/release-please-config.json +0 -0
  32. {qurl_python-0.5.0 → qurl_python-0.5.1}/src/layerv_qurl/__init__.py +0 -0
  33. {qurl_python-0.5.0 → qurl_python-0.5.1}/src/layerv_qurl/_pagination.py +0 -0
  34. {qurl_python-0.5.0 → qurl_python-0.5.1}/src/layerv_qurl/_utils.py +0 -0
  35. {qurl_python-0.5.0 → qurl_python-0.5.1}/src/layerv_qurl/crid.py +0 -0
  36. {qurl_python-0.5.0 → qurl_python-0.5.1}/src/layerv_qurl/errors.py +0 -0
  37. {qurl_python-0.5.0 → qurl_python-0.5.1}/src/layerv_qurl/langchain.py +0 -0
  38. {qurl_python-0.5.0 → qurl_python-0.5.1}/src/layerv_qurl/py.typed +0 -0
  39. {qurl_python-0.5.0 → qurl_python-0.5.1}/src/layerv_qurl/types.py +0 -0
  40. {qurl_python-0.5.0 → qurl_python-0.5.1}/tests/__init__.py +0 -0
  41. {qurl_python-0.5.0 → qurl_python-0.5.1}/tests/client_support.py +0 -0
  42. {qurl_python-0.5.0 → qurl_python-0.5.1}/tests/conftest.py +0 -0
  43. {qurl_python-0.5.0 → qurl_python-0.5.1}/tests/fixtures/README.md +0 -0
  44. {qurl_python-0.5.0 → qurl_python-0.5.1}/tests/fixtures/crid_v1_vectors.json +0 -0
  45. {qurl_python-0.5.0 → qurl_python-0.5.1}/tests/test_access_code_listing.py +0 -0
  46. {qurl_python-0.5.0 → qurl_python-0.5.1}/tests/test_batch_client.py +0 -0
  47. {qurl_python-0.5.0 → qurl_python-0.5.1}/tests/test_client.py +0 -0
  48. {qurl_python-0.5.0 → qurl_python-0.5.1}/tests/test_conformance.py +0 -0
  49. {qurl_python-0.5.0 → qurl_python-0.5.1}/tests/test_contract_client.py +0 -0
  50. {qurl_python-0.5.0 → qurl_python-0.5.1}/tests/test_langchain.py +0 -0
  51. {qurl_python-0.5.0 → qurl_python-0.5.1}/tests/test_pagination.py +0 -0
  52. {qurl_python-0.5.0 → qurl_python-0.5.1}/tests/test_portal.py +0 -0
  53. {qurl_python-0.5.0 → qurl_python-0.5.1}/tests/test_post_retry.py +0 -0
  54. {qurl_python-0.5.0 → qurl_python-0.5.1}/tests/test_qurl_mutations.py +0 -0
  55. {qurl_python-0.5.0 → qurl_python-0.5.1}/tests/test_qurl_reads.py +0 -0
  56. {qurl_python-0.5.0 → qurl_python-0.5.1}/tests/test_resource_name.py +0 -0
  57. {qurl_python-0.5.0 → qurl_python-0.5.1}/tests/test_sdk_alignment.py +0 -0
@@ -18,7 +18,7 @@ jobs:
18
18
  steps:
19
19
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
20
20
  - name: Set up Python ${{ matrix.python-version }}
21
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
21
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
22
22
  with:
23
23
  python-version: ${{ matrix.python-version }}
24
24
  - name: Install dependencies
@@ -45,7 +45,7 @@ jobs:
45
45
  steps:
46
46
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
47
47
  - name: Set up Python 3.10
48
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
48
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
49
49
  with:
50
50
  python-version: "3.10"
51
51
  - name: Install the newest schema-1 conformance release
@@ -67,7 +67,7 @@ jobs:
67
67
  steps:
68
68
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
69
69
  - name: Set up Python 3.10
70
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
70
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
71
71
  with:
72
72
  python-version: "3.10"
73
73
  - name: Install dependencies
@@ -89,7 +89,7 @@ jobs:
89
89
  needs: [test, test-schema1, type-check]
90
90
  if: always() && github.event_name != 'pull_request'
91
91
  permissions: {}
92
- uses: layervai/ops-routines-workflows/.github/workflows/notify.yml@d07fa2fe3a103907ff2b1a1a71509a6dcd8e9d32 # v0.13.0
92
+ uses: layervai/ops-routines-workflows/.github/workflows/notify.yml@414821a809395384de83d853a4bfd5b46e6bfc55 # v0.15.0
93
93
  with:
94
94
  header: "qURL Python SDK Build"
95
95
  gates: '{"Tests": "${{ needs.test.result }}", "Conformance schema 1": "${{ needs.test-schema1.result }}", "Type check": "${{ needs.type-check.result }}"}'
@@ -161,7 +161,7 @@ jobs:
161
161
  # step, so the verify step never runs and the PR gets a bare
162
162
  # `cancelled` check with no comment and nothing saying why.
163
163
  timeout-minutes: 10
164
- uses: anthropics/claude-code-action@6c690188da61cf16ee7debb28383ba67f02abb4c # v1.0.242
164
+ uses: anthropics/claude-code-action@d535f5172dec02b204866b393d6b20b9b2aaf98d # v1.0.266
165
165
  with:
166
166
  anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
167
167
  github_token: ${{ github.token }}
@@ -328,7 +328,7 @@ jobs:
328
328
  if: success()
329
329
  # v1.0.180 keeps interactive commands on the same fail-closed runner as
330
330
  # automatic review and avoids the v1.0.140/141 issue-comment crash.
331
- uses: anthropics/claude-code-action@6c690188da61cf16ee7debb28383ba67f02abb4c # v1.0.242
331
+ uses: anthropics/claude-code-action@d535f5172dec02b204866b393d6b20b9b2aaf98d # v1.0.266
332
332
  with:
333
333
  anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
334
334
  # Omit github_token so the action uses its OIDC/GitHub App token path.
@@ -41,7 +41,7 @@ jobs:
41
41
  with:
42
42
  ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
43
43
  - name: Set up Python ${{ matrix.python-version }}
44
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
44
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
45
45
  with:
46
46
  python-version: ${{ matrix.python-version }}
47
47
  - name: Install dependencies
@@ -62,7 +62,7 @@ jobs:
62
62
  with:
63
63
  ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
64
64
  - name: Set up Python
65
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
65
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
66
66
  with:
67
67
  python-version: "3.12"
68
68
  - name: Install build tools
@@ -35,4 +35,4 @@ permissions:
35
35
 
36
36
  jobs:
37
37
  validate-templates:
38
- uses: layervai/ops-routines-workflows/.github/workflows/validate-issue-templates.yml@d07fa2fe3a103907ff2b1a1a71509a6dcd8e9d32 # v0.13.0
38
+ uses: layervai/ops-routines-workflows/.github/workflows/validate-issue-templates.yml@414821a809395384de83d853a4bfd5b46e6bfc55 # v0.15.0
@@ -0,0 +1 @@
1
+ {".":"0.5.1"}
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.1](https://github.com/layervai/qurl-python/compare/v0.5.0...v0.5.1) (2026-11-15)
4
+
5
+
6
+ ### Features
7
+
8
+ * support scheduled invitations ([#190](https://github.com/layervai/qurl-python/issues/190)) ([8222a63](https://github.com/layervai/qurl-python/commit/8222a636d1d82811ee9841df6e0facbc75ce99b1))
9
+
3
10
  ## [0.5.0](https://github.com/layervai/qurl-python/compare/v0.4.0...v0.5.0) (2026-11-07)
4
11
 
5
12
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: qurl-python
3
- Version: 0.5.0
3
+ Version: 0.5.1
4
4
  Summary: Python SDK for the qURL™ API — secure, time-limited access links. Quantum URL is how you enter the hidden layer of the internet.
5
5
  Project-URL: Homepage, https://github.com/layervai/qurl-python
6
6
  Project-URL: Repository, https://github.com/layervai/qurl-python
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "qurl-python"
7
- version = "0.5.0"
7
+ version = "0.5.1"
8
8
  description = "Python SDK for the qURL™ API — secure, time-limited access links. Quantum URL is how you enter the hidden layer of the internet."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -321,6 +321,8 @@ class AsyncQURLClient:
321
321
  self,
322
322
  resource: AsyncProtectedResource | str,
323
323
  *,
324
+ not_before: str | None = None,
325
+ expires_at: str | None = None,
324
326
  valid_for: str | timedelta | None = None,
325
327
  label: str | None = None,
326
328
  one_time_use: bool | None = None,
@@ -360,6 +362,12 @@ class AsyncQURLClient:
360
362
  max_sessions=max_sessions,
361
363
  session_duration=session_duration,
362
364
  )
365
+ if expires_at is not None and "expires_in" in body:
366
+ raise ValueError("expires_at cannot be combined with valid_for")
367
+ if not_before is not None:
368
+ body["not_before"] = not_before
369
+ if expires_at is not None:
370
+ body["expires_at"] = expires_at
363
371
  resp = await self._request(
364
372
  "POST",
365
373
  f"/v1/resources/{resource_id}/qurls",
@@ -1088,6 +1096,8 @@ class AsyncQURLClient:
1088
1096
  self,
1089
1097
  resource_id: str,
1090
1098
  *,
1099
+ not_before: str | None = None,
1100
+ expires_at: str | None = None,
1091
1101
  expires_in: str | None = None,
1092
1102
  label: str | None = None,
1093
1103
  one_time_use: bool | None = None,
@@ -1099,8 +1109,12 @@ class AsyncQURLClient:
1099
1109
  """Mint a qURL against an existing resource."""
1100
1110
  validate_id(resource_id)
1101
1111
  validate_mint_input(label=label, max_sessions=max_sessions)
1112
+ if expires_at is not None and expires_in is not None:
1113
+ raise ValueError("expires_at cannot be combined with expires_in")
1102
1114
  body = build_body(
1103
1115
  {
1116
+ "not_before": not_before,
1117
+ "expires_at": expires_at,
1104
1118
  "expires_in": expires_in,
1105
1119
  "label": label,
1106
1120
  "one_time_use": one_time_use,
@@ -1888,6 +1902,8 @@ class AsyncProtectedResource:
1888
1902
  async def create_portal(
1889
1903
  self,
1890
1904
  *,
1905
+ not_before: str | None = None,
1906
+ expires_at: str | None = None,
1891
1907
  valid_for: str | timedelta | None = None,
1892
1908
  label: str | None = None,
1893
1909
  one_time_use: bool | None = None,
@@ -1901,6 +1917,8 @@ class AsyncProtectedResource:
1901
1917
  """
1902
1918
  return await self._client.create_portal(
1903
1919
  self,
1920
+ not_before=not_before,
1921
+ expires_at=expires_at,
1904
1922
  valid_for=valid_for,
1905
1923
  label=label,
1906
1924
  one_time_use=one_time_use,
@@ -371,6 +371,8 @@ class QURLClient:
371
371
  self,
372
372
  resource: ProtectedResource | str,
373
373
  *,
374
+ not_before: str | None = None,
375
+ expires_at: str | None = None,
374
376
  valid_for: str | timedelta | None = None,
375
377
  label: str | None = None,
376
378
  one_time_use: bool | None = None,
@@ -391,6 +393,11 @@ class QURLClient:
391
393
  id string — the CRID a handle carries or that
392
394
  :meth:`protect_url` returns. CRID-shaped IDs are validated
393
395
  locally; the service validates other identifier forms.
396
+ not_before: Future opening time as an RFC 3339 timestamp.
397
+ An early attempt does not consume a single-use link.
398
+ expires_at: Absolute expiry as an RFC 3339 timestamp. With
399
+ ``not_before``, it also caps active sessions. Use it in
400
+ place of ``valid_for``.
394
401
  valid_for: How long the link stays valid — a duration string
395
402
  (``"5m"``, ``"24h"``) or :class:`datetime.timedelta`. A
396
403
  timedelta must be whole seconds and **at least one minute**;
@@ -445,6 +452,12 @@ class QURLClient:
445
452
  max_sessions=max_sessions,
446
453
  session_duration=session_duration,
447
454
  )
455
+ if expires_at is not None and "expires_in" in body:
456
+ raise ValueError("expires_at cannot be combined with valid_for")
457
+ if not_before is not None:
458
+ body["not_before"] = not_before
459
+ if expires_at is not None:
460
+ body["expires_at"] = expires_at
448
461
  resp = self._request(
449
462
  "POST",
450
463
  f"/v1/resources/{resource_id}/qurls",
@@ -1218,6 +1231,8 @@ class QURLClient:
1218
1231
  self,
1219
1232
  resource_id: str,
1220
1233
  *,
1234
+ not_before: str | None = None,
1235
+ expires_at: str | None = None,
1221
1236
  expires_in: str | None = None,
1222
1237
  label: str | None = None,
1223
1238
  one_time_use: bool | None = None,
@@ -1232,8 +1247,12 @@ class QURLClient:
1232
1247
  """
1233
1248
  validate_id(resource_id)
1234
1249
  validate_mint_input(label=label, max_sessions=max_sessions)
1250
+ if expires_at is not None and expires_in is not None:
1251
+ raise ValueError("expires_at cannot be combined with expires_in")
1235
1252
  body = build_body(
1236
1253
  {
1254
+ "not_before": not_before,
1255
+ "expires_at": expires_at,
1237
1256
  "expires_in": expires_in,
1238
1257
  "label": label,
1239
1258
  "one_time_use": one_time_use,
@@ -2012,6 +2031,8 @@ class ProtectedResource:
2012
2031
  def create_portal(
2013
2032
  self,
2014
2033
  *,
2034
+ not_before: str | None = None,
2035
+ expires_at: str | None = None,
2015
2036
  valid_for: str | timedelta | None = None,
2016
2037
  label: str | None = None,
2017
2038
  one_time_use: bool | None = None,
@@ -2025,6 +2046,8 @@ class ProtectedResource:
2025
2046
  """
2026
2047
  return self._client.create_portal(
2027
2048
  self,
2049
+ not_before=not_before,
2050
+ expires_at=expires_at,
2028
2051
  valid_for=valid_for,
2029
2052
  label=label,
2030
2053
  one_time_use=one_time_use,
@@ -1 +0,0 @@
1
- {".":"0.5.0"}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes