destiny_sdk 0.7.5__py3-none-any.whl → 0.7.6__py3-none-any.whl

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/client.py CHANGED
@@ -213,8 +213,8 @@ class OAuthMiddleware(httpx.Auth):
213
213
 
214
214
  auth = OAuthMiddleware(
215
215
  azure_client_id="client-id",
216
- azure_application_id="login-url",
217
- azure_tenant_id="tenant-id",
216
+ azure_application_id="application-id",
217
+ azure_login_url="login-url",
218
218
  )
219
219
 
220
220
  **Confidential Client Application (client credentials)**
@@ -260,11 +260,9 @@ class OAuthMiddleware(httpx.Auth):
260
260
  """
261
261
  Initialize the auth middleware.
262
262
 
263
- :param tenant_id: The OAuth2 tenant ID.
264
- :type tenant_id: str
265
- :param client_id: The OAuth2 client ID.
266
- :type client_id: str
267
- :param application_id: The application ID for the Destiny API.
263
+ :param azure_client_id: The OAuth2 client ID.
264
+ :type azure_client_id: str
265
+ :param azure_application_id: The application ID for the Destiny API.
268
266
  :type application_id: str
269
267
  :param azure_login_url: The Azure login URL.
270
268
  :type azure_login_url: str
@@ -5,7 +5,7 @@ import json
5
5
  from enum import StrEnum, auto
6
6
  from typing import Annotated, Any, Literal, Self
7
7
 
8
- from pydantic import UUID4, BaseModel, Field, HttpUrl, model_validator
8
+ from pydantic import UUID4, BaseModel, Field, HttpUrl, field_validator, model_validator
9
9
 
10
10
  from destiny_sdk.core import _JsonlFileInputMixIn
11
11
  from destiny_sdk.identifiers import Identifier
@@ -70,6 +70,42 @@ class Authorship(BaseModel):
70
70
  )
71
71
 
72
72
 
73
+ class Pagination(BaseModel):
74
+ """
75
+ Pagination information for journal articles.
76
+
77
+ Maps to OpenAlex's work.biblio object. All fields are strings to match
78
+ OpenAlex's format, which may include non-numeric values like "Spring" or "A1".
79
+ """
80
+
81
+ volume: str | None = Field(
82
+ default=None,
83
+ description="The volume number of the journal/publication.",
84
+ )
85
+ issue: str | None = Field(
86
+ default=None,
87
+ description="The issue number of the journal/publication.",
88
+ )
89
+ first_page: str | None = Field(
90
+ default=None,
91
+ description="The first page number of the reference in the publication.",
92
+ )
93
+ last_page: str | None = Field(
94
+ default=None,
95
+ description="The last page number of the reference in the publication.",
96
+ )
97
+
98
+ @field_validator("volume", "issue", "first_page", "last_page", mode="before")
99
+ @classmethod
100
+ def normalize_pagination_string(cls, value: str | None) -> str | None:
101
+ """Normalize pagination strings: NBSP to space, strip, empty to None."""
102
+ if isinstance(value, str):
103
+ # Replace NBSP with space, then strip
104
+ value = value.replace("\u00a0", " ").strip()
105
+ return value if value else None
106
+ return value
107
+
108
+
73
109
  class BibliographicMetadataEnhancement(BaseModel):
74
110
  """
75
111
  An enhancement which is made up of bibliographic metadata.
@@ -111,6 +147,10 @@ other works have cited this work
111
147
  description="The name of the entity which published the version of record.",
112
148
  )
113
149
  title: str | None = Field(default=None, description="The title of the reference.")
150
+ pagination: Pagination | None = Field(
151
+ default=None,
152
+ description="Pagination info (volume, issue, pages).",
153
+ )
114
154
 
115
155
  @property
116
156
  def fingerprint(self) -> str:
@@ -1,10 +1,24 @@
1
1
  """Identifier classes for the Destiny SDK."""
2
2
 
