destiny_sdk 0.2.0.post0__py3-none-any.whl → 0.2.3__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/core.py CHANGED
@@ -4,6 +4,18 @@ from typing import Self
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
7
+ # These are non-standard newline characters that are not escaped by model_dump_json().
8
+ # We want jsonl files to have empirical new lines so they can be streamed line by line.
9
+ # Hence we replace each occurrence with standard new lines.
10
+ _ESCAPED_NEW_LINE = "\\n"
11
+ _UNSUPPORTED_NEWLINE_TRANSLATION = str.maketrans(
12
+ {
13
+ "\u0085": _ESCAPED_NEW_LINE,
14
+ "\u2028": _ESCAPED_NEW_LINE,
15
+ "\u2029": _ESCAPED_NEW_LINE,
16
+ }
17
+ )
18
+
7
19
 
8
20
  class _JsonlFileInputMixIn(BaseModel):
9
21
  """
@@ -20,7 +32,9 @@ class _JsonlFileInputMixIn(BaseModel):
20
32
  :return: The JSONL string representation of the model.
21
33
  :rtype: str
22
34
  """
23
- return self.model_dump_json(exclude_none=True)
35
+ return self.model_dump_json(exclude_none=True).translate(
36
+ _UNSUPPORTED_NEWLINE_TRANSLATION
37
+ )
24
38
 
25
39
  @classmethod
26
40
  def from_jsonl(cls, jsonl: str) -> Self:
@@ -58,7 +58,7 @@ class Authorship(BaseModel):
58
58
  """
59
59
 
60
60
  display_name: str = Field(description="The display name of the author.")
61
- orcid: str = Field(description="The ORCid of the author.")
61
+ orcid: str | None = Field(default=None, description="The ORCid of the author.")
62
62
  position: AuthorPosition = Field(
63
63
  description="The position of the author within the list of authors."
64
64
  )
@@ -105,7 +105,7 @@ other works have cited this work
105
105
 
106
106
  class AbstractProcessType(StrEnum):
107
107
  """
108
- The process used to acquyire the abstract.
108
+ The process used to acquire the abstract.
109
109
 
110
110
  **Allowed values**:
111
111
  - `uninverted`
@@ -27,13 +27,6 @@ class ExternalIdentifierType(StrEnum):
27
27
  OTHER = auto()
28
28
 
29
29
 
30
- def remove_doi_url(value: str) -> str:
31
- """Remove the URL part of the DOI if it exists."""
32
- return (
33
- value.removeprefix("http://doi.org/").removeprefix("https://doi.org/").strip()
34
- )
35
-
36
-
37
30
  class DOIIdentifier(BaseModel):
38
31
  """An external identifier representing a DOI."""
39
32
 
@@ -75,6 +68,16 @@ class OpenAlexIdentifier(BaseModel):
75
68
  ExternalIdentifierType.OPEN_ALEX, description="The type of identifier used."
76
69
  )
77
70
 
71
+ @field_validator("identifier", mode="before")
72
+ @classmethod
73
+ def remove_open_alex_url(cls, value: str) -> str:
74
+ """Remove the OpenAlex URL if it exists."""
75
+ return (
76
+ value.removeprefix("http://openalex.org/")
77
+ .removeprefix("https://openalex.org/")
78
+ .strip()
79
+ )
80
+
78
81
 
79
82
  class OtherIdentifier(BaseModel):
80
83
  """An external identifier not otherwise defined by the repository."""
