destiny_sdk 0.2.0.post0__tar.gz → 0.2.3__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 (32) hide show
  1. destiny_sdk-0.2.3/.gitignore +199 -0
  2. {destiny_sdk-0.2.0.post0 → destiny_sdk-0.2.3}/PKG-INFO +21 -26
  3. {destiny_sdk-0.2.0.post0 → destiny_sdk-0.2.3}/README.md +7 -7
  4. destiny_sdk-0.2.3/pyproject.toml +43 -0
  5. {destiny_sdk-0.2.0.post0 → destiny_sdk-0.2.3}/src/destiny_sdk/core.py +15 -1
  6. {destiny_sdk-0.2.0.post0 → destiny_sdk-0.2.3}/src/destiny_sdk/enhancements.py +2 -2
  7. {destiny_sdk-0.2.0.post0 → destiny_sdk-0.2.3}/src/destiny_sdk/identifiers.py +10 -7
  8. destiny_sdk-0.2.3/src/destiny_sdk/parsers/__init__.py +5 -0
  9. destiny_sdk-0.2.3/src/destiny_sdk/parsers/eppi_parser.py +173 -0
  10. destiny_sdk-0.2.3/tests/unit/__init__.py +0 -0
  11. destiny_sdk-0.2.3/tests/unit/conftest.py +15 -0
  12. destiny_sdk-0.2.3/tests/unit/parsers/test_eppi_parser.py +47 -0
  13. destiny_sdk-0.2.3/tests/unit/test_auth.py +195 -0
  14. destiny_sdk-0.2.3/tests/unit/test_client.py +66 -0
  15. destiny_sdk-0.2.3/tests/unit/test_data/eppi_import.jsonl +4 -0
  16. destiny_sdk-0.2.3/tests/unit/test_data/eppi_import_with_annotations.jsonl +4 -0
  17. destiny_sdk-0.2.3/tests/unit/test_data/eppi_report.json +41 -0
  18. destiny_sdk-0.2.3/tests/unit/test_enhancements.py +126 -0
  19. destiny_sdk-0.2.3/tests/unit/test_identifiers.py +104 -0
  20. destiny_sdk-0.2.3/tests/unit/test_references.py +114 -0
  21. destiny_sdk-0.2.3/tests/unit/test_robots.py +61 -0
  22. destiny_sdk-0.2.3/uv.lock +591 -0
  23. destiny_sdk-0.2.0.post0/pyproject.toml +0 -41
  24. {destiny_sdk-0.2.0.post0 → destiny_sdk-0.2.3}/LICENSE +0 -0
  25. {destiny_sdk-0.2.0.post0 → destiny_sdk-0.2.3}/src/destiny_sdk/__init__.py +0 -0
  26. {destiny_sdk-0.2.0.post0 → destiny_sdk-0.2.3}/src/destiny_sdk/auth.py +0 -0
  27. {destiny_sdk-0.2.0.post0 → destiny_sdk-0.2.3}/src/destiny_sdk/client.py +0 -0
  28. {destiny_sdk-0.2.0.post0 → destiny_sdk-0.2.3}/src/destiny_sdk/imports.py +0 -0
  29. {destiny_sdk-0.2.0.post0 → destiny_sdk-0.2.3}/src/destiny_sdk/py.typed +0 -0
  30. {destiny_sdk-0.2.0.post0 → destiny_sdk-0.2.3}/src/destiny_sdk/references.py +0 -0
  31. {destiny_sdk-0.2.0.post0 → destiny_sdk-0.2.3}/src/destiny_sdk/robots.py +0 -0
  32. {destiny_sdk-0.2.0.post0 → destiny_sdk-0.2.3}/src/destiny_sdk/visibility.py +0 -0
