destiny_sdk 0.7.3__tar.gz → 0.7.5__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 (38) hide show
  1. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/PKG-INFO +1 -1
  2. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/pyproject.toml +1 -1
  3. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/src/destiny_sdk/client.py +14 -0
  4. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/src/destiny_sdk/enhancements.py +46 -1
  5. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/src/destiny_sdk/identifiers.py +5 -2
  6. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/tests/unit/test_enhancements.py +51 -0
  7. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/.gitignore +0 -0
  8. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/LICENSE +0 -0
  9. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/README.md +0 -0
  10. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/src/destiny_sdk/__init__.py +0 -0
  11. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/src/destiny_sdk/auth.py +0 -0
  12. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/src/destiny_sdk/core.py +0 -0
  13. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/src/destiny_sdk/imports.py +0 -0
  14. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/src/destiny_sdk/labs/__init__.py +0 -0
  15. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/src/destiny_sdk/labs/references.py +0 -0
  16. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/src/destiny_sdk/parsers/__init__.py +0 -0
  17. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/src/destiny_sdk/parsers/eppi_parser.py +0 -0
  18. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/src/destiny_sdk/parsers/exceptions.py +0 -0
  19. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/src/destiny_sdk/py.typed +0 -0
  20. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/src/destiny_sdk/references.py +0 -0
  21. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/src/destiny_sdk/robots.py +0 -0
  22. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/src/destiny_sdk/search.py +0 -0
  23. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/src/destiny_sdk/visibility.py +0 -0
  24. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/tests/unit/__init__.py +0 -0
  25. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/tests/unit/conftest.py +0 -0
  26. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/tests/unit/labs/test_references.py +0 -0
  27. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/tests/unit/parsers/test_eppi_parser.py +0 -0
  28. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/tests/unit/test_auth.py +0 -0
  29. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/tests/unit/test_client.py +0 -0
  30. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/tests/unit/test_data/destiny_references.jsonl +0 -0
  31. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/tests/unit/test_data/eppi_import.jsonl +0 -0
  32. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/tests/unit/test_data/eppi_import_with_annotations.jsonl +0 -0
  33. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/tests/unit/test_data/eppi_import_with_raw.jsonl +0 -0
  34. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/tests/unit/test_data/eppi_report.json +0 -0
  35. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/tests/unit/test_identifiers.py +0 -0
  36. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/tests/unit/test_references.py +0 -0
  37. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/tests/unit/test_robots.py +0 -0
  38. {destiny_sdk-0.7.3 → destiny_sdk-0.7.5}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: destiny_sdk
3
- Version: 0.7.3
3
+ Version: 0.7.5
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
@@ -35,7 +35,7 @@ license = "Apache-2.0"
35
35
  name = "destiny_sdk"
36
36
  readme = "README.md"
37
37
  requires-python = "~=3.12"
38
- version = "0.7.3"
38
+ version = "0.7.5"
39
39
 
40
40
  [project.optional-dependencies]
41
41
  labs = []
@@ -479,6 +479,7 @@ class OAuthClient:
479
479
  self,
480
480
  base_url: HttpUrl | str,
481
481
  auth: httpx.Auth | None = None,
482
+ timeout: int = 10,
482
483
  ) -> None:
483
484
  """
484
485
  Initialize the client.
@@ -490,6 +491,8 @@ class OAuthClient:
490
491
  instance of ``OAuthMiddleware``, unless you need to create a custom auth
491
492
  class.
492
493
  :type auth: httpx.Auth | None
494
+ :param timeout: The timeout for requests, in seconds. Defaults to 10 seconds.
495
+ :type timeout: int
493
496
  """
494
497
  self._client = httpx.Client(
495
498
  base_url=str(base_url).removesuffix("/").removesuffix("/v1") + "/v1",
@@ -497,6 +500,7 @@ class OAuthClient:
497
500
  "Content-Type": "application/json",
498
501
  "User-Agent": user_agent,
499
502
  },
503
+ timeout=timeout,
500
504
  )
501
505
 
502
506
  if auth:
@@ -529,6 +533,7 @@ class OAuthClient:
529
533
  annotations: list[str | AnnotationFilter] | None = None,
530
534
  sort: str | None = None,
531
535
  page: int = 1,
536
+ timeout: int | None = None,
532
537
  ) -> ReferenceSearchResult:
533
538
  """
534
539
  Send a search request to the Destiny Repository API.
@@ -547,6 +552,9 @@ class OAuthClient:
547
552
  :type sort: str | None
548
553
  :param page: The page number of results to retrieve.
549
554
  :type page: int
555
+ :param timeout: The timeout for the request, in seconds. If provided, this will override
556
+ the client timeout.
557
+ :type timeout: int | None
550
558
  :return: The response from the API.
551
559
  :rtype: libs.sdk.src.destiny_sdk.references.ReferenceSearchResult
552
560
  """ # noqa: E501