@@ -0,0 +1,5 @@
1
+ """The parsers module contains the parsers for the Destiny SDK."""
2
+
3
+ from destiny_sdk.parsers.eppi_parser import EPPIParser
4
+
5
+ __all__ = ["EPPIParser"]
@@ -0,0 +1,173 @@
1
+ """Parser for a EPPI JSON export file."""
2
+
3
+ from typing import Any
4
+
5
+ from destiny_sdk.enhancements import (
6
+ AbstractContentEnhancement,
7
+ AbstractProcessType,
8
+ AnnotationEnhancement,
9
+ AnnotationType,
10
+ AuthorPosition,
11
+ Authorship,
12
+ BibliographicMetadataEnhancement,
13
+ BooleanAnnotation,
14
+ EnhancementContent,
15
+ EnhancementFileInput,
16
+ )
17
+ from destiny_sdk.identifiers import (
18
+ DOIIdentifier,
19
+ ExternalIdentifier,
20
+ ExternalIdentifierType,
21
+ )
22
+ from destiny_sdk.references import ReferenceFileInput
23
+ from destiny_sdk.visibility import Visibility
24
+
25
+
26
+ class EPPIParser:
27
+ """
28
+ Parser for an EPPI JSON export file.
29
+
30
+ See example here: https://eppi.ioe.ac.uk/cms/Portals/35/Maps/Examples/example_orignal.json
31
+ """
32
+
33
+ version = "1.0"
34
+
35
+ def __init__(self, tags: list[str] | None = None) -> None:
36
+ """
37
+ Initialize the EPPIParser with optional tags.
38
+
39
+ Args:
40
+ tags (list[str] | None): Optional list of tags to annotate references.
41
+
42
+ """
43
+ self.tags = tags or []
44
+ self.parser_source = f"destiny_sdk.eppi_parser@{self.version}"
45
+
46
+ def _parse_identifiers(
47
+ self, ref_to_import: dict[str, Any]
48
+ ) -> list[ExternalIdentifier]:
49
+ identifiers = []
50
+ if doi := ref_to_import.get("DOI"):
51
+ identifiers.append(
52
+ DOIIdentifier(
53
+ identifier=doi,
54
+ identifier_type=ExternalIdentifierType.DOI,
55
+ )
56
+ )
57
+ return identifiers
58
+
59
+ def _parse_abstract_enhancement(
60
+ self, ref_to_import: dict[str, Any]
61
+ ) -> EnhancementContent | None:
62
+ if abstract := ref_to_import.get("Abstract"):
63
+ return AbstractContentEnhancement(
64
+ process=AbstractProcessType.OTHER,
65
+ abstract=abstract,
66
+ )
67
+ return None
68
+
69
+ def _parse_bibliographic_enhancement(
70
+ self, ref_to_import: dict[str, Any]
71
+ ) -> EnhancementContent | None:
72
+ title = ref_to_import.get("Title")
73
+ publication_year = (
74
+ int(year)
75
+ if (year := ref_to_import.get("Year")) and year.isdigit()
76
+ else None
77
+ )
78
+ publisher = ref_to_import.get("Publisher")
79
+ authors_string = ref_to_import.get("Authors")
80
+
81
+ authorships = []
82
+ if authors_string:
83
+ authors = [
84
+ author.strip() for author in authors_string.split(";") if author.strip()
85
+ ]
86
+ for i, author_name in enumerate(authors):
87
+ position = AuthorPosition.MIDDLE
88
+ if i == 0:
89
+ position = AuthorPosition.FIRST
90
+ if i == len(authors) - 1 and i > 0:
91
+ position = AuthorPosition.LAST
92
+
93
+ authorships.append(
94
+ Authorship(
95
+ display_name=author_name,
96
+ position=position,
97
+ )
98
+ )
99
+
100
+ if not title and not publication_year and not publisher and not authorships:
101
+ return None
102
+
103
+ return BibliographicMetadataEnhancement(
104
+ title=title,
105
+ publication_year=publication_year,
106
+ publisher=publisher,
107
+ authorship=authorships if authorships else None,
108
+ )
109
+
110
+ def _create_annotation_enhancement(self) -> EnhancementContent | None:
111
+ if not self.tags:
112
+ return None
113
+ annotations = [
114
+ BooleanAnnotation(
115
+ annotation_type=AnnotationType.BOOLEAN,
116
+ scheme=self.parser_source,
117
+ label=tag,
118
+ value=True,
119
+ )
120
+ for tag in self.tags
121
+ ]
122
+ return AnnotationEnhancement(
123
+ annotations=annotations,
124
+ )
125
+
126
+ def parse_data(
127
+ self, data: dict, source: str | None = None, robot_version: str | None = None
128
+ ) -> list[ReferenceFileInput]:
129
+ """
130
+ Parse an EPPI JSON export dict and return a list of ReferenceFileInput objects.
131
+
132
+ Args:
133
+ data (dict): Parsed EPPI JSON export data.
134
+ source (str | None): Optional source string for deduplication/provenance.
135
+ robot_version (str | None): Optional robot version string for provenance.
136
+ Defaults to parser version.
137
+
138
+ Returns:
139
+ list[ReferenceFileInput]: List of parsed references from the data.
140
+
141
+ """
142
+ parser_source = source if source is not None else self.parser_source
143
+ references = []
144
+ for ref_to_import in data.get("References", []):
145
+ enhancement_contents = [
146
+ content
147
+ for content in [
148
+ self._parse_abstract_enhancement(ref_to_import),
149
+ self._parse_bibliographic_enhancement(ref_to_import),
150
+ self._create_annotation_enhancement(),
151
+ ]
152
+ if content
153
+ ]
154
+
155
+ enhancements = [
156
+ EnhancementFileInput(
157
+ source=parser_source,
158
+ visibility=Visibility.PUBLIC,
159
+ content=content,
160
+ enhancement_type=content.enhancement_type,
161
+ robot_version=robot_version,
162
+ )
163
+ for content in enhancement_contents
164
+ ]
165
+
166
+ references.append(
167
+ ReferenceFileInput(
168
+ visibility=Visibility.PUBLIC,
169
+ identifiers=self._parse_identifiers(ref_to_import),
170
+ enhancements=enhancements,
171
+ )
172
+ )
173
+ return references
@@ -1,23 +1,19 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: destiny_sdk
3
- Version: 0.2.0.post0
3
+ Version: 0.2.3
4
4
  Summary: A software development kit (sdk) to support interaction with the DESTINY repository