@@ -0,0 +1,199 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+ docs/html/
74
+
75
+ # PyBuilder
76
+ .pybuilder/
77
+ target/
78
+
79
+ # Jupyter Notebook
80
+ .ipynb_checkpoints
81
+
82
+ # IPython
83
+ profile_default/
84
+ ipython_config.py
85
+
86
+ # pyenv
87
+ # For a library or package, you might want to ignore these files since the code is
88
+ # intended to run in multiple environments; otherwise, check them in:
89
+ # .python-version
90
+
91
+ # pipenv
92
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
94
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
95
+ # install all needed dependencies.
96
+ #Pipfile.lock
97
+
98
+ # UV
99
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
100
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
101
+ # commonly ignored for libraries.
102
+ #uv.lock
103
+
104
+ # poetry
105
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
106
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
107
+ # commonly ignored for libraries.
108
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
109
+ #poetry.lock
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ #pdm.lock
114
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
115
+ # in version control.
116
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
117
+ .pdm.toml
118
+ .pdm-python
119
+ .pdm-build/
120
+
121
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
122
+ __pypackages__/
123
+
124
+ # Celery stuff
125
+ celerybeat-schedule
126
+ celerybeat.pid
127
+
128
+ # SageMath parsed files
129
+ *.sage.py
130
+
131
+ # Environments
132
+ .env
133
+ .venv
134
+ env/
135
+ venv/
136
+ ENV/
137
+ env.bak/
138
+ venv.bak/
139
+ cli/.env.*
140
+ !/cli/.env.example
141
+
142
+ # Spyder project settings
143
+ .spyderproject
144
+ .spyproject
145
+
146
+ # Rope project settings
147
+ .ropeproject
148
+
149
+ # mkdocs documentation
150
+ /site
151
+
152
+ # mypy
153
+ .mypy_cache/
154
+ .dmypy.json
155
+ dmypy.json
156
+
157
+ # ruff
158
+ .ruff_cache/
159
+
160
+ # Pyre type checker
161
+ .pyre/
162
+
163
+ # pytype static type analyzer
164
+ .pytype/
165
+
166
+ # Cython debug symbols
167
+ cython_debug/
168
+
169
+ # PyCharm
170
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
171
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
172
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
173
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
174
+ #.idea/
175
+
176
+ # PyPI configuration file
177
+ .pypirc
178
+
179
+ # Terraform
180
+ .terraform/
181
+
182
+ # VS Code Workspace settings
183
+ *.code-workspace
184
+ .vscode/
185
+
186
+ # Mac Finder config
187
+ .DS_Store
188
+
189
+ # MinIO presigned URLs
190
+ .minio/presigned_urls.json
191
+
192
+ # delete-me working files
193
+ tmp-scripts.sh
194
+ infra/app/*.py
195
+
196
+
197
+ .certs
198
+
199
+ libs/fake_data/*.jsonl
@@ -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
-
@@ -13,7 +13,7 @@ pip install destiny-sdk
13
13
  ```
14
14
 
15
15
  ```sh
16
- poetry add destiny-sdk
16
+ uv add destiny-sdk
17
17
  ```
18
18
 
19
19
  ## Development
@@ -21,21 +21,21 @@ poetry add destiny-sdk
21
21
  ### Dependencies
22
22
 
23
23
  ```sh
24
- poetry install
24
+ uv install
25
25
  ```
26
26
 
27
27
  ### Tests
28
28
 
29
29
  ```sh
30
- poetry run pytest
30
+ uv run pytest
31
31
  ```
32
32
 
33
33
  ### Installing as an editable package of another project
34
34
 
35
- 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.
35
+ 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.
36
36
 
37
37
  ```sh
38
- poetry add --editable ./PATH/TO/sdk/
38
+ uv add --editable ./PATH/TO/sdk/
39
39
  ```
40
40
 
41
41
  or replace the dependency in `pyproject.toml` with
@@ -49,8 +49,8 @@ destiny-sdk = {path = "./PATH/TO/sdk/", develop = true}
49
49
  If you want to use a local build of the sdk `z.whl`, do
50
50
 
51
51
  ```sh
52
- poetry build
53
- poetry add ./PATH/TO/WHEEL.whl
52
+ uv build
53
+ uv add ./PATH/TO/WHEEL.whl
54
54
  ```
55
55
 
56
56
  ### Publishing
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ build-backend = "hatchling.build"
3
+ requires = ["hatchling"]
4
+
5
+ [dependency-groups]
6
+ dev = [
7
+ "mypy>=1.15.0,<2",
8
+ "pre-commit>=4.2.0,<5",
9
+ "pytest-env>=1.1.5,<2",
10
+ "ruff>=0.11.5,<0.12",
11
+ "uvloop>=0.21.0,<0.22",
12
+ ]
13
+
14
+ [project]
15
+ authors = [
16
+ {email = "adam@futureevidence.org", name = "Adam Hamilton"},
17
+ {email = "andrew@futureevidence.org", name = "Andrew Harvey"},
18
+ {email = "daniel@futureevidence.org", name = "Daniel Breves"},
19
+ {email = "jack@futureevidence.org", name = "Jack Walmisley"},
20
+ ]
21
+ dependencies = [
22
+ "cachetools>=5.5.2,<6",
23
+ "fastapi>=0.115.12,<0.116",
24
+ "httpx>=0.28.1,<0.29",
25
+ "pydantic>=2.11.3,<3",
26
+ "pytest-asyncio>=1.0.0,<2",
27
+ "pytest-httpx>=0.35.0,<0.36",
28
+ "pytest>=8.4.0,<9",
29
+ "python-jose>=3.4.0,<4",
30
+ ]
31
+ description = "A software development kit (sdk) to support interaction with the DESTINY repository"
32
+ license = "Apache-2.0"
33
+ name = "destiny_sdk"
34
+ readme = "README.md"
35
+ requires-python = "~=3.12"
36
+ version = "0.2.3"
37
+
38
+ [tool.pytest.ini_options]
39
+ addopts = ["--color=yes", "--import-mode=importlib", "--verbose"]
40
+ env = ["ENV=test"]
41
+ norecursedirs = ["tests/e2e"]
42
+ pythonpath = "."
43
+ testpaths = ["tests"]
@@ -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
File without changes
@@ -0,0 +1,15 @@
1
+ import asyncio
2
+ import logging
3
+ from typing import Any
4
+
5
+ import pytest
6
+
7
+ MIGRATION_TASK: asyncio.Task | None = None
8
+
9
+ logging.getLogger("asyncio").setLevel("DEBUG")
10
+
11
+
12
+ @pytest.fixture(scope="session", autouse=True)
13
+ def anyio_backend() -> tuple[str, dict[str, Any]]:
14
+ """Specify the anyio backend for async tests."""
15
+ return "asyncio", {"use_uvloop": True}