@@ -562,6 +570,7 @@ class OAuthClient:
562
570
  response = self._client.get(
563
571
  "/references/search/",
564
572
  params=params,
573
+ timeout=timeout or httpx.USE_CLIENT_DEFAULT,
565
574
  )
566
575
  self._raise_for_status(response)
567
576
  return ReferenceSearchResult.model_validate(response.json())
@@ -569,6 +578,7 @@ class OAuthClient:
569
578
  def lookup(
570
579
  self,
571
580
  identifiers: list[str | IdentifierLookup],
581
+ timeout: int | None = None,
572
582
  ) -> list[Reference]:
573
583
  """
574
584
  Lookup references by identifiers.
@@ -577,6 +587,9 @@ class OAuthClient:
577
587
 
578
588
  :param identifiers: The identifiers to look up.
579
589
  :type identifiers: list[str | libs.sdk.src.destiny_sdk.identifiers.IdentifierLookup]
590
+ :param timeout: The timeout for the request, in seconds. If provided, this will override
591
+ the client timeout.
592
+ :type timeout: int | None
580
593
  :return: The list of references matching the identifiers.
581
594
  :rtype: list[libs.sdk.src.destiny_sdk.references.Reference]
582
595
  """ # noqa: E501
@@ -585,6 +598,7 @@ class OAuthClient:
585
598
  params={
586
599
  "identifier": ",".join([str(identifier) for identifier in identifiers])
587
600
  },
601
+ timeout=timeout or httpx.USE_CLIENT_DEFAULT,
588
602
  )
589
603
  self._raise_for_status(response)
590
604
  return TypeAdapter(list[Reference]).validate_python(response.json())
@@ -8,6 +8,7 @@ from typing import Annotated, Any, Literal, Self
8
8
  from pydantic import UUID4, BaseModel, Field, HttpUrl, model_validator
9
9
 
10
10
  from destiny_sdk.core import _JsonlFileInputMixIn
11
+ from destiny_sdk.identifiers import Identifier
11
12
  from destiny_sdk.visibility import Visibility
12
13
 
13
14
 
@@ -26,6 +27,8 @@ class EnhancementType(StrEnum):
26
27
  """A free-form enhancement for tagging with labels."""
27
28
  LOCATION = auto()
28
29
  """Locations where the reference can be found."""
30
+ REFERENCE_ASSOCIATION = auto()
31
+ """Associations to other references."""
29
32
  RAW = auto()
30
33
  """A free form enhancement for arbitrary/unstructured data."""
31
34
  FULL_TEXT = auto()
@@ -56,7 +59,11 @@ class Authorship(BaseModel):
56
59
  for our purposes.
57
60
  """
58
61
 
59
- display_name: str = Field(description="The display name of the author.")
62
+ display_name: str = Field(
63
+ description="The display name of the author. "
64
+ "Expected format FIRSTNAME <MIDDLENAME> LASTNAME. "
65
+ "Providing display_name in an unexpected format will affect search performance."
66
+ )
60
67
  orcid: str | None = Field(default=None, description="The ORCid of the author.")
61
68
  position: AuthorPosition = Field(
62
69
  description="The position of the author within the list of authors."
@@ -320,6 +327,43 @@ class LocationEnhancement(BaseModel):
320
327
  )
321
328
 
322
329
 
330
+ class ReferenceAssociationType(StrEnum):
331
+ """
332
+ The type of association between references.
333
+
334
+ Direction is important: "this reference <association_type> associated reference".
335
+ """
336
+
337
+ CITES = auto()
338
+ """This reference cites the related reference."""
339
+ IS_CITED_BY = auto()
340
+ """This reference is cited by the related reference."""
341
+ IS_SIMILAR_TO = auto()
342
+ """This reference is similar to the related reference."""
343
+
344
+
345
+ class ReferenceAssociationEnhancement(BaseModel):
346
+ """An enhancement for storing associations between references."""
347
+
348
+ enhancement_type: Literal[EnhancementType.REFERENCE_ASSOCIATION] = (
349
+ EnhancementType.REFERENCE_ASSOCIATION
350
+ )
351
+ associated_reference_ids: list[Identifier] = Field(
352
+ min_length=1,
353
+ description=(
354
+ "A list of Identifiers which are associated to this reference. "
355
+ "These can either be ExternalIdentifiers or resolved repository UUID4s."
356
+ ),
357
+ )
358
+ association_type: ReferenceAssociationType = Field(
359
+ description=(
360
+ "The type of association between this reference and the associated ones. "
361
+ "Direction is important: "
362
+ '"this reference <association_type> associated reference".'
363
+ )
364
+ )
365
+
366
+
323
367
  class RawEnhancement(BaseModel):
324
368
  """