3
+ import re
4
+ import unicodedata
3
5
  import uuid
4
6
  from enum import StrEnum, auto
5
7
  from typing import Annotated, Literal, Self
6
8
 
7
- from pydantic import UUID4, BaseModel, Field, TypeAdapter, field_validator
9
+ from pydantic import UUID4, BaseModel, Field, PositiveInt, TypeAdapter, field_validator
10
+
11
+ # Case-insensitive patterns for DOI URL prefix stripping
12
+ _DOI_URL_PREFIX_RE = re.compile(r"^(?:https?://)?(?:dx\.)?doi\.org/", re.IGNORECASE)
13
+ _DOI_SCHEME_RE = re.compile(r"^doi:\s*", re.IGNORECASE)
14
+
15
+ # Translation table for DOI canonicalization (extensible for future characters)
16
+ _DOI_CHAR_TRANSLATION = str.maketrans(
17
+ {
18
+ "\u00a0": " ", # NBSP -> space
19
+ "\u2010": "-", # Unicode hyphen -> ASCII hyphen
20
+ }
21
+ )
8
22
 
9
23
 
10
24
  class ExternalIdentifierType(StrEnum):
@@ -24,7 +38,7 @@ class ExternalIdentifierType(StrEnum):
24
38
  PM_ID = auto()
25
39
  """A PubMed ID which is a unique identifier for a document in PubMed."""
26
40
  PRO_QUEST = auto()
27
- """A ProQuest ID which is a unqiue identifier for a document in ProQuest."""
41
+ """A ProQuest ID which is a unique identifier for a document in ProQuest."""
28
42
  OPEN_ALEX = auto()
29
43
  """An OpenAlex ID which is a unique identifier for a document in OpenAlex."""
30
44
  OTHER = auto()
@@ -35,8 +49,13 @@ class DOIIdentifier(BaseModel):
35
49
  """An external identifier representing a DOI."""
36
50
 
37
51
  identifier: str = Field(
38
- description="The DOI of the reference.",
39
- pattern=r"^10\.\d{4,9}/[-._;()/:a-zA-Z0-9%<>\[\]+&]+$",
52
+ description=(
53
+ "The DOI of the reference. "
54
+ "Format: '10.<registrant>/<suffix>' where registrant is 4-9 digits "
55
+ "and suffix is any non-whitespace characters. "
56
+ "Examples: 10.1000/journal.pone.0001, 10.18730/9WQ$D, 10.1000/édition"
57
+ ),
58
+ pattern=r"^10\.\d{4,9}/\S+$",
40
59
  )
