pystac-ext-scientific 1.0.0rc0__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 (29) hide show
  1. pystac_ext_scientific-1.0.0rc0/.gitignore +163 -0
  2. pystac_ext_scientific-1.0.0rc0/PKG-INFO +37 -0
  3. pystac_ext_scientific-1.0.0rc0/README.md +13 -0
  4. pystac_ext_scientific-1.0.0rc0/pyproject.toml +36 -0
  5. pystac_ext_scientific-1.0.0rc0/pystac/extensions/py.typed +0 -0
  6. pystac_ext_scientific-1.0.0rc0/pystac/extensions/scientific.py +360 -0
  7. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_citation.yaml +88 -0
  8. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_collection_citation.yaml +88 -0
  9. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_collection_doi.yaml +88 -0
  10. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_collection_publications.yaml +88 -0
  11. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_collection_publications_one.yaml +88 -0
  12. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_collection_remove_all_publications_one.yaml +88 -0
  13. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_collection_remove_all_publications_with_none.yaml +88 -0
  14. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_collection_remove_all_publications_with_some.yaml +88 -0
  15. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_collection_remove_publication_forward.yaml +88 -0
  16. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_collection_remove_publication_one.yaml +88 -0
  17. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_collection_remove_publication_reverse.yaml +88 -0
  18. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_doi.yaml +88 -0
  19. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_publications.yaml +88 -0
  20. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_publications_one.yaml +88 -0
  21. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_remove_all_publications_one.yaml +88 -0
  22. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_remove_all_publications_with_none.yaml +88 -0
  23. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_remove_all_publications_with_some.yaml +88 -0
  24. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_remove_publication_forward.yaml +88 -0
  25. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_remove_publication_one.yaml +88 -0
  26. pystac_ext_scientific-1.0.0rc0/tests/cassettes/test_scientific/test_remove_publication_reverse.yaml +88 -0
  27. pystac_ext_scientific-1.0.0rc0/tests/data-files/collection.json +63 -0
  28. pystac_ext_scientific-1.0.0rc0/tests/data-files/item.json +80 -0
  29. pystac_ext_scientific-1.0.0rc0/tests/test_scientific.py +514 -0