325
369
  An enhancement for storing raw/arbitrary/unstructured data.
@@ -377,6 +421,7 @@ EnhancementContent = Annotated[
377
421
  | AbstractContentEnhancement
378
422
  | AnnotationEnhancement
379
423
  | LocationEnhancement
424
+ | ReferenceAssociationEnhancement
380
425
  | RawEnhancement,
381
426
  Field(discriminator="enhancement_type"),
382
427
  ]
@@ -165,6 +165,9 @@ ExternalIdentifier = Annotated[
165
165
  Field(discriminator="identifier_type"),
166
166
  ]
167
167
 
168
+ #: Any identifier including external identifiers and repository UUID4s.
169
+ Identifier = Annotated[ExternalIdentifier | UUID4, Field()]
170
+
168
171
  ExternalIdentifierAdapter: TypeAdapter[ExternalIdentifier] = TypeAdapter(
169
172
  ExternalIdentifier
170
173
  )
@@ -245,7 +248,7 @@ class IdentifierLookup(BaseModel):
245
248
  )
246
249
 
247
250
  @classmethod
248
- def from_identifier(cls, identifier: ExternalIdentifier | UUID4) -> Self:
251
+ def from_identifier(cls, identifier: Identifier) -> Self:
249
252
  """Create an IdentifierLookup from an ExternalIdentifier or UUID4."""
250
253
  if isinstance(identifier, uuid.UUID):
251
254
  return cls(identifier=str(identifier), identifier_type=None)
@@ -255,7 +258,7 @@ class IdentifierLookup(BaseModel):
255
258
  other_identifier_name=getattr(identifier, "other_identifier_name", None),
256
259
  )
257
260
 
258
- def to_identifier(self) -> ExternalIdentifier | UUID4:
261
+ def to_identifier(self) -> Identifier:
259
262
  """Convert into an ExternalIdentifier or UUID4 if it has no identifier_type."""
260
263
  if self.identifier_type is None:
261
264
  return UUID4(self.identifier)
@@ -173,3 +173,54 @@ def test_empty_location_enhancement_errors():
173
173
  enhancement_type=destiny_sdk.enhancements.EnhancementType.LOCATION,
174
174
  locations=[],
175
175
  )
176
+
177
+
178
+ def test_association_enhancement_valid():
179
+ # Create valid association content
180
+ association_content = destiny_sdk.enhancements.ReferenceAssociationEnhancement(
181
+ enhancement_type=destiny_sdk.enhancements.EnhancementType.REFERENCE_ASSOCIATION,
182
+ associated_reference_ids=[
183
+ uuid.uuid4(),
184
+ destiny_sdk.identifiers.OpenAlexIdentifier(
185
+ identifier="https://openalex.org/W1234567890",
186
+ identifier_type=destiny_sdk.identifiers.ExternalIdentifierType.OPEN_ALEX,
187
+ ),
188
+ ],
189
+ association_type=destiny_sdk.enhancements.ReferenceAssociationType.CITES,
190
+ )
191
+ enhancement = destiny_sdk.enhancements.Enhancement(
192
+ id=uuid.uuid4(),
193
+ source="test_source",
194
+ visibility="public",
195
+ robot_version="1.0",
196
+ enhancement_type=destiny_sdk.enhancements.EnhancementType.REFERENCE_ASSOCIATION,
197
+ content=association_content,
198
+ reference_id=uuid.uuid4(),
199
+ )
200
+ assert (
201
+ enhancement.content.association_type
202
+ == destiny_sdk.enhancements.ReferenceAssociationType.CITES
203
+ )
204
+ assert enhancement.content.associated_reference_ids[1].identifier == "W1234567890"
205
+
206
+
207
+ def test_association_enhancement_empty_associated_reference_ids_errors():
208
+ # Test that an empty associated_reference_ids list raises a validation error
209
+ with pytest.raises(ValidationError):
210
+ destiny_sdk.enhancements.ReferenceAssociationEnhancement(
211
+ enhancement_type=destiny_sdk.enhancements.EnhancementType.REFERENCE_ASSOCIATION,
212
+ associated_reference_ids=[],
213
+ association_type=destiny_sdk.enhancements.ReferenceAssociationType.CITES,
214
+ )
215
+
216
+
217
+ def test_association_enhancement_invalid_identifier_type_errors():
218
+ # Test that an invalid identifier type raises a validation error
219
+ with pytest.raises(ValidationError):
220
+ destiny_sdk.enhancements.ReferenceAssociationEnhancement(
221
+ enhancement_type=destiny_sdk.enhancements.EnhancementType.REFERENCE_ASSOCIATION,
222
+ associated_reference_ids=[
223
+ "random-string",
224
+ ],
225
+ association_type=destiny_sdk.enhancements.ReferenceAssociationType.CITES,
226
+ )
File without changes
File without changes
File without changes
File without changes