41
60
  identifier_type: Literal[ExternalIdentifierType.DOI] = Field(
42
61
  ExternalIdentifierType.DOI, description="The type of identifier used."
@@ -44,23 +63,33 @@ class DOIIdentifier(BaseModel):
44
63
 
45
64
  @field_validator("identifier", mode="before")
46
65
  @classmethod
47
- def remove_doi_url(cls, value: str) -> str:
48
- """Remove the URL part of the DOI if it exists."""
49
- return (
50
- value.removeprefix("http://")
51
- .removeprefix("https://")
52
- .removeprefix("doi.org/")
53
- .removeprefix("dx.doi.org/")
54
- .removeprefix("doi:")
55
- .strip()
56
- )
66
+ def canonicalize_doi(cls, value: str) -> str:
67
+ """
68
+ Canonicalize DOI: strip URL prefixes and normalize Unicode.
69
+
70
+ - NFC Unicode normalization
71
+ - Translate special characters (NBSP, Unicode hyphen) via translation table
72
+ - Case-insensitive URL/scheme prefix stripping
73
+ """
74
+ # NFC normalization first
75
+ value = unicodedata.normalize("NFC", str(value))
76
+ # Translate special characters and strip
77
+ value = value.translate(_DOI_CHAR_TRANSLATION).strip()
78
+ # Strip URL prefixes (case-insensitive)
79
+ value = _DOI_URL_PREFIX_RE.sub("", value)
80
+ value = _DOI_SCHEME_RE.sub("", value)
81
+ return value.strip()
57
82
 
58
83
 
59
84
  class ProQuestIdentifier(BaseModel):
60
85
  """An external identifier representing a ProQuest ID."""
61
86
 
62
87
  identifier: str = Field(
63
- description="The ProQuest id of the reference", pattern=r"[0-9]+$"
88
+ description=(
89
+ "The ProQuest ID of the reference. "
90
+ "Format: numeric digits only. Example: 12345678"
91
+ ),
92
+ pattern=r"[0-9]+$",
64
93
  )
65
94
  identifier_type: Literal[ExternalIdentifierType.PRO_QUEST] = Field(
66
95
  ExternalIdentifierType.PRO_QUEST, description="The type of identifier used."
@@ -84,12 +113,16 @@ class ERICIdentifier(BaseModel):
84
113
  """
85
114
  An external identifier representing an ERIC Number.
86
115
 
87
- An ERIC Number is defined as a unqiue identifiying number preceeded by
88
- EJ (for a journal article) or ED (for a non-journal document).
116
+ An ERIC Number is defined as a unique identifying number preceded by
117
+ ED (for a non-journal document) or EJ (for a journal article).
89
118
  """
90
119
 
91
120
  identifier: str = Field(
92
- description="The ERIC Number of the reference.", pattern=r"E[D|J][0-9]+$"
121
+ description=(
122
+ "The ERIC Number. "
123
+ "Format: 'ED' or 'EJ' followed by digits. Example: ED123456 or EJ789012"
124
+ ),
125
+ pattern=r"E[D|J][0-9]+$",
93
126
  )
94
127
  identifier_type: Literal[ExternalIdentifierType.ERIC] = Field(
95
128
  ExternalIdentifierType.ERIC, description="The type of identifier used."
@@ -112,7 +145,9 @@ class ERICIdentifier(BaseModel):
112
145
  class PubMedIdentifier(BaseModel):
113
146
  """An external identifier representing a PubMed ID."""
114
147
 
115
- identifier: int = Field(description="The PubMed ID of the reference.")
148
+ identifier: PositiveInt = Field(
149
+ description="The PubMed ID (PMID) of the reference. Example: 12345678",
150
+ )
116
151
  identifier_type: Literal[ExternalIdentifierType.PM_ID] = Field(
117
152
  ExternalIdentifierType.PM_ID, description="The type of identifier used."
118
153
  )
@@ -122,7 +157,11 @@ class OpenAlexIdentifier(BaseModel):
122
157
  """An external identifier representing an OpenAlex ID."""
123
158
 
124
159
  identifier: str = Field(
125
- description="The OpenAlex ID of the reference.", pattern=r"^W\d+$"
160
+ description=(
161
+ "The OpenAlex ID of the reference. "
162
+ "Format: 'W' followed by digits. Example: W2741809807"
163
+ ),
164
+ pattern=r"^W\d+$",
126
165
  )
127
166
  identifier_type: Literal[ExternalIdentifierType.OPEN_ALEX] = Field(
128
167
  ExternalIdentifierType.OPEN_ALEX, description="The type of identifier used."
@@ -1,19 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: destiny_sdk
3
- Version: 0.7.5
3
+ Version: 0.7.6
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
- Requires-Python: ~=3.12
8
+ Requires-Python: <4,>=3.12
9
9
  Requires-Dist: cachetools<6,>=5.5.2
10
10
  Requires-Dist: fastapi<0.116,>=0.115.12
11
11
  Requires-Dist: httpx<0.29,>=0.28.1
12
12
  Requires-Dist: msal>=1.34.0
13
13
  Requires-Dist: pydantic<3,>=2.11.3
14
- Requires-Dist: pytest-asyncio<2,>=1.0.0
15
- Requires-Dist: pytest-httpx<0.36,>=0.35.0
16
- Requires-Dist: pytest<9,>=8.4.0
17
14
  Requires-Dist: python-jose<4,>=3.4.0
18
15
  Provides-Extra: labs
19
16
  Description-Content-Type: text/markdown
@@ -1,9 +1,9 @@
1
1
  destiny_sdk/__init__.py,sha256=NdSlsPQyDF3TW30_JzbvYMRBRA9iT677iTRWWCMdYOA,382
2
2
  destiny_sdk/auth.py,sha256=bY72ywZEcG_67YBd9PrwgWTXkCf58rhLvVEXrtXbWtA,6247
3
- destiny_sdk/client.py,sha256=XJi4LXrG6C_0aWSiEE27Ehmq17dE9MdaEOidb1ki_nY,21979
3
+ destiny_sdk/client.py,sha256=Hrsw_ofThHTy-WkmDYO2C0DLwXx4_4dEBEROXSl00dk,21925
4
4
  destiny_sdk/core.py,sha256=E0Wotu9psggK1JRJxbvx3Jc7WEGE6zaz2R2awvRrLz8,2023
5
- destiny_sdk/enhancements.py,sha256=79-lXOowRPC3EdM3PZPkcbnVIj5Bnbp_GIlVuEYhLkM,15707
6
- destiny_sdk/identifiers.py,sha256=JoJvRBzK4pGTRB03mCaGPyjd32tqZLT5hnRgc9Yi95Q,9828
5
+ destiny_sdk/enhancements.py,sha256=oqkDu7slpQxJYBYO_xbZ8-wWmAmm5tMjYRD9NFCRY-o,17123
6
+ destiny_sdk/identifiers.py,sha256=0D2FTR0SO_WJNfWgXVg8M1T0VmtvHGxdIsWSiuQim7U,11215
7
7
  destiny_sdk/imports.py,sha256=b-rh-dt3NsyLGxqmVzIzKaHiXhbw-3wtAaBN-ZW-i1E,5940
8
8
  destiny_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  destiny_sdk/references.py,sha256=3Y8gBMTSyZY35S3pB1bnVHMai9RRiGeoGZysNvSo7kk,2553
@@ -15,7 +15,7 @@ destiny_sdk/labs/references.py,sha256=iZisRgGZ5c7X7uTFoe6Q0AwwFMa4yJbIoPUVv_hvOi
15
15
  destiny_sdk/parsers/__init__.py,sha256=d5gS--bXla_0I7e_9wTBnGWMXt2U8b-_ndeprTPe1hk,149
16
16
  destiny_sdk/parsers/eppi_parser.py,sha256=_1xnAT0F0o1HKpMWOGQbVS3VPOrhPqyzHDWR3CosWwk,9484
17
17
  destiny_sdk/parsers/exceptions.py,sha256=0Sc_M4j560Nqh4SjeP_YrgOUVagdIwWwRz24E6YlZ1k,573
18
- destiny_sdk-0.7.5.dist-info/METADATA,sha256=fgalMU_UB56EZrHU-emwsc5QuSunDO0f1QEl5iMSlcU,2685
19
- destiny_sdk-0.7.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
20
- destiny_sdk-0.7.5.dist-info/licenses/LICENSE,sha256=6QURU4gvvTjVZ5rfp5amZ6FtFvcpPhAGUjxF5WSZAHI,9138
21
- destiny_sdk-0.7.5.dist-info/RECORD,,
18
+ destiny_sdk-0.7.6.dist-info/METADATA,sha256=F1diRhYTbom-cL9sROdIiItvf4v4ACm0h7rLAgP18hs,2574
19
+ destiny_sdk-0.7.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
20
+ destiny_sdk-0.7.6.dist-info/licenses/LICENSE,sha256=6QURU4gvvTjVZ5rfp5amZ6FtFvcpPhAGUjxF5WSZAHI,9138
21
+ destiny_sdk-0.7.6.dist-info/RECORD,,