@@ -0,0 +1,163 @@
1
+ *.pyc
2
+ *.egg-info
3
+ *.eggs
4
+ .DS_Store
5
+ data
6
+ config.json
7
+ stdout*
8
+ /integration*
9
+ .idea
10
+ .vscode
11
+ .actrc
12
+
13
+
14
+ # Sphinx documentation
15
+ .ipynb_checkpoints/
16
+
17
+ docs/tutorials/pystac-example*
18
+ docs/tutorials/spacenet-stac/
19
+ docs/tutorials/spacenet-cog-stac/
20
+ docs/tutorials/data/
21
+ docs/quickstart_stac/
22
+
23
+ # Byte-compiled / optimized / DLL files
24
+ __pycache__/
25
+ *.py[cod]
26
+ *$py.class
27
+
28
+ # C extensions
29
+ *.so
30
+
31
+ # Distribution / packaging
32
+ .Python
33
+ build/
34
+ develop-eggs/
35
+ dist/
36
+ downloads/
37
+ eggs/
38
+ .eggs/
39
+ lib/
40
+ lib64/
41
+ parts/
42
+ sdist/
43
+ var/
44
+ wheels/
45
+ share/python-wheels/
46
+ *.egg-info/
47
+ .installed.cfg
48
+ *.egg
49
+ MANIFEST
50
+
51
+ # PyInstaller
52
+ # Usually these files are written by a python script from a template
53
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
54
+ *.manifest
55
+ *.spec
56
+
57
+ # Installer logs
58
+ pip-log.txt
59
+ pip-delete-this-directory.txt
60
+
61
+ # Unit test / coverage reports
62
+ htmlcov/
63
+ .tox/
64
+ .nox/
65
+ .coverage
66
+ .coverage.*
67
+ .cache
68
+ nosetests.xml
69
+ coverage.xml
70
+ *.cover
71
+ *.py,cover
72
+ .hypothesis/
73
+ .pytest_cache/
74
+ cover/
75
+
76
+ # Translations
77
+ *.mo
78
+ *.pot
79
+
80
+ # Django stuff:
81
+ *.log
82
+ local_settings.py
83
+ db.sqlite3
84
+ db.sqlite3-journal
85
+
86
+ # Flask stuff:
87
+ instance/
88
+ .webassets-cache
89
+
90
+ # Scrapy stuff:
91
+ .scrapy
92
+
93
+ # Sphinx documentation
94
+ docs/_build/
95
+
96
+ # PyBuilder
97
+ .pybuilder/
98
+ target/
99
+
100
+ # Jupyter Notebook
101
+ .ipynb_checkpoints
102
+
103
+ # IPython
104
+ profile_default/
105
+ ipython_config.py
106
+
107
+ # pyenv
108
+ # For a library or package, you might want to ignore these files since the code is
109
+ # intended to run in multiple environments; otherwise, check them in:
110
+ # .python-version
111
+
112
+ # pipenv
113
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
114
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
115
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
116
+ # install all needed dependencies.
117
+ # Pipfile.lock
118
+
119
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
120
+ __pypackages__/
121
+
122
+ # Celery stuff
123
+ celerybeat-schedule
124
+ celerybeat.pid
125
+
126
+ # SageMath parsed files
127
+ *.sage.py
128
+
129
+ # Environments
130
+ .env
131
+ .venv
132
+ env/
133
+ venv/
134
+ ENV/
135
+ env.bak/
136
+ venv.bak/
137
+
138
+ # Spyder project settings
139
+ .spyderproject
140
+ .spyproject
141
+
142
+ # Rope project settings
143
+ .ropeproject
144
+
145
+ # mkdocs documentation
146
+ /site
147
+
148
+ # mypy
149
+ .mypy_cache/
150
+ .dmypy.json
151
+ dmypy.json
152
+
153
+ # Pyre type checker
154
+ .pyre/
155
+
156
+ # pytype static type analyzer
157
+ .pytype/
158
+
159
+ # Cython debug symbols
160
+ cython_debug/
161
+
162
+ # asv environments
163
+ .asv
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: pystac-ext-scientific
3
+ Version: 1.0.0rc0
4
+ Summary: Scientific extension for PySTAC
5
+ Project-URL: Documentation, https://pystac.readthedocs.io
6
+ Project-URL: Repository, https://github.com/stac-utils/pystac
7
+ Project-URL: Issues, https://github.com/stac-utils/pystac/issues
8
+ Project-URL: Changelog, https://github.com/stac-utils/pystac/blob/main/CHANGELOG.md
9
+ Project-URL: Discussions, https://github.com/radiantearth/stac-spec/discussions/categories/stac-software
10
+ License: Apache-2.0
11
+ Keywords: STAC,catalog,imagery,pystac,raster,scientific
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Natural Language :: English
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: pystac-core
23
+ Description-Content-Type: text/markdown
24
+
25
+ # pystac-ext-scientific
26
+
27
+ [PySTAC](https://pypi.org/project/pystac/) extension package for the [Scientific Citation Extension](https://github.com/stac-extensions/scientific).
28
+ This extension provides fields for linking STAC items and collections to scientific publications, including DOIs and bibliographic citations.
29
+
30
+ ## Supported versions
31
+
32
+ - [v1.0.0](https://stac-extensions.github.io/scientific/v1.0.0/schema.json)
33
+
34
+ ## Versioning
35
+
36
+ This package's version corresponds to the version of the extension specification it targets.
37
+ When we release updates to the package code without changing the target extension version, we use [post releases](https://packaging.python.org/en/latest/discussions/versioning/#post-releases), e.g. `1.0.0.post1`.
@@ -0,0 +1,13 @@
1
+ # pystac-ext-scientific
2
+
3
+ [PySTAC](https://pypi.org/project/pystac/) extension package for the [Scientific Citation Extension](https://github.com/stac-extensions/scientific).
4
+ This extension provides fields for linking STAC items and collections to scientific publications, including DOIs and bibliographic citations.
5
+
6
+ ## Supported versions
7
+
8
+ - [v1.0.0](https://stac-extensions.github.io/scientific/v1.0.0/schema.json)
9
+
10
+ ## Versioning
11
+
12
+ This package's version corresponds to the version of the extension specification it targets.
13
+ When we release updates to the package code without changing the target extension version, we use [post releases](https://packaging.python.org/en/latest/discussions/versioning/#post-releases), e.g. `1.0.0.post1`.
@@ -0,0 +1,36 @@
1
+ [project]
2
+ name = "pystac-ext-scientific"
3
+ description = "Scientific extension for PySTAC"
4
+ readme = "README.md"
5
+ version = "1.0.0-rc.0"
6
+ authors = []
7
+ maintainers = []
8
+ keywords = ["pystac", "imagery", "raster", "catalog", "STAC", "scientific"]
9
+ license = { text = "Apache-2.0" }
10
+ classifiers = [
11
+ "Development Status :: 5 - Production/Stable",
12
+ "Intended Audience :: Developers",
13
+ "License :: OSI Approved :: Apache Software License",
14
+ "Natural Language :: English",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ ]
21
+ requires-python = ">=3.10"
22
+ dependencies = ["pystac-core"]
23
+
24
+ [project.urls]
25
+ Documentation = "https://pystac.readthedocs.io"
26
+ Repository = "https://github.com/stac-utils/pystac"
27
+ Issues = "https://github.com/stac-utils/pystac/issues"
28
+ Changelog = "https://github.com/stac-utils/pystac/blob/main/CHANGELOG.md"
29
+ Discussions = "https://github.com/radiantearth/stac-spec/discussions/categories/stac-software"
30
+
31
+ [build-system]
32
+ requires = ["hatchling"]
33
+ build-backend = "hatchling.build"
34
+
35
+ [tool.hatch.build.targets.wheel]
36
+ packages = ["pystac"]
@@ -0,0 +1,360 @@
1
+ """Implements the :stac-ext:`Scientific Citation Extension <scientific>`.
2
+
3
+ For a description of Digital Object Identifiers (DOIs), see the DOI Handbook:
4
+
5
+ https://doi.org/10.1000/182
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Generic, Literal, TypeVar, cast
11
+
12
+ import pystac
13
+ from pystac.extensions.base import (
14
+ ExtensionManagementMixin,
15
+ PropertiesExtension,
16
+ SummariesExtension,
17
+ )
18
+ from pystac.extensions.hooks import ExtensionHooks
19
+ from pystac.utils import StringEnum, map_opt
20
+
21
+ #: Generalized version of :class:`~pystac.Collection` or :class:`~pystac.Item`
22
+ T = TypeVar("T", pystac.Collection, pystac.Item)
23
+
24
+ SCHEMA_URI: str = "https://stac-extensions.github.io/scientific/v1.0.0/schema.json"
25
+ PREFIX: str = "sci:"
26
+
27
+ # Field names
28
+ DOI_PROP: str = PREFIX + "doi"
29
+ CITATION_PROP: str = PREFIX + "citation"
30
+ PUBLICATIONS_PROP: str = PREFIX + "publications"
31
+
32
+ DOI_URL_BASE = "https://doi.org/"
33
+
34
+
35
+ # Link rel type.
36
+ class ScientificRelType(StringEnum):
37
+ """A list of rel types defined in the Scientific Citation Extension.
38
+
39
+ See the :stac-ext:`Scientific Citation Extension Relation types
40
+ <scientific#relation-types>` documentation for details.
41
+ """
42
+
43
+ CITE_AS = "cite-as"
44
+ """Used to indicate a link to the publication referenced by the ``sci:doi``
45
+ field."""
46
+
47
+
48
+ def doi_to_url(doi: str) -> str:
49
+ """Converts a DOI to the corresponding URL."""
50
+ from urllib import parse
51
+
52
+ return DOI_URL_BASE + parse.quote(doi)
53
+
54
+
55
+ class Publication:
56
+ """Helper for Publication entries."""
57
+
58
+ citation: str | None
59
+ doi: str | None
60
+
61
+ def __init__(self, doi: str | None, citation: str | None) -> None:
62
+ self.doi = doi
63
+ self.citation = citation
64
+
65
+ def __eq__(self, other: Any) -> bool:
66
+ if not isinstance(other, Publication):
67
+ return NotImplemented
68
+
69
+ return self.doi == other.doi and self.citation == other.citation
70
+
71
+ def __repr__(self) -> str:
72
+ return f"<Publication doi={self.doi} target={self.citation}>"
73
+
74
+ def to_dict(self) -> dict[str, str | None]:
75
+ import copy
76
+
77
+ return copy.deepcopy({"doi": self.doi, "citation": self.citation})
78
+
79
+ @staticmethod
80
+ def from_dict(d: dict[str, str]) -> Publication:
81
+ return Publication(d.get("doi"), d.get("citation"))
82
+
83
+ def get_link(self) -> pystac.Link | None:
84
+ """Gets a :class:`~pystac.Link` for the DOI for this publication. If
85
+ :attr:`Publication.doi` is ``None``, this method will also return ``None``."""
86
+ if self.doi is None:
87
+ return None
88
+ return pystac.Link(ScientificRelType.CITE_AS, doi_to_url(self.doi))
89
+
90
+
91
+ def remove_link(links: list[pystac.Link], doi: str | None) -> None:
92
+ if doi is None:
93
+ return
94
+ url = doi_to_url(doi)
95
+ for i, a_link in enumerate(links):
96
+ if a_link.rel != ScientificRelType.CITE_AS:
97
+ continue
98
+ if a_link.target == url:
99
+ del links[i]
100
+ break
101
+
102
+
103
+ class ScientificExtension(
104
+ Generic[T],
105
+ PropertiesExtension,
106
+ ExtensionManagementMixin[pystac.Item | pystac.Collection],
107
+ ):
108
+ """An abstract class that can be used to extend the properties of an
109
+ :class:`~pystac.Item` or a :class:`pystac.Collection` with properties from the
110
+ :stac-ext:`Scientific Citation Extension <scientific>`. This class is generic over
111
+ the type of STAC Object to be extended (e.g. :class:`~pystac.Item`,
112
+ :class:`~pystac.Collection`).
113
+
114
+ To create a concrete instance of :class:`ScientificExtension`, use the
115
+ :meth:`ScientificExtension.ext` method. For example:
116
+
117
+ .. code-block:: python
118
+
119
+ >>> item: pystac.Item = ...
120
+ >>> sci_ext = ScientificExtension.ext(item)
121
+ """
122
+
123
+ name: Literal["sci"] = "sci"
124
+ obj: pystac.STACObject
125
+
126
+ def __init__(self, obj: pystac.STACObject) -> None:
127
+ self.obj = obj
128
+
129
+ def apply(
130
+ self,
131
+ doi: str | None = None,
132
+ citation: str | None = None,
133
+ publications: list[Publication] | None = None,
134
+ ) -> None:
135
+ """Applies scientific extension properties to the extended
136
+ :class:`~pystac.Item`.
137
+
138
+ Args:
139
+ doi : Optional DOI string for the item. Must not be a DOI link.
140
+ citation : Optional human-readable reference.
141
+ publications : Optional list of relevant publications
142
+ referencing and describing the data.
143
+ """
144
+ self.doi = doi
145
+ self.citation = citation
146
+ self.publications = publications
147
+
148
+ @property
149
+ def doi(self) -> str | None:
150
+ """Get or sets the DOI for the item.
151
+
152
+ This MUST NOT be a DOIs link. For all DOI names respective DOI links SHOULD be
153
+ added to the links section.
154
+ """
155
+ return self._get_property(DOI_PROP, str)
156
+
157
+ @doi.setter
158
+ def doi(self, v: str | None) -> None:
159
+ if DOI_PROP in self.properties:
160
+ if v == self.properties[DOI_PROP]:
161
+ return
162
+ remove_link(self.obj.links, self.properties[DOI_PROP])
163
+
164
+ if v is not None:
165
+ self.properties[DOI_PROP] = v
166
+ url = doi_to_url(v)
167
+ self.obj.add_link(pystac.Link(ScientificRelType.CITE_AS, url))
168
+
169
+ @property
170
+ def citation(self) -> str | None:
171
+ """Get or sets the recommended human-readable reference (citation) to be used by
172
+ publications citing the data.
173
+
174
+ No specific citation style is suggested, but the citation should contain all
175
+ information required to find the publication distinctively.
176
+ """
177
+ return self._get_property(CITATION_PROP, str)
178
+
179
+ @citation.setter
180
+ def citation(self, v: str | None) -> None:
181
+ self._set_property(CITATION_PROP, v)
182
+
183
+ @property
184
+ def publications(self) -> list[Publication] | None:
185
+ """Get or sets the list of relevant publications referencing and describing the
186
+ data."""
187
+ return map_opt(
188
+ lambda pubs: [Publication.from_dict(pub) for pub in pubs],
189
+ self._get_property(PUBLICATIONS_PROP, list[dict[str, Any]]),
190
+ )
191
+
192
+ @publications.setter
193
+ def publications(self, v: list[Publication] | None) -> None:
194
+ self._set_property(
195
+ PUBLICATIONS_PROP, map_opt(lambda pubs: [pub.to_dict() for pub in pubs], v)
196
+ )
197
+ if v is not None:
198
+ for pub in v:
199
+ pub_link = pub.get_link()
200
+ if pub_link is not None:
201
+ self.obj.add_link(pub_link)
202
+
203
+ # None for publication will clear all.
204
+ def remove_publication(self, publication: Publication | None = None) -> None:
205
+ """Removes the given :class:`Publication` from the extended
206
+ :class:`~pystac.Item`. If the ``publication`` argument is ``None``, all
207
+ publications will be removed from the :class:`~pystac.Item`."""
208
+ if PUBLICATIONS_PROP not in self.properties:
209
+ return
210
+
211
+ if not publication:
212
+ pubs = self.publications
213
+ if pubs is not None:
214
+ for one_pub in pubs:
215
+ remove_link(self.obj.links, one_pub.doi)
216
+
217
+ del self.properties[PUBLICATIONS_PROP]
218
+ return
219
+
220
+ # One publication and link to remove
221
+ remove_link(self.obj.links, publication.doi)
222
+ to_remove = publication.to_dict()
223
+ self.properties[PUBLICATIONS_PROP].remove(to_remove)
224
+
225
+ if not self.properties[PUBLICATIONS_PROP]:
226
+ del self.properties[PUBLICATIONS_PROP]
227
+
228
+ @classmethod
229
+ def get_schema_uri(cls) -> str:
230
+ return SCHEMA_URI
231
+
232
+ @classmethod
233
+ def ext(cls, obj: T, add_if_missing: bool = False) -> ScientificExtension[T]:
234
+ """Extends the given STAC Object with properties from the :stac-ext:`Scientific
235
+ Extension <scientific>`.
236
+
237
+ This extension can be applied to instances of :class:`~pystac.Item` or
238
+ :class:`~pystac.Collection`.
239
+
240
+ Raises:
241
+
242
+ pystac.ExtensionTypeError : If an invalid object type is passed.
243
+ """
244
+ if isinstance(obj, pystac.Collection):
245
+ cls.ensure_has_extension(obj, add_if_missing)
246
+ return cast(ScientificExtension[T], CollectionScientificExtension(obj))
247
+ if isinstance(obj, pystac.Item):
248
+ cls.ensure_has_extension(obj, add_if_missing)
249
+ return cast(ScientificExtension[T], ItemScientificExtension(obj))
250
+ else:
251
+ raise pystac.ExtensionTypeError(cls._ext_error_message(obj))
252
+
253
+ @classmethod
254
+ def summaries(
255
+ cls, obj: pystac.Collection, add_if_missing: bool = False
256
+ ) -> SummariesScientificExtension:
257
+ """Returns the extended summaries object for the given collection."""
258
+ cls.ensure_has_extension(obj, add_if_missing)
259
+ return SummariesScientificExtension(obj)
260
+
261
+
262
+ class CollectionScientificExtension(ScientificExtension[pystac.Collection]):
263
+ """A concrete implementation of :class:`ScientificExtension` on an
264
+ :class:`~pystac.Collection` that extends the properties of the Item to include
265
+ properties defined in the :stac-ext:`Scientific Citation Extension <scientific>`.
266
+
267
+ This class should generally not be instantiated directly. Instead, call
268
+ :meth:`ScientificExtension.ext` on an :class:`~pystac.Collection` to extend it.
269
+ """
270
+
271
+ collection: pystac.Collection
272
+ """The :class:`~pystac.Collection` being extended."""
273
+
274
+ properties: dict[str, Any]
275
+ """The :class:`~pystac.Collection` properties, including extension properties."""
276
+
277
+ links: list[pystac.Link]
278
+ """The list of :class:`~pystac.Link` objects associated with the
279
+ :class:`~pystac.Collection` being extended, including links added by this extension.
280
+ """
281
+
282
+ def __init__(self, collection: pystac.Collection):
283
+ self.collection = collection
284
+ self.properties = collection.extra_fields
285
+ self.links = collection.links
286
+ super().__init__(self.collection)
287
+
288
+ def __repr__(self) -> str:
289
+ return "<CollectionScientificExtension Collection id={}>".format(
290
+ self.collection.id
291
+ )
292
+
293
+
294
+ class ItemScientificExtension(ScientificExtension[pystac.Item]):
295
+ """A concrete implementation of :class:`ScientificExtension` on an
296
+ :class:`~pystac.Item` that extends the properties of the Item to include
297
+ properties defined in the :stac-ext:`Scientific Citation Extension
298
+ <scientific>`.
299
+
300
+ This class should generally not be instantiated directly. Instead, call
301
+ :meth:`ScientificExtension.ext` on an :class:`~pystac.Item` to extend it.
302
+ """
303
+
304
+ item: pystac.Item
305
+ """The :class:`~pystac.Item` being extended."""
306
+
307
+ properties: dict[str, Any]
308
+ """The :class:`~pystac.Item` properties, including extension properties."""
309
+
310
+ links: list[pystac.Link]
311
+ """The list of :class:`~pystac.Link` objects associated with the
312
+ :class:`~pystac.Item` being extended, including links added by this extension.
313
+ """
314
+
315
+ def __init__(self, item: pystac.Item):
316
+ self.item = item
317
+ self.properties = item.properties
318
+ self.links = item.links
319
+ super().__init__(self.item)
320
+
321
+ def __repr__(self) -> str:
322
+ return f"<ItemScientificExtension Item id={self.item.id}>"
323
+
324
+
325
+ class SummariesScientificExtension(SummariesExtension):
326
+ """A concrete implementation of :class:`~pystac.extensions.base.SummariesExtension`
327
+ that extends the ``summaries`` field of a :class:`~pystac.Collection` to include
328
+ properties defined in the :stac-ext:`Scientific Citation Extension <scientific>`.
329
+ """
330
+
331
+ @property
332
+ def citation(self) -> list[str] | None:
333
+ """Get or sets the summary of :attr:`ScientificExtension.citation` values
334
+ for this Collection.
335
+ """
336
+ return self.summaries.get_list(CITATION_PROP)
337
+
338
+ @citation.setter
339
+ def citation(self, v: list[str] | None) -> None:
340
+ self._set_summary(CITATION_PROP, v)
341
+
342
+ @property
343
+ def doi(self) -> list[str] | None:
344
+ """Get or sets the summary of :attr:`ScientificExtension.citation` values
345
+ for this Collection.
346
+ """
347
+ return self.summaries.get_list(DOI_PROP)
348
+
349
+ @doi.setter
350
+ def doi(self, v: list[str] | None) -> None:
351
+ self._set_summary(DOI_PROP, v)
352
+
353
+
354
+ class ScientificExtensionHooks(ExtensionHooks):
355
+ schema_uri: str = SCHEMA_URI
356
+ prev_extension_ids = {"scientific"}
357
+ stac_object_types = {pystac.STACObjectType.COLLECTION, pystac.STACObjectType.ITEM}
358
+
359
+
360
+ SCIENTIFIC_EXTENSION_HOOKS: ExtensionHooks = ScientificExtensionHooks()