destiny_sdk 0.7.6__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.
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/PKG-INFO +3 -3
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/pyproject.toml +3 -3
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/src/destiny_sdk/__init__.py +2 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/src/destiny_sdk/auth.py +8 -7
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/src/destiny_sdk/client.py +11 -9
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/src/destiny_sdk/core.py +8 -2
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/src/destiny_sdk/enhancements.py +59 -5
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/src/destiny_sdk/identifiers.py +12 -8
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/src/destiny_sdk/imports.py +9 -8
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/src/destiny_sdk/references.py +3 -3
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/src/destiny_sdk/robots.py +16 -16
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/tests/unit/test_auth.py +2 -2
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/tests/unit/test_client.py +8 -8
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/tests/unit/test_enhancements.py +33 -12
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/tests/unit/test_identifiers.py +5 -5
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/tests/unit/test_references.py +5 -7
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/tests/unit/test_robots.py +2 -2
- destiny_sdk-0.9.0/uv.lock +945 -0
- destiny_sdk-0.7.6/uv.lock +0 -825
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/.gitignore +0 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/LICENSE +0 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/README.md +0 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/src/destiny_sdk/labs/__init__.py +0 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/src/destiny_sdk/labs/references.py +0 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/src/destiny_sdk/parsers/__init__.py +0 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/src/destiny_sdk/parsers/eppi_parser.py +0 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/src/destiny_sdk/parsers/exceptions.py +0 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/src/destiny_sdk/py.typed +0 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/src/destiny_sdk/search.py +0 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/src/destiny_sdk/visibility.py +0 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/tests/unit/__init__.py +0 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/tests/unit/conftest.py +0 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/tests/unit/labs/test_references.py +0 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/tests/unit/parsers/test_eppi_parser.py +0 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/tests/unit/test_data/destiny_references.jsonl +0 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/tests/unit/test_data/eppi_import.jsonl +0 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/tests/unit/test_data/eppi_import_with_annotations.jsonl +0 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/tests/unit/test_data/eppi_import_with_raw.jsonl +0 -0
- {destiny_sdk-0.7.6 → destiny_sdk-0.9.0}/tests/unit/test_data/eppi_report.json +0 -0
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: destiny_sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: A software development kit (sdk) to support interaction with the DESTINY repository
|
|
5
5
|
Author-email: Adam Hamilton <adam@futureevidence.org>, Andrew Harvey <andrew@futureevidence.org>, Daniel Breves <daniel@futureevidence.org>, Jack Walmisley <jack@futureevidence.org>, Tim Repke <tim.repke@pik-potsdam.de>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Requires-Python: <4,>=3.12
|
|
9
9
|
Requires-Dist: cachetools<6,>=5.5.2
|
|
10
|
-
Requires-Dist: fastapi
|
|
10
|
+
Requires-Dist: fastapi>=0.119.1
|
|
11
11
|
Requires-Dist: httpx<0.29,>=0.28.1
|
|
12
12
|
Requires-Dist: msal>=1.34.0
|
|
13
|
-
Requires-Dist: pydantic<3,>=2.
|
|
13
|
+
Requires-Dist: pydantic<3,>=2.12.5
|
|
14
14
|
Requires-Dist: python-jose<4,>=3.4.0
|
|
15
15
|
Provides-Extra: labs
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
@@ -24,10 +24,10 @@ authors = [
|
|
|
24
24
|
]
|
|
25
25
|
dependencies = [
|
|
26
26
|
"cachetools>=5.5.2,<6",
|
|
27
|
-
"fastapi>=0.
|
|
27
|
+
"fastapi>=0.119.1",
|
|
28
28
|
"httpx>=0.28.1,<0.29",
|
|
29
29
|
"msal>=1.34.0",
|
|
30
|
-
"pydantic>=2.
|
|
30
|
+
"pydantic>=2.12.5,<3",
|
|
31
31
|
"python-jose>=3.4.0,<4",
|
|
32
32
|
]
|
|
33
33
|
description = "A software development kit (sdk) to support interaction with the DESTINY repository"
|
|
@@ -35,7 +35,7 @@ license = "Apache-2.0"
|
|
|
35
35
|
name = "destiny_sdk"
|
|
36
36
|
readme = "README.md"
|
|
37
37
|
requires-python = ">=3.12, <4"
|
|
38
|
-
version = "0.
|
|
38
|
+
version = "0.9.0"
|
|
39
39
|
|
|
40
40
|
[project.optional-dependencies]
|
|
41
41
|
labs = []
|
|
@@ -4,10 +4,11 @@ import hashlib
|
|
|
4
4
|
import hmac
|
|
5
5
|
import time
|
|
6
6
|
from typing import Protocol, Self
|
|
7
|
-
from uuid import UUID
|
|
8
7
|
|
|
9
8
|
from fastapi import HTTPException, Request, status
|
|
10
|
-
from pydantic import
|
|
9
|
+
from pydantic import BaseModel, TypeAdapter
|
|
10
|
+
|
|
11
|
+
from destiny_sdk.core import UUID
|
|
11
12
|
|
|
12
13
|
FIVE_MINUTES = 60 * 5
|
|
13
14
|
|
|
@@ -29,7 +30,7 @@ class AuthException(HTTPException):
|
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
def create_signature(
|
|
32
|
-
secret_key: str, request_body: bytes, client_id:
|
|
33
|
+
secret_key: str, request_body: bytes, client_id: UUID, timestamp: float
|
|
33
34
|
) -> str:
|
|
34
35
|
"""
|
|
35
36
|
Create an HMAC signature using SHA256.
|
|
@@ -39,7 +40,7 @@ def create_signature(
|
|
|
39
40
|
:param request_body: request body to be encrypted
|
|
40
41
|
:type request_body: bytes
|
|
41
42
|
:param client_id: client id to include in hmac
|
|
42
|
-
:type:
|
|
43
|
+
:type: UUID
|
|
43
44
|
:param timestamp: timestamp for when the request is sent
|
|
44
45
|
:type: float
|
|
45
46
|
:return: encrypted hexdigest of the request body with the secret key
|
|
@@ -64,7 +65,7 @@ class HMACAuthorizationHeaders(BaseModel):
|
|
|
64
65
|
"""
|
|
65
66
|
|
|
66
67
|
signature: str
|
|
67
|
-
client_id:
|
|
68
|
+
client_id: UUID
|
|
68
69
|
timestamp: float
|
|
69
70
|
|
|
70
71
|
@classmethod
|
|
@@ -108,11 +109,11 @@ class HMACAuthorizationHeaders(BaseModel):
|
|
|
108
109
|
)
|
|
109
110
|
|
|
110
111
|
try:
|
|
111
|
-
UUID(client_id)
|
|
112
|
+
TypeAdapter(UUID).validate_python(client_id)
|
|
112
113
|
except (ValueError, TypeError) as exc:
|
|
113
114
|
raise AuthException(
|
|
114
115
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
115
|
-
detail="Invalid format for client id, expected
|
|
116
|
+
detail="Invalid format for client id, expected UUIDv4 or UUIDv7.",
|
|
116
117
|
) from exc
|
|
117
118
|
|
|
118
119
|
timestamp = request.headers.get("X-Request-Timestamp")
|
|
@@ -11,10 +11,10 @@ from msal import (
|
|
|
11
11
|
PublicClientApplication,
|
|
12
12
|
UserAssignedManagedIdentity,
|
|
13
13
|
)
|
|
14
|
-
from pydantic import
|
|
14
|
+
from pydantic import HttpUrl, TypeAdapter
|
|
15
15
|
|
|
16
16
|
from destiny_sdk.auth import create_signature
|
|
17
|
-
from destiny_sdk.core import sdk_version
|
|
17
|
+
from destiny_sdk.core import UUID, sdk_version
|
|
18
18
|
from destiny_sdk.identifiers import IdentifierLookup
|
|
19
19
|
from destiny_sdk.references import Reference, ReferenceSearchResult
|
|
20
20
|
from destiny_sdk.robots import (
|
|
@@ -35,7 +35,7 @@ class HMACSigningAuth(httpx.Auth):
|
|
|
35
35
|
|
|
36
36
|
requires_request_body = True
|
|
37
37
|
|
|
38
|
-
def __init__(self, secret_key: str, client_id:
|
|
38
|
+
def __init__(self, secret_key: str, client_id: UUID) -> None:
|
|
39
39
|
"""
|
|
40
40
|
Initialize the client.
|
|
41
41
|
|
|
@@ -73,14 +73,16 @@ class RobotClient:
|
|
|
73
73
|
Current implementation only supports robot results.
|
|
74
74
|
"""
|
|
75
75
|
|
|
76
|
-
def __init__(self, base_url: HttpUrl, secret_key: str, client_id:
|
|
76
|
+
def __init__(self, base_url: HttpUrl, secret_key: str, client_id: UUID) -> None:
|
|
77
77
|
"""
|
|
78
78
|
Initialize the client.
|
|
79
79
|
|
|
80
80
|
:param base_url: The base URL for the Destiny Repository API.
|
|
81
81
|
:type base_url: HttpUrl
|
|
82
82
|
:param secret_key: The secret key for signing requests
|
|
83
|
-
:type
|
|
83
|
+
:type secret_key: str
|
|
84
|
+
:param client_id: The client ID for signing requests
|
|
85
|
+
:type client_id: UUID
|
|
84
86
|
"""
|
|
85
87
|
self.session = httpx.Client(
|
|
86
88
|
base_url=str(base_url).removesuffix("/").removesuffix("/v1") + "/v1",
|
|
@@ -131,7 +133,7 @@ class RobotClient:
|
|
|
131
133
|
|
|
132
134
|
def poll_robot_enhancement_batch(
|
|
133
135
|
self,
|
|
134
|
-
robot_id:
|
|
136
|
+
robot_id: UUID,
|
|
135
137
|
limit: int = 10,
|
|
136
138
|
lease: str | None = None,
|
|
137
139
|
timeout: int = 60,
|
|
@@ -142,7 +144,7 @@ class RobotClient:
|
|
|
142
144
|
Signs the request with the client's secret key.
|
|
143
145
|
|
|
144
146
|
:param robot_id: The ID of the robot to poll for
|
|
145
|
-
:type robot_id:
|
|
147
|
+
:type robot_id: UUID
|
|
146
148
|
:param limit: The maximum number of pending enhancements to return
|
|
147
149
|
:type limit: int
|
|
148
150
|
:param lease: The duration to lease the pending enhancements for,
|
|
@@ -169,7 +171,7 @@ class RobotClient:
|
|
|
169
171
|
return RobotEnhancementBatch.model_validate(response.json())
|
|
170
172
|
|
|
171
173
|
def renew_robot_enhancement_batch_lease(
|
|
172
|
-
self, robot_enhancement_batch_id:
|
|
174
|
+
self, robot_enhancement_batch_id: UUID, lease_duration: str | None = None
|
|
173
175
|
) -> None:
|
|
174
176
|
"""
|
|
175
177
|
Renew the lease for a robot enhancement batch.
|
|
@@ -177,7 +179,7 @@ class RobotClient:
|
|
|
177
179
|
Signs the request with the client's secret key.
|
|
178
180
|
|
|
179
181
|
:param robot_enhancement_batch_id: The ID of the robot enhancement batch
|
|
180
|
-
:type robot_enhancement_batch_id:
|
|
182
|
+
:type robot_enhancement_batch_id: UUID
|
|
181
183
|
:param lease_duration: The duration to lease the pending enhancements for,
|
|
182
184
|
in ISO 8601 duration format eg PT10M. If not provided the repository will
|
|
183
185
|
use a default lease duration.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""Core classes for the Destiny SDK, not exposed to package users."""
|
|
2
2
|
|
|
3
3
|
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
-
from typing import Self
|
|
4
|
+
from typing import Annotated, Self
|
|
5
5
|
|
|
6
|
-
from pydantic import BaseModel, Field
|
|
6
|
+
from pydantic import UUID4, UUID7, BaseModel, Field
|
|
7
7
|
|
|
8
8
|
from destiny_sdk.search import SearchResultPage, SearchResultTotal
|
|
9
9
|
|
|
@@ -67,3 +67,9 @@ class SearchResultMixIn(BaseModel):
|
|
|
67
67
|
page: SearchResultPage = Field(
|
|
68
68
|
description="Information about the page of results.",
|
|
69
69
|
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
UUID = Annotated[
|
|
73
|
+
UUID4 | UUID7,
|
|
74
|
+
Field(description="A DESTINY UUID, which can be either UUID4 or UUID7."),
|
|
75
|
+
]
|
|
@@ -5,9 +5,9 @@ import json
|
|
|
5
5
|
from enum import StrEnum, auto
|
|
6
6
|
from typing import Annotated, Any, Literal, Self
|
|
7
7
|
|
|
8
|
-
from pydantic import
|
|
8
|
+
from pydantic import BaseModel, Field, HttpUrl, field_validator, model_validator
|
|
9
9
|
|
|
10
|
-
from destiny_sdk.core import _JsonlFileInputMixIn
|
|
10
|
+
from destiny_sdk.core import UUID, _JsonlFileInputMixIn
|
|
11
11
|
from destiny_sdk.identifiers import Identifier
|
|
12
12
|
from destiny_sdk.visibility import Visibility
|
|
13
13
|
|
|
@@ -50,6 +50,27 @@ class AuthorPosition(StrEnum):
|
|
|
50
50
|
"""The last author."""
|
|
51
51
|
|
|
52
52
|
|
|
53
|
+
class PublicationVenueType(StrEnum):
|
|
54
|
+
"""
|
|
55
|
+
Type of publication venue.
|
|
56
|
+
|
|
57
|
+
Aligns with OpenAlex source types.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
JOURNAL = auto()
|
|
61
|
+
"""A journal publication."""
|
|
62
|
+
REPOSITORY = auto()
|
|
63
|
+
"""A repository (includes preprint servers like arXiv, bioRxiv)."""
|
|
64
|
+
CONFERENCE = auto()
|
|
65
|
+
"""A conference proceeding."""
|
|
66
|
+
EBOOK_PLATFORM = auto()
|
|
67
|
+
"""An ebook platform."""
|
|
68
|
+
BOOK_SERIES = auto()
|
|
69
|
+
"""A book series."""
|
|
70
|
+
OTHER = auto()
|
|
71
|
+
"""Other venue type."""
|
|
72
|
+
|
|
73
|
+
|
|
53
74
|
class Authorship(BaseModel):
|
|
54
75
|
"""
|
|
55
76
|
Represents a single author and their association with a reference.
|
|
@@ -106,6 +127,35 @@ class Pagination(BaseModel):
|
|
|
106
127
|
return value
|
|
107
128
|
|
|
108
129
|
|
|
130
|
+
class PublicationVenue(BaseModel):
|
|
131
|
+
"""A publication venue (journal, repository, conference, etc.)."""
|
|
132
|
+
|
|
133
|
+
display_name: str | None = Field(
|
|
134
|
+
default=None,
|
|
135
|
+
description=(
|
|
136
|
+
"The display name of the venue (journal name, repository name, etc.)"
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
venue_type: PublicationVenueType | None = Field(
|
|
140
|
+
default=None,
|
|
141
|
+
description="The type of venue: journal, repository, book, conference, etc.",
|
|
142
|
+
)
|
|
143
|
+
issn: list[str] | None = Field(
|
|
144
|
+
default=None,
|
|
145
|
+
description="List of ISSNs associated with this venue (print and electronic)",
|
|
146
|
+
)
|
|
147
|
+
issn_l: str | None = Field(
|
|
148
|
+
default=None,
|
|
149
|
+
description=(
|
|
150
|
+
"The linking ISSN - a canonical ISSN for the venue across format changes"
|
|
151
|
+
),
|
|
152
|
+
)
|
|
153
|
+
host_organization_name: str | None = Field(
|
|
154
|
+
default=None,
|
|
155
|
+
description="Display name of the host organization (publisher)",
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
109
159
|
class BibliographicMetadataEnhancement(BaseModel):
|
|
110
160
|
"""
|
|
111
161
|
An enhancement which is made up of bibliographic metadata.
|
|
@@ -151,6 +201,10 @@ other works have cited this work
|
|
|
151
201
|
default=None,
|
|
152
202
|
description="Pagination info (volume, issue, pages).",
|
|
153
203
|
)
|
|
204
|
+
publication_venue: PublicationVenue | None = Field(
|
|
205
|
+
default=None,
|
|
206
|
+
description="Publication venue information (journal, repository, etc.).",
|
|
207
|
+
)
|
|
154
208
|
|
|
155
209
|
@property
|
|
156
210
|
def fingerprint(self) -> str:
|
|
@@ -470,7 +524,7 @@ EnhancementContent = Annotated[
|
|
|
470
524
|
class Enhancement(_JsonlFileInputMixIn, BaseModel):
|
|
471
525
|
"""Core enhancement class."""
|
|
472
526
|
|
|
473
|
-
id:
|
|
527
|
+
id: UUID | None = Field(
|
|
474
528
|
default=None,
|
|
475
529
|
description=(
|
|
476
530
|
"The ID of the enhancement. "
|
|
@@ -478,7 +532,7 @@ class Enhancement(_JsonlFileInputMixIn, BaseModel):
|
|
|
478
532
|
),
|
|
479
533
|
)
|
|
480
534
|
|
|
481
|
-
reference_id:
|
|
535
|
+
reference_id: UUID = Field(
|
|
482
536
|
description="The ID of the reference this enhancement is associated with."
|
|
483
537
|
)
|
|
484
538
|
source: str = Field(
|
|
@@ -491,7 +545,7 @@ class Enhancement(_JsonlFileInputMixIn, BaseModel):
|
|
|
491
545
|
default=None,
|
|
492
546
|
description="The version of the robot that generated the content.",
|
|
493
547
|
)
|
|
494
|
-
derived_from: list[
|
|
548
|
+
derived_from: list[UUID] | None = Field(
|
|
495
549
|
default=None,
|
|
496
550
|
description="List of enhancement IDs that this enhancement was derived from.",
|
|
497
551
|
)
|
|
@@ -6,7 +6,9 @@ import uuid
|
|
|
6
6
|
from enum import StrEnum, auto
|
|
7
7
|
from typing import Annotated, Literal, Self
|
|
8
8
|
|
|
9
|
-
from pydantic import
|
|
9
|
+
from pydantic import BaseModel, Field, PositiveInt, TypeAdapter, field_validator
|
|
10
|
+
|
|
11
|
+
from .core import UUID
|
|
10
12
|
|
|
11
13
|
# Case-insensitive patterns for DOI URL prefix stripping
|
|
12
14
|
_DOI_URL_PREFIX_RE = re.compile(r"^(?:https?://)?(?:dx\.)?doi\.org/", re.IGNORECASE)
|
|
@@ -205,7 +207,7 @@ ExternalIdentifier = Annotated[
|
|
|
205
207
|
]
|
|
206
208
|
|
|
207
209
|
#: Any identifier including external identifiers and repository UUID4s.
|
|
208
|
-
Identifier = Annotated[ExternalIdentifier |
|
|
210
|
+
Identifier = Annotated[ExternalIdentifier | UUID, Field()]
|
|
209
211
|
|
|
210
212
|
ExternalIdentifierAdapter: TypeAdapter[ExternalIdentifier] = TypeAdapter(
|
|
211
213
|
ExternalIdentifier
|
|
@@ -219,7 +221,7 @@ class LinkedExternalIdentifier(BaseModel):
|
|
|
219
221
|
description="The identifier of the reference.",
|
|
220
222
|
discriminator="identifier_type",
|
|
221
223
|
)
|
|
222
|
-
reference_id:
|
|
224
|
+
reference_id: UUID = Field(
|
|
223
225
|
description="The ID of the reference this identifier identifies."
|
|
224
226
|
)
|
|
225
227
|
|
|
@@ -250,11 +252,11 @@ class IdentifierLookup(BaseModel):
|
|
|
250
252
|
"""Parse an identifier string into an IdentifierLookup."""
|
|
251
253
|
if delimiter not in identifier_lookup_string:
|
|
252
254
|
try:
|
|
253
|
-
|
|
255
|
+
TypeAdapter(UUID).validate_python(identifier_lookup_string)
|
|
254
256
|
except ValueError as exc:
|
|
255
257
|
msg = (
|
|
256
258
|
f"Invalid identifier lookup string: {identifier_lookup_string}. "
|
|
257
|
-
"Must be
|
|
259
|
+
"Must be UUID if no identifier type is specified."
|
|
258
260
|
)
|
|
259
261
|
raise ValueError(msg) from exc
|
|
260
262
|
return cls(
|
|
@@ -288,7 +290,9 @@ class IdentifierLookup(BaseModel):
|
|
|
288
290
|
|
|
289
291
|
@classmethod
|
|
290
292
|
def from_identifier(cls, identifier: Identifier) -> Self:
|
|
291
|
-
"""Create an IdentifierLookup from an ExternalIdentifier or
|
|
293
|
+
"""Create an IdentifierLookup from an ExternalIdentifier or UUID."""
|
|
294
|
+
# Use stdlib uuid here as we can't use Annotated types in isinstance checks
|
|
295
|
+
# The UUID versions is already validated per `identifier`
|
|
292
296
|
if isinstance(identifier, uuid.UUID):
|
|
293
297
|
return cls(identifier=str(identifier), identifier_type=None)
|
|
294
298
|
return cls(
|
|
@@ -298,9 +302,9 @@ class IdentifierLookup(BaseModel):
|
|
|
298
302
|
)
|
|
299
303
|
|
|
300
304
|
def to_identifier(self) -> Identifier:
|
|
301
|
-
"""Convert into an ExternalIdentifier or
|
|
305
|
+
"""Convert into an ExternalIdentifier or UUID if it has no identifier_type."""
|
|
302
306
|
if self.identifier_type is None:
|
|
303
|
-
return
|
|
307
|
+
return TypeAdapter(UUID).validate_python(self.identifier)
|
|
304
308
|
return ExternalIdentifierAdapter.validate_python(self.model_dump())
|
|
305
309
|
|
|
306
310
|
def __repr__(self) -> str:
|
|
@@ -4,13 +4,14 @@ import datetime
|
|
|
4
4
|
from enum import StrEnum, auto
|
|
5
5
|
|
|
6
6
|
from pydantic import (
|
|
7
|
-
UUID4,
|
|
8
7
|
BaseModel,
|
|
9
8
|
Field,
|
|
10
9
|
HttpUrl,
|
|
11
10
|
PastDatetime,
|
|
12
11
|
)
|
|
13
12
|
|
|
13
|
+
from .core import UUID
|
|
14
|
+
|
|
14
15
|
|
|
15
16
|
class ImportRecordStatus(StrEnum):
|
|
16
17
|
"""Describes the status of an import record."""
|
|
@@ -108,7 +109,7 @@ class ImportRecordIn(_ImportRecordBase):
|
|
|
108
109
|
class ImportRecordRead(_ImportRecordBase):
|
|
109
110
|
"""Core import record class."""
|
|
110
111
|
|
|
111
|
-
id:
|
|
112
|
+
id: UUID = Field(
|
|
112
113
|
description="The ID of the import record",
|
|
113
114
|
)
|
|
114
115
|
status: ImportRecordStatus = Field(
|
|
@@ -140,13 +141,13 @@ class ImportBatchIn(_ImportBatchBase):
|
|
|
140
141
|
class ImportBatchRead(_ImportBatchBase):
|
|
141
142
|
"""Core import batch class."""
|
|
142
143
|
|
|
143
|
-
id:
|
|
144
|
+
id: UUID = Field(
|
|
144
145
|
description="The ID of the import batch",
|
|
145
146
|
)
|
|
146
147
|
status: ImportBatchStatus = Field(
|
|
147
148
|
default=ImportBatchStatus.CREATED, description="The status of the batch."
|
|
148
149
|
)
|
|
149
|
-
import_record_id:
|
|
150
|
+
import_record_id: UUID = Field(
|
|
150
151
|
description="The ID of the import record this batch is associated with"
|
|
151
152
|
)
|
|
152
153
|
import_record: ImportRecordRead | None = Field(
|
|
@@ -160,13 +161,13 @@ class ImportBatchRead(_ImportBatchBase):
|
|
|
160
161
|
class ImportBatchSummary(_ImportBatchBase):
|
|
161
162
|
"""A view for an import batch that includes a summary of its results."""
|
|
162
163
|
|
|
163
|
-
id:
|
|
164
|
+
id: UUID = Field(
|
|
164
165
|
description="""
|
|
165
166
|
The identifier of the batch.
|
|
166
167
|
""",
|
|
167
168
|
)
|
|
168
169
|
|
|
169
|
-
import_batch_id:
|
|
170
|
+
import_batch_id: UUID = Field(description="The ID of the batch being summarised")
|
|
170
171
|
|
|
171
172
|
import_batch_status: ImportBatchStatus = Field(
|
|
172
173
|
description="The status of the batch being summarised"
|
|
@@ -187,8 +188,8 @@ The identifier of the batch.
|
|
|
187
188
|
class ImportResultRead(BaseModel):
|
|
188
189
|
"""Core import result class."""
|
|
189
190
|
|
|
190
|
-
id:
|
|
191
|
-
reference_id:
|
|
191
|
+
id: UUID = Field(description="The ID of the import result.")
|
|
192
|
+
reference_id: UUID | None = Field(
|
|
192
193
|
default=None,
|
|
193
194
|
description="The ID of the reference created by this import result.",
|
|
194
195
|
)
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Self
|
|
4
4
|
|
|
5
|
-
from pydantic import
|
|
5
|
+
from pydantic import BaseModel, Field, TypeAdapter
|
|
6
6
|
|
|
7
|
-
from destiny_sdk.core import SearchResultMixIn, _JsonlFileInputMixIn
|
|
7
|
+
from destiny_sdk.core import UUID, SearchResultMixIn, _JsonlFileInputMixIn
|
|
8
8
|
from destiny_sdk.enhancements import Enhancement, EnhancementFileInput
|
|
9
9
|
from destiny_sdk.identifiers import ExternalIdentifier
|
|
10
10
|
from destiny_sdk.visibility import Visibility
|
|
@@ -19,7 +19,7 @@ class Reference(_JsonlFileInputMixIn, BaseModel):
|
|
|
19
19
|
default=Visibility.PUBLIC,
|
|
20
20
|
description="The level of visibility of the reference",
|
|
21
21
|
)
|
|
22
|
-
id:
|
|
22
|
+
id: UUID = Field(
|
|
23
23
|
description="The ID of the reference",
|
|
24
24
|
)
|
|
25
25
|
identifiers: list[ExternalIdentifier] | None = Field(
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
from enum import StrEnum, auto
|
|
4
4
|
from typing import Annotated, Any
|
|
5
5
|
|
|
6
|
-
from pydantic import
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field, HttpUrl
|
|
7
7
|
|
|
8
|
-
from destiny_sdk.core import _JsonlFileInputMixIn
|
|
8
|
+
from destiny_sdk.core import UUID, _JsonlFileInputMixIn
|
|
9
9
|
from destiny_sdk.enhancements import Enhancement
|
|
10
10
|
|
|
11
11
|
|
|
@@ -30,7 +30,7 @@ class LinkedRobotError(_JsonlFileInputMixIn, RobotError):
|
|
|
30
30
|
id is derived from the request id.
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
-
reference_id:
|
|
33
|
+
reference_id: UUID = Field(
|
|
34
34
|
description="The ID of the reference which caused the error."
|
|
35
35
|
)
|
|
36
36
|
|
|
@@ -38,7 +38,7 @@ class LinkedRobotError(_JsonlFileInputMixIn, RobotError):
|
|
|
38
38
|
class RobotResult(BaseModel):
|
|
39
39
|
"""Used to indicate to the repository that the robot has finished processing."""
|
|
40
40
|
|
|
41
|
-
request_id:
|
|
41
|
+
request_id: UUID
|
|
42
42
|
error: RobotError | None = Field(
|
|
43
43
|
default=None,
|
|
44
44
|
description="""
|
|
@@ -55,7 +55,7 @@ None, the repository will assume that the result file is ready for processing.
|
|
|
55
55
|
class RobotEnhancementBatchResult(BaseModel):
|
|
56
56
|
"""Used to indicate that the robot has finished processing a batch."""
|
|
57
57
|
|
|
58
|
-
request_id:
|
|
58
|
+
request_id: UUID
|
|
59
59
|
error: RobotError | None = Field(
|
|
60
60
|
default=None,
|
|
61
61
|
description="""
|
|
@@ -72,7 +72,7 @@ None, the repository will assume that the result file is ready for processing.
|
|
|
72
72
|
class RobotResultValidationEntry(_JsonlFileInputMixIn, BaseModel):
|
|
73
73
|
"""A single entry in the validation result file for a batch enhancement request."""
|
|
74
74
|
|
|
75
|
-
reference_id:
|
|
75
|
+
reference_id: UUID | None = Field(
|
|
76
76
|
default=None,
|
|
77
77
|
description=(
|
|
78
78
|
"The ID of the reference which was enhanced. "
|
|
@@ -99,7 +99,7 @@ EnhancementResultEntry = Annotated[
|
|
|
99
99
|
class RobotRequest(BaseModel):
|
|
100
100
|
"""A batch enhancement request from the repo to a robot."""
|
|
101
101
|
|
|
102
|
-
id:
|
|
102
|
+
id: UUID
|
|
103
103
|
reference_storage_url: HttpUrl = Field(
|
|
104
104
|
description="""
|
|
105
105
|
The URL at which the set of references are stored. The file is a jsonl
|
|
@@ -130,7 +130,7 @@ If the URL expires, a new one can be generated using
|
|
|
130
130
|
class RobotEnhancementBatch(BaseModel):
|
|
131
131
|
"""A robot enhancement batch from the repo to a robot."""
|
|
132
132
|
|
|
133
|
-
id:
|
|
133
|
+
id: UUID
|
|
134
134
|
reference_storage_url: HttpUrl = Field(
|
|
135
135
|
description="""
|
|
136
136
|
The URL at which the set of references are stored. The file is a jsonl
|
|
@@ -190,10 +190,10 @@ class _EnhancementRequestBase(BaseModel):
|
|
|
190
190
|
A enhancement request is a request to create one or more enhancements.
|
|
191
191
|
"""
|
|
192
192
|
|
|
193
|
-
robot_id:
|
|
193
|
+
robot_id: UUID = Field(
|
|
194
194
|
description="The robot to be used to create the enhancements."
|
|
195
195
|
)
|
|
196
|
-
reference_ids: list[
|
|
196
|
+
reference_ids: list[UUID] = Field(
|
|
197
197
|
description="The IDs of the references to be enhanced."
|
|
198
198
|
)
|
|
199
199
|
source: str | None = Field(
|
|
@@ -209,7 +209,7 @@ class EnhancementRequestIn(_EnhancementRequestBase):
|
|
|
209
209
|
class EnhancementRequestRead(_EnhancementRequestBase):
|
|
210
210
|
"""Core batch enhancement request class."""
|
|
211
211
|
|
|
212
|
-
id:
|
|
212
|
+
id: UUID
|
|
213
213
|
request_status: EnhancementRequestStatus = Field(
|
|
214
214
|
description="The status of the request to create enhancements",
|
|
215
215
|
)
|
|
@@ -264,7 +264,7 @@ class _RobotEnhancementBatchBase(BaseModel):
|
|
|
264
264
|
for processing.
|
|
265
265
|
"""
|
|
266
266
|
|
|
267
|
-
robot_id:
|
|
267
|
+
robot_id: UUID = Field(
|
|
268
268
|
description="The robot to be used to create the enhancements."
|
|
269
269
|
)
|
|
270
270
|
source: str | None = Field(
|
|
@@ -276,7 +276,7 @@ class _RobotEnhancementBatchBase(BaseModel):
|
|
|
276
276
|
class RobotEnhancementBatchRead(_RobotEnhancementBatchBase):
|
|
277
277
|
"""Core robot enhancement batch class."""
|
|
278
278
|
|
|
279
|
-
id:
|
|
279
|
+
id: UUID
|
|
280
280
|
reference_data_url: HttpUrl | None = Field(
|
|
281
281
|
default=None,
|
|
282
282
|
description="""
|
|
@@ -343,7 +343,7 @@ class RobotIn(_RobotBase):
|
|
|
343
343
|
class Robot(_RobotBase):
|
|
344
344
|
"""Then model for a registered robot."""
|
|
345
345
|
|
|
346
|
-
id:
|
|
346
|
+
id: UUID = Field(
|
|
347
347
|
description="The id of the robot provided by destiny repository. "
|
|
348
348
|
"Used as the client_id when sending HMAC authenticated requests."
|
|
349
349
|
)
|
|
@@ -366,7 +366,7 @@ class ProvisionedRobot(Robot):
|
|
|
366
366
|
class _RobotAutomationBase(BaseModel):
|
|
367
367
|
"""Base Robot Automation class."""
|
|
368
368
|
|
|
369
|
-
robot_id:
|
|
369
|
+
robot_id: UUID = Field(
|
|
370
370
|
description="The ID of the robot that will be used to enhance the reference."
|
|
371
371
|
)
|
|
372
372
|
query: dict[str, Any] = Field(
|
|
@@ -394,6 +394,6 @@ class RobotAutomation(_RobotAutomationBase):
|
|
|
394
394
|
is sent to the specified robot to perform the enhancement.
|
|
395
395
|
"""
|
|
396
396
|
|
|
397
|
-
id:
|
|
397
|
+
id: UUID = Field(
|
|
398
398
|
description="The ID of the robot automation.",
|
|
399
399
|
)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""Tests for HMAC Authentication."""
|
|
2
2
|
|
|
3
3
|
import time
|
|
4
|
-
import uuid
|
|
5
4
|
from collections.abc import AsyncGenerator
|
|
5
|
+
from uuid import uuid7
|
|
6
6
|
|
|
7
7
|
import destiny_sdk
|
|
8
8
|
import pytest
|
|
@@ -11,7 +11,7 @@ from fastapi import APIRouter, Depends, FastAPI, status
|
|
|
11
11
|
from httpx import ASGITransport, AsyncClient
|
|
12
12
|
|
|
13
13
|
TEST_SECRET_KEY = "dlfskdfhgk8ei346oiehslkdfrerikfglser934utofs"
|
|
14
|
-
TEST_CLIENT_ID =
|
|
14
|
+
TEST_CLIENT_ID = uuid7()
|
|
15
15
|
REQUEST_BODY = b'{"message": "info"}'
|
|
16
16
|
|
|
17
17
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Tests client authentication"""
|
|
2
2
|
|
|
3
3
|
import time
|
|
4
|
-
import
|
|
4
|
+
from uuid import UUID, uuid7
|
|
5
5
|
|
|
6
6
|
import httpx
|
|
7
7
|
import pytest
|
|
@@ -43,7 +43,7 @@ def base_url():
|
|
|
43
43
|
|
|
44
44
|
@pytest.fixture
|
|
45
45
|
def test_reference_id():
|
|
46
|
-
return
|
|
46
|
+
return uuid7()
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
@pytest.fixture
|
|
@@ -66,19 +66,19 @@ class TestRobotClient:
|
|
|
66
66
|
) -> None:
|
|
67
67
|
"""Test that robot enhancement batch result request is authorized."""
|
|
68
68
|
fake_secret_key = "asdfhjgji94523q0uflsjf349wjilsfjd9q23"
|
|
69
|
-
fake_robot_id =
|
|
69
|
+
fake_robot_id = uuid7()
|
|
70
70
|
fake_destiny_repository_url = (
|
|
71
71
|
"https://www.destiny-repository-lives-here.co.au/v1"
|
|
72
72
|
)
|
|
73
73
|
|
|
74
74
|
fake_batch_result = RobotEnhancementBatchResult(
|
|
75
|
-
request_id=
|
|
75
|
+
request_id=uuid7(),
|
|
76
76
|
error=RobotError(message="Cannot process this batch"),
|
|
77
77
|
)
|
|
78
78
|
|
|
79
79
|
expected_response_body = RobotEnhancementBatchRead(
|
|
80
|
-
id=
|
|
81
|
-
robot_id=
|
|
80
|
+
id=uuid7(),
|
|
81
|
+
robot_id=uuid7(),
|
|
82
82
|
error="Cannot process this batch",
|
|
83
83
|
)
|
|
84
84
|
|
|
@@ -126,7 +126,7 @@ class TestOAuthClient:
|
|
|
126
126
|
httpx_mock: HTTPXMock,
|
|
127
127
|
oauth_client: OAuthClient,
|
|
128
128
|
base_url: str,
|
|
129
|
-
test_reference_id:
|
|
129
|
+
test_reference_id: UUID,
|
|
130
130
|
mock_reference_response: dict,
|
|
131
131
|
) -> None:
|
|
132
132
|
"""Test that search works without authentication."""
|
|
@@ -199,7 +199,7 @@ class TestOAuthClient:
|
|
|
199
199
|
httpx_mock: HTTPXMock,
|
|
200
200
|
oauth_client: OAuthClient,
|
|
201
201
|
base_url: str,
|
|
202
|
-
test_reference_id:
|
|
202
|
+
test_reference_id: UUID,
|
|
203
203
|
) -> None:
|
|
204
204
|
"""Test lookup references by identifiers."""
|
|
205
205
|
httpx_mock.add_response(
|