5
- License: Apache 2.0
6
- Author: Adam Hamilton
7
- Author-email: adam@futureevidence.org
8
- Requires-Python: >=3.12,<4.0
9
- Classifier: License :: Other/Proprietary License
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.12
12
- Classifier: Programming Language :: Python :: 3.13
13
- Requires-Dist: cachetools (>=5.5.2,<6.0.0)
14
- Requires-Dist: fastapi (>=0.115.12,<0.116.0)
15
- Requires-Dist: httpx (>=0.28.1,<0.29.0)
16
- Requires-Dist: pydantic (>=2.11.3,<3.0.0)
17
- Requires-Dist: pytest (>=8.4.0,<9.0.0)
18
- Requires-Dist: pytest-asyncio (>=1.0.0,<2.0.0)
19
- Requires-Dist: pytest-httpx (>=0.35.0,<0.36.0)
20
- Requires-Dist: python-jose (>=3.4.0,<4.0.0)
5
+ Author-email: Adam Hamilton <adam@futureevidence.org>, Andrew Harvey <andrew@futureevidence.org>, Daniel Breves <daniel@futureevidence.org>, Jack Walmisley <jack@futureevidence.org>
6
+ License-Expression: Apache-2.0
7
+ License-File: LICENSE
8
+ Requires-Python: ~=3.12
9
+ Requires-Dist: cachetools<6,>=5.5.2
10
+ Requires-Dist: fastapi<0.116,>=0.115.12
11
+ Requires-Dist: httpx<0.29,>=0.28.1
12
+ Requires-Dist: pydantic<3,>=2.11.3
13
+ Requires-Dist: pytest-asyncio<2,>=1.0.0
14
+ Requires-Dist: pytest-httpx<0.36,>=0.35.0
15
+ Requires-Dist: pytest<9,>=8.4.0
16
+ Requires-Dist: python-jose<4,>=3.4.0
21
17
  Description-Content-Type: text/markdown
22
18
 
23
19
  # DESTINY SDK
@@ -35,7 +31,7 @@ pip install destiny-sdk
35
31
  ```
36
32
 
37
33
  ```sh
38
- poetry add destiny-sdk
34
+ uv add destiny-sdk
39
35
  ```
40
36
 
41
37
  ## Development
@@ -43,21 +39,21 @@ poetry add destiny-sdk
43
39
  ### Dependencies
44
40
 
45
41
  ```sh
46
- poetry install
42
+ uv install
47
43
  ```
48
44
 
49
45
  ### Tests
50
46
 
51
47
  ```sh
52
- poetry run pytest
48
+ uv run pytest
53
49
  ```
54
50
 
55
51
  ### Installing as an editable package of another project
56
52
 
57
- Run the following command in the root folder of the other project (assuming poetry as a packaging framework). Pip also has an `--editable` option that you can use.
53
+ Run the following command in the root folder of the other project (assuming uv as a packaging framework). Pip also has an `--editable` option that you can use.
58
54
 
59
55
  ```sh
60
- poetry add --editable ./PATH/TO/sdk/
56
+ uv add --editable ./PATH/TO/sdk/
61
57
  ```
62
58
 
63
59
  or replace the dependency in `pyproject.toml` with
@@ -71,8 +67,8 @@ destiny-sdk = {path = "./PATH/TO/sdk/", develop = true}
71
67
  If you want to use a local build of the sdk `z.whl`, do
72
68
 
73
69
  ```sh
74
- poetry build
75
- poetry add ./PATH/TO/WHEEL.whl
70
+ uv build
71
+ uv add ./PATH/TO/WHEEL.whl
76
72
  ```
77
73
 
78
74
  ### Publishing
@@ -88,4 +84,3 @@ Given a version number `MAJOR.MINOR.PATCH`, increment the:
88
84
  - `MAJOR` version when you make incompatible API change
89
85
  - `MINOR` version when you add functionality in a backward compatible manner
90
86
  - `PATCH` version when you make backward compatible bug fixes
91
-
@@ -0,0 +1,17 @@
1
+ destiny_sdk/__init__.py,sha256=gmmrceJX84T4msk_GSm_OjTQvCpHFZRjnlUK5_7IODE,356
2
+ destiny_sdk/auth.py,sha256=bY72ywZEcG_67YBd9PrwgWTXkCf58rhLvVEXrtXbWtA,6247
3
+ destiny_sdk/client.py,sha256=ADPJhVA0vGR-wSI7tRxyUKwgd7xSEVFLIY2pLB7d9sc,3788
4
+ destiny_sdk/core.py,sha256=_FwDaczKTSaUSV_qfcnLhkBbZagh4ayFpN0qUwJ03-o,1448
5
+ destiny_sdk/enhancements.py,sha256=_S_anq194qdaEGklgycSG1qLEJeWzKe1u3oyXNxAg54,11800
6
+ destiny_sdk/identifiers.py,sha256=9CFEaPhZr2IHfL4RSAzOvidXhhzKLDPXiSKddBMWzwM,3606
7
+ destiny_sdk/imports.py,sha256=mfC1QXbAU27iD3hO9OFTRQes352F6DS1U4fSMw26-8w,8243
8
+ destiny_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ destiny_sdk/references.py,sha256=Dx-WKcv9gNJkKU9n52AYoEey7siTHR5_wBVBKSHND6Q,2321
10
+ destiny_sdk/robots.py,sha256=CL8hRTyHhTp4PShLmCuX2Ck4UBScPkOF44ivT1XRbMs,12806
11
+ destiny_sdk/visibility.py,sha256=nDLqnWuSZSk0vG3ynzDpHAvaALsbk8cuEZujTsqf6no,684
12
+ destiny_sdk/parsers/__init__.py,sha256=d5gS--bXla_0I7e_9wTBnGWMXt2U8b-_ndeprTPe1hk,149
13
+ destiny_sdk/parsers/eppi_parser.py,sha256=aSTqmm8N2WK1gnkj38XvfWaEXsaf0y6je6h9qN0xvFA,5584
14
+ destiny_sdk-0.2.3.dist-info/METADATA,sha256=UV-AOwRoOoCJsHyL6kQgHb0qLQEepDFt0PYvKNfPVHo,2440
15
+ destiny_sdk-0.2.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
+ destiny_sdk-0.2.3.dist-info/licenses/LICENSE,sha256=6QURU4gvvTjVZ5rfp5amZ6FtFvcpPhAGUjxF5WSZAHI,9138
17
+ destiny_sdk-0.2.3.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.3
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,15 +0,0 @@
1
- destiny_sdk/__init__.py,sha256=gmmrceJX84T4msk_GSm_OjTQvCpHFZRjnlUK5_7IODE,356
2
- destiny_sdk/auth.py,sha256=bY72ywZEcG_67YBd9PrwgWTXkCf58rhLvVEXrtXbWtA,6247
3
- destiny_sdk/client.py,sha256=ADPJhVA0vGR-wSI7tRxyUKwgd7xSEVFLIY2pLB7d9sc,3788
4
- destiny_sdk/core.py,sha256=GgNc7EncHKyZ5ppIG2CATWN2JFPjtA7IYhF0FutqwGE,945
5
- destiny_sdk/enhancements.py,sha256=Sciu5YCyYbOXCdlmqVdL6Z-J9nDjByj_H_QW4sfveU4,11780
6
- destiny_sdk/identifiers.py,sha256=JHxu-wM3T8kKtAYuk5dMec35Ay73KxYP7gthbSvOhuI,3480
7
- destiny_sdk/imports.py,sha256=mfC1QXbAU27iD3hO9OFTRQes352F6DS1U4fSMw26-8w,8243
8
- destiny_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- destiny_sdk/references.py,sha256=Dx-WKcv9gNJkKU9n52AYoEey7siTHR5_wBVBKSHND6Q,2321
10
- destiny_sdk/robots.py,sha256=CL8hRTyHhTp4PShLmCuX2Ck4UBScPkOF44ivT1XRbMs,12806
11
- destiny_sdk/visibility.py,sha256=nDLqnWuSZSk0vG3ynzDpHAvaALsbk8cuEZujTsqf6no,684
12
- destiny_sdk-0.2.0.post0.dist-info/LICENSE,sha256=6QURU4gvvTjVZ5rfp5amZ6FtFvcpPhAGUjxF5WSZAHI,9138
13
- destiny_sdk-0.2.0.post0.dist-info/METADATA,sha256=QojdOGuyFqMdwPqgKS_fAwY_G70BJwSfJy2SscbJD3c,2574
14
- destiny_sdk-0.2.0.post0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
15
- destiny_sdk-0.2.0.post0.dist-info/RECORD,,