pybliometrics 4.2__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.
Files changed (60) hide show
  1. pybliometrics/__init__.py +11 -0
  2. pybliometrics/exception.py +88 -0
  3. pybliometrics/sciencedirect/__init__.py +9 -0
  4. pybliometrics/sciencedirect/article_entitlement.py +95 -0
  5. pybliometrics/sciencedirect/article_metadata.py +171 -0
  6. pybliometrics/sciencedirect/article_retrieval.py +244 -0
  7. pybliometrics/sciencedirect/object_metadata.py +70 -0
  8. pybliometrics/sciencedirect/object_retrieval.py +54 -0
  9. pybliometrics/sciencedirect/sciencedirect_search.py +172 -0
  10. pybliometrics/sciencedirect/subject_classifications.py +47 -0
  11. pybliometrics/sciencedirect/tests/__init__.py +0 -0
  12. pybliometrics/sciencedirect/tests/test_ArticleEntitlement.py +30 -0
  13. pybliometrics/sciencedirect/tests/test_ArticleMetadata.py +132 -0
  14. pybliometrics/sciencedirect/tests/test_ArticleRetrieval.py +248 -0
  15. pybliometrics/sciencedirect/tests/test_ObjectMetadata.py +46 -0
  16. pybliometrics/sciencedirect/tests/test_ObjectRetrieval.py +32 -0
  17. pybliometrics/sciencedirect/tests/test_ScienceDirectSearch.py +79 -0
  18. pybliometrics/sciencedirect/tests/test_ScienceDirectSubjectClassifications.py +58 -0
  19. pybliometrics/scopus/__init__.py +13 -0
  20. pybliometrics/scopus/abstract_citation.py +311 -0
  21. pybliometrics/scopus/abstract_retrieval.py +975 -0
  22. pybliometrics/scopus/affiliation_retrieval.py +188 -0
  23. pybliometrics/scopus/affiliation_search.py +114 -0
  24. pybliometrics/scopus/author_retrieval.py +430 -0
  25. pybliometrics/scopus/author_search.py +125 -0
  26. pybliometrics/scopus/plumx_metrics.py +145 -0
  27. pybliometrics/scopus/scopus_search.py +243 -0
  28. pybliometrics/scopus/serial_search.py +180 -0
  29. pybliometrics/scopus/serial_title.py +313 -0
  30. pybliometrics/scopus/subject_classifications.py +99 -0
  31. pybliometrics/scopus/tests/__init__.py +0 -0
  32. pybliometrics/scopus/tests/test_AbstractRetrieval.py +549 -0
  33. pybliometrics/scopus/tests/test_AffiliationRetrieval.py +150 -0
  34. pybliometrics/scopus/tests/test_AffiliationSearch.py +33 -0
  35. pybliometrics/scopus/tests/test_AuthorRetrieval.py +313 -0
  36. pybliometrics/scopus/tests/test_AuthorSearch.py +35 -0
  37. pybliometrics/scopus/tests/test_CitationOverview.py +156 -0
  38. pybliometrics/scopus/tests/test_PlumXMetrics.py +99 -0
  39. pybliometrics/scopus/tests/test_ScopusSearch.py +105 -0
  40. pybliometrics/scopus/tests/test_SerialSearch.py +42 -0
  41. pybliometrics/scopus/tests/test_SerialTitle.py +188 -0
  42. pybliometrics/scopus/tests/test_SubjectClassifications.py +57 -0
  43. pybliometrics/superclasses/__init__.py +3 -0
  44. pybliometrics/superclasses/base.py +213 -0
  45. pybliometrics/superclasses/retrieval.py +51 -0
  46. pybliometrics/superclasses/search.py +68 -0
  47. pybliometrics/utils/__init__.py +6 -0
  48. pybliometrics/utils/checks.py +6 -0
  49. pybliometrics/utils/constants.py +120 -0
  50. pybliometrics/utils/create_config.py +76 -0
  51. pybliometrics/utils/get_content.py +196 -0
  52. pybliometrics/utils/parse_content.py +219 -0
  53. pybliometrics/utils/startup.py +159 -0
  54. pybliometrics/utils/tests/__init__.py +0 -0
  55. pybliometrics/utils/tests/test_startup.py +68 -0
  56. pybliometrics-4.2.dist-info/LICENSE +27 -0
  57. pybliometrics-4.2.dist-info/METADATA +144 -0
  58. pybliometrics-4.2.dist-info/RECORD +60 -0
  59. pybliometrics-4.2.dist-info/WHEEL +5 -0
  60. pybliometrics-4.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,11 @@
1
+ from importlib.metadata import version
2
+
3
+ __version__ = version("pybliometrics")
4
+
5
+ __citation__ = 'Rose, Michael E. and John R. Kitchin: "pybliometrics: '\
6
+ 'Scriptable bibliometrics using a Python interface to Scopus", SoftwareX '\
7
+ '10 (2019) 100263.'
8
+
9
+ import pybliometrics.scopus
10
+ import pybliometrics.sciencedirect
11
+ from pybliometrics.utils.startup import init
@@ -0,0 +1,88 @@
1
+ """Base exceptions and classes for pybliometrics.scopus."""
2
+
3
+ import warnings
4
+
5
+ warnings.filterwarnings("default", category=DeprecationWarning)
6
+
7
+
8
+ def deprecated(cls):
9
+ original_init = cls.__init__
10
+ def __init__(self, *args, **kwargs):
11
+ msg = f"{cls.__name__} is deprecated and will be removed in the next "\
12
+ "release. Please use ScopusServerError instead"
13
+ warnings.warn(msg, DeprecationWarning, stacklevel=2)
14
+ original_init(self, *args, **kwargs)
15
+ cls.__init__ = __init__
16
+ return cls
17
+
18
+
19
+ # Base classes
20
+ class ScopusException(Exception):
21
+ """Base class for exceptions in pybliometrics."""
22
+
23
+
24
+ class ScopusError(ScopusException):
25
+ """Exception for a serious error in pybliometrics."""
26
+
27
+
28
+ # Query errors
29
+ class ScopusQueryError(ScopusException):
30
+ """Exception for problems related to Scopus queries."""
31
+
32
+
33
+ # HTML errors
34
+ class ScopusHtmlError(ScopusException):
35
+ """Wrapper for exceptions raised by requests."""
36
+
37
+
38
+ class Scopus400Error(ScopusHtmlError):
39
+ """Raised if a query yields a 400 error (Bad Request for url)."""
40
+
41
+
42
+ class Scopus401Error(ScopusHtmlError):
43
+ """Raised if a query yields a 401 error (Unauthorized for url)."""
44
+
45
+
46
+ class Scopus403Error(ScopusHtmlError):
47
+ """Raised if a query yields a 403 error (Forbidden for url)."""
48
+
49
+
50
+ class Scopus404Error(ScopusHtmlError):
51
+ """Raised if a query yields a 404 error (Not Found for url)."""
52
+
53
+
54
+ class Scopus407Error(ScopusHtmlError):
55
+ """Raised if a query yields a 407 error (Proxy Authentication Required)."""
56
+
57
+
58
+ class Scopus413Error(ScopusHtmlError):
59
+ """Raised if a query yields a 413 error (Request Entity Too
60
+ Large for url).
61
+ """
62
+
63
+
64
+ class Scopus414Error(ScopusHtmlError):
65
+ """Raised if a query yields a 414 error (Request-URI Too Large for url)."""
66
+
67
+
68
+ class Scopus429Error(ScopusHtmlError):
69
+ """Raised if a query yields a 429 error (Quota exceeded)."""
70
+
71
+
72
+ class ScopusServerError(ScopusHtmlError):
73
+ """Wrapper for Server related exceptions (code 5xx)."""
74
+
75
+
76
+ @deprecated
77
+ class Scopus500Error(ScopusServerError):
78
+ """Raised if a query yields a 500 error (Internal Server Error
79
+ for url).
80
+ """
81
+
82
+ @deprecated
83
+ class Scopus502Error(ScopusServerError):
84
+ """Raised if a query yields a 502 error (Bad gateway for url)."""
85
+
86
+ @deprecated
87
+ class Scopus504Error(ScopusServerError):
88
+ """Raised if a query yields a 504 error (Gateway Time-out for url)."""
@@ -0,0 +1,9 @@
1
+ from pybliometrics.utils import *
2
+
3
+ from pybliometrics.sciencedirect.article_entitlement import *
4
+ from pybliometrics.sciencedirect.article_metadata import *
5
+ from pybliometrics.sciencedirect.article_retrieval import *
6
+ from pybliometrics.sciencedirect.object_metadata import *
7
+ from pybliometrics.sciencedirect.object_retrieval import *
8
+ from pybliometrics.sciencedirect.sciencedirect_search import *
9
+ from pybliometrics.sciencedirect.subject_classifications import *
@@ -0,0 +1,95 @@
1
+ """Module for retrieving article entitlement information from ScienceDirect."""
2
+
3
+ from typing import Optional, Union
4
+
5
+ from pybliometrics.superclasses import Retrieval
6
+ from pybliometrics.utils import chained_get, check_parameter_value, detect_id_type, VIEWS
7
+
8
+
9
+ class ArticleEntitlement(Retrieval):
10
+ """Class to retrieve the entitlement status for a document from ScienceDirect."""
11
+ @property
12
+ def status(self) -> Optional[str]:
13
+ """Status of whether a document has been found"""
14
+ return self._json.get("@status")
15
+
16
+ @property
17
+ def identifier(self) -> Optional[str]:
18
+ """Identifier of a document."""
19
+ return self._json.get("dc:identifier")
20
+
21
+ @property
22
+ def eid(self) -> Optional[str]:
23
+ "The EID of a document."
24
+ return self._json.get("eid")
25
+
26
+ @property
27
+ def entitled(self) -> Optional[str]:
28
+ """Entitlement status of a document."""
29
+ return self._json.get("entitled")
30
+
31
+ @property
32
+ def link(self) -> Optional[str]:
33
+ """ScienceDirect canonical URL."""
34
+ return chained_get(self._json, ['link', '@href'])
35
+
36
+ @property
37
+ def message(self) -> Optional[str]:
38
+ """Entitlement status message."""
39
+ return self._json.get("message")
40
+
41
+ @property
42
+ def pii(self) -> Optional[str]:
43
+ """The PII of a document."""
44
+ return self._json.get("pii")
45
+
46
+ @property
47
+ def pii_norm(self) -> Optional[str]:
48
+ """The PII-norm of a document."""
49
+ return self._json.get("pii-norm")
50
+
51
+ @property
52
+ def doi(self) -> Optional[str]:
53
+ """The DOI of a document."""
54
+ return self._json.get("prism:doi")
55
+
56
+ @property
57
+ def pubmed_id(self) -> Optional[str]:
58
+ """The Pubmed ID of a document (when used in the request)."""
59
+ return self._json.get("pubmed_id")
60
+
61
+ @property
62
+ def url(self) -> Optional[str]:
63
+ """API URL used to check entitlement."""
64
+ return self._json.get("prism:url")
65
+
66
+ @property
67
+ def scopus_id(self) -> Optional[str]:
68
+ """The Scopus ID of a document (when used in the request)."""
69
+ return self._json.get("scopus_id")
70
+
71
+ def __init__(self,
72
+ identifier: Union[int, str],
73
+ view: str = "FULL",
74
+ id_type: Optional[str] = None,
75
+ refresh: Union[bool, int] = False,
76
+ **kwds: str) -> None:
77
+ # Checks
78
+ identifier = str(identifier)
79
+ check_parameter_value(view, VIEWS["ArticleEntitlement"], "view")
80
+ if not id_type:
81
+ id_type = detect_id_type(identifier)
82
+ else:
83
+ allowed_id_types = ("eid", "pii", "scopus_id", "pubmed_id", "doi", "pui")
84
+ check_parameter_value(id_type, allowed_id_types, "id_type")
85
+
86
+ self._view = view
87
+ self._refresh = refresh
88
+ # Retrieve and get content
89
+ Retrieval.__init__(self, identifier=identifier, id_type=id_type, **kwds)
90
+ self._json = chained_get(self._json, ["entitlement-response", "document-entitlement"])
91
+
92
+ def __str__(self) -> str:
93
+ s = self.message
94
+ s += f' with doi: {self.doi}'
95
+ return s
@@ -0,0 +1,171 @@
1
+ from collections import namedtuple
2
+ from typing import Optional, Union
3
+
4
+ from pybliometrics.superclasses import Search
5
+ from pybliometrics.utils import check_field_consistency, chained_get, \
6
+ check_integrity, check_parameter_value, deduplicate, \
7
+ make_search_summary, VIEWS
8
+
9
+
10
+ class ArticleMetadata(Search):
11
+ @property
12
+ def results(self) -> Optional[list[namedtuple]]:
13
+ """A list of namedtuples in the form `(authorKeywords authors available_online_date
14
+ first_author abstract_text doi title eid link openArchiveArticle openaccess_status
15
+ openaccessArticle openaccessUserLicense pii aggregationType copyright coverDate
16
+ coverDisplayDate edition endingPage isbn publicationName startingPage teaser
17
+ api_link publicationType vor_available_online_date)`.
18
+
19
+ Field definitions correspond to the `Article Metadata Views
20
+ <https://dev.elsevier.com/sd_article_meta_views.html>`__ and return the
21
+ values as-is, except for `authors` which are joined on `";"`.
22
+
23
+ Raises
24
+ ------
25
+ ValueError
26
+ If the elements provided in `integrity_fields` do not match the
27
+ actual field names (listed above).
28
+
29
+ Notes
30
+ -----
31
+ The list of authors and the list of affiliations per author are
32
+ deduplicated.
33
+ """
34
+ fields = 'authorKeywords authors available_online_date first_author abstract_text ' \
35
+ 'doi title eid link openArchiveArticle openaccess_status openaccessArticle '\
36
+ 'openaccessUserLicense pii aggregationType copyright coverDate coverDisplayDate '\
37
+ 'edition endingPage isbn publicationName startingPage teaser api_link publicationType '\
38
+ 'vor_available_online_date'
39
+ doc = namedtuple('Document', fields)
40
+ check_field_consistency(self._integrity, fields)
41
+ # Parse elements one-by-one
42
+ out = []
43
+ for item in self._json:
44
+ # Get authors and create ";" separated string
45
+ authors_list = [author.get('$') for author in chained_get(item, ['authors', 'author'], [])]
46
+ authors_list = deduplicate(authors_list)
47
+ authors = ';'.join(authors_list)
48
+ first_author = item.get('dc:creator')[0].get('$')
49
+ link = item.get('link')[0].get('@href')
50
+ doi = item.get("prism:doi") or item.get("dc:identifier")[4:] if item.get("dc:identifier") else None
51
+ new = doc(
52
+ authorKeywords=item.get('authkeywords'),
53
+ authors=authors,
54
+ available_online_date=item.get('available-online-date'),
55
+ first_author=first_author,
56
+ abstract_text=item.get('dc:description'),
57
+ doi=doi,
58
+ title=item.get('dc:title'),
59
+ eid=item.get('eid'),
60
+ link=link,
61
+ openArchiveArticle=item.get('openArchiveArticle'),
62
+ openaccess_status=item.get('openaccess'),
63
+ openaccessArticle=item.get('openaccessArticle'),
64
+ openaccessUserLicense=item.get('openaccessUserLicense'),
65
+ pii=item.get('pii'),
66
+ aggregationType=item.get('prism:aggregationType'),
67
+ copyright=item.get('prism:copyright'),
68
+ coverDate=item.get('prism:coverDate'),
69
+ coverDisplayDate=item.get('prism:coverDisplayDate'),
70
+ edition=item.get('prism:edition'),
71
+ endingPage=item.get('prism:endingPage'),
72
+ isbn=item.get('prism:isbn'),
73
+ publicationName=item.get('prism:publicationName'),
74
+ startingPage=item.get('prism:startingPage'),
75
+ teaser=item.get('prism:teaser'),
76
+ api_link=item.get('prism:url'),
77
+ publicationType=item.get('pubType'),
78
+ vor_available_online_date=item.get('vor-available-online-date'),
79
+ )
80
+ out.append(new)
81
+ check_integrity(out, self._integrity, self._action)
82
+ return out or None
83
+
84
+ def __init__(self,
85
+ query: str,
86
+ refresh: Union[bool, int] = False,
87
+ view: str = None,
88
+ verbose: bool = False,
89
+ download: bool = True,
90
+ integrity_fields: Union[list[str], tuple[str, ...]] = None,
91
+ integrity_action: str = "raise",
92
+ subscriber: bool = True,
93
+ **kwds: str
94
+ ) -> None:
95
+ """Interaction with the ScienceDirect Article Metadata API.
96
+
97
+ :param query: A string of the query as used in the `Advanced Search <https://dev.elsevier.com/tecdoc_sdsearch_migration.html>`__.
98
+ :param refresh: Whether to refresh the cached file if it exists or not.
99
+ If int is passed, cached file will be refreshed if the
100
+ number of days since last modification exceeds that value.
101
+ :param view: Which view to use for the query, see `the documentation <https://dev.elsevier.com/sd_article_meta_views.html>`__.
102
+ Allowed values: `STANDARD`, `COMPLETE`. If `None`, defaults to
103
+ `COMPLETE` if `subscriber=True` and to `STANDARD` if
104
+ `subscriber=False`.
105
+ :param verbose: Whether to print a download progress bar.
106
+ :param download: Whether to download results (if they have not been
107
+ cached).
108
+ :param integrity_fields: A list or tuple with the names of fields whose completeness should
109
+ be checked. `ArticleMetadata` will perform the
110
+ action specified in `integrity_action` if
111
+ elements in these fields are missing. This
112
+ helps to avoid idiosynchratically missing
113
+ elements that should always be present
114
+ (e.g., EID or source ID).
115
+ :param integrity_action: What to do in case integrity of provided fields
116
+ cannot be verified. Possible actions:
117
+ - `"raise"`: Raise an `AttributeError`
118
+ - `"warn"`: Raise a `UserWarning`
119
+ :param subscriber: Whether you access ScienceDirect with a subscription or not.
120
+ For subscribers, ScienceDirect's cursor navigation will be
121
+ used. Sets the number of entries in each query
122
+ iteration to the maximum number allowed by the
123
+ corresponding view.
124
+ :param unescape: Convert named and numeric characters in the `results` to
125
+ their corresponding Unicode characters.
126
+ :param kwds: Keywords passed on as query parameters. Must contain
127
+ fields and values mentioned in the `API specification <https://dev.elsevier.com/documentation/ArticleMetadataAPI.wadl>`__.
128
+
129
+ Raises
130
+ ------
131
+ ScopusQueryError
132
+ For non-subscribers, if the number of search results exceeds 5000.
133
+
134
+ ValueError
135
+ If any of the parameters `integrity_action`, `refresh` or `view`
136
+ is not one of the allowed values.
137
+
138
+ Notes
139
+ -----
140
+ The directory for cached results is `{path}/{view}/{fname}`,
141
+ where `path` is specified in your configuration file and `fname` is
142
+ the md5-hashed version of `query`.
143
+ """
144
+ # Check view or set to default
145
+ if view:
146
+ check_parameter_value(view, VIEWS['ArticleMetadata'], "view")
147
+ else:
148
+ view = "COMPLETE" if subscriber else "STANDARD"
149
+
150
+ allowed = ("warn", "raise")
151
+ check_parameter_value(integrity_action, allowed, "integrity_action")
152
+
153
+ size = 25 # for pagination
154
+ if view == "STANDARD" and subscriber:
155
+ size = 200
156
+
157
+ # Query
158
+ self._action = integrity_action
159
+ self._integrity = integrity_fields or []
160
+ self._refresh = refresh
161
+ self._query = query
162
+ self._view = view
163
+ Search.__init__(self, query=query, size=size, download=download, verbose=verbose, **kwds)
164
+
165
+ def __str__(self):
166
+ """Print a summary string."""
167
+ return make_search_summary(self, "document", self.get_eids())
168
+
169
+ def get_eids(self):
170
+ """EIDs of retrieved documents."""
171
+ return [d['eid'] for d in self._json]
@@ -0,0 +1,244 @@
1
+ from collections import namedtuple
2
+ from typing import Optional, Union
3
+
4
+ from pybliometrics.superclasses import Retrieval
5
+ from pybliometrics.utils import (
6
+ chained_get,
7
+ check_parameter_value,
8
+ detect_id_type,
9
+ list_authors,
10
+ make_bool_if_possible,
11
+ make_int_if_possible,
12
+ parse_pages,
13
+ VIEWS,
14
+ )
15
+
16
+
17
+ class ArticleRetrieval(Retrieval):
18
+ @property
19
+ def abstract(self) -> Optional[str]:
20
+ """The abstract of a document."""
21
+ abstract = chained_get(self._json, ['coredata', 'dc:description'])
22
+ if abstract:
23
+ abstract = abstract.strip()
24
+ return abstract
25
+
26
+ @property
27
+ def aggregationType(self) -> Optional[str]:
28
+ """The aggregation type of a document."""
29
+ return chained_get(self._json, ['coredata', 'prism:aggregationType'])
30
+
31
+ @property
32
+ def authors(self) -> Optional[list]:
33
+ """The authors of a document."""
34
+ out = []
35
+ auth = namedtuple('Author', 'surname given_name')
36
+ for author in chained_get(self._json, ['coredata', 'dc:creator']):
37
+ surname, given_name = author['$'].split(', ')
38
+ new = auth(surname=surname, given_name=given_name)
39
+ out.append(new)
40
+ return out or None
41
+
42
+ @property
43
+ def copyright(self) -> Optional[str]:
44
+ """The copyright of a document."""
45
+ return chained_get(self._json, ['coredata', 'prism:copyright'])
46
+
47
+ @property
48
+ def coverDate(self) -> Optional[str]:
49
+ """The date of the cover the document is in."""
50
+ return chained_get(self._json, ['coredata', 'prism:coverDate'])
51
+
52
+ @property
53
+ def coverDisplayDate(self) -> Optional[str]:
54
+ """The cover display date of a document."""
55
+ return chained_get(self._json, ['coredata', 'prism:coverDisplayDate'])
56
+
57
+ @property
58
+ def document_entitlement_status(self) -> Optional[str]:
59
+ """Returns the document entitlement status, i.e. tells if the requestor
60
+ is entitled to the requested resource.
61
+ Note: Only works with `ENTITLED` view.
62
+ """
63
+ return chained_get(self._json, ['document-entitlement', 'status'])
64
+
65
+ @property
66
+ def doi(self) -> str:
67
+ """The doi of a document."""
68
+ return chained_get(self._json, ['coredata', 'prism:doi'])
69
+
70
+ @property
71
+ def eid(self) -> Optional[str]:
72
+ """The eid of a document."""
73
+ return chained_get(self._json, ['coredata', 'eid'])
74
+
75
+ @property
76
+ def endingPage(self) -> Optional[str]:
77
+ """The ending page of a document."""
78
+ page = chained_get(self._json, ['coredata', 'prism:endingPage'])
79
+ return page
80
+
81
+ @property
82
+ def issn(self) -> int:
83
+ """The issn of a document."""
84
+ return make_int_if_possible(chained_get(self._json, ['coredata', 'prism:issn']))
85
+
86
+ @property
87
+ def openaccess(self) -> bool:
88
+ """The document is open access."""
89
+ open_access = chained_get(self._json, ['coredata', 'openaccessArticle'])
90
+ return make_bool_if_possible(open_access)
91
+
92
+ @property
93
+ def openaccessSponsorName(self) -> Optional[str]:
94
+ """The open access sponsor name of a document."""
95
+ return chained_get(self._json, ['coredata', 'openaccessSponsorName'])
96
+
97
+ @property
98
+ def openaccessSponsorType(self) -> Optional[str]:
99
+ """The open access sponsor type of a document."""
100
+ return chained_get(self._json, ['coredata', 'openaccessSponsorType'])
101
+
102
+ @property
103
+ def openaccessType(self) -> Optional[str]:
104
+ """The open access type of a document."""
105
+ return chained_get(self._json, ['coredata', 'openaccessType'])
106
+
107
+ @property
108
+ def openaccessUserLicense(self) -> Optional[str]:
109
+ """The open access user license of a document."""
110
+ return chained_get(self._json, ['coredata', 'openaccessUserLicense'])
111
+
112
+ @property
113
+ def openArchiveArticle(self) -> bool:
114
+ """The document is an open archive article."""
115
+ open_archive = chained_get(self._json, ['coredata', 'openArchiveArticle'])
116
+ return make_bool_if_possible(open_archive)
117
+
118
+ @property
119
+ def originalText(self) -> Optional[str]:
120
+ """Complete document text."""
121
+ return self._json.get('originalText')
122
+
123
+ @property
124
+ def pageRange(self) -> Optional[str]:
125
+ """The prism:pageRange of a document."""
126
+ return chained_get(self._json, ['coredata', 'prism:pageRange'])
127
+
128
+ @property
129
+ def publicationName(self) -> str:
130
+ """The publication name of a document (e.g. Journal of Economy and Technology)."""
131
+ return chained_get(self._json, ['coredata', 'prism:publicationName'])
132
+
133
+ @property
134
+ def publisher(self) -> Optional[str]:
135
+ """The publisher of a document."""
136
+ return chained_get(self._json, ['coredata', 'prism:publisher'])
137
+
138
+ @property
139
+ def pubType(self) -> Optional[str]:
140
+ """The publication type of a document."""
141
+ return chained_get(self._json, ['coredata', 'pubType'])
142
+
143
+ @property
144
+ def pii(self) -> str:
145
+ """The pii of a document."""
146
+ return chained_get(self._json, ['coredata', 'pii'])
147
+
148
+ @property
149
+ def sciencedirect_link(self) -> str:
150
+ """The ScienceDirect link of a document."""
151
+ links = chained_get(self._json, ['coredata', 'link'])
152
+ for link in links:
153
+ if link['@rel'] == 'scidir':
154
+ return link['@href']
155
+
156
+ @property
157
+ def self_link(self) -> str:
158
+ """The API link of a document."""
159
+ links = chained_get(self._json, ['coredata', 'link'])
160
+ for link in links:
161
+ if link['@rel'] == 'self':
162
+ return link['@href']
163
+
164
+ @property
165
+ def startingPage(self) -> Optional[str]:
166
+ """The starting page of a document."""
167
+ return chained_get(self._json, ['coredata', 'prism:startingPage'])
168
+
169
+ @property
170
+ def subjects(self) -> Optional[str]:
171
+ """The subjects of a document."""
172
+ subjects = chained_get(self._json, ['coredata', 'dcterms:subject'])
173
+ return [subject['$'] for subject in subjects]
174
+
175
+ @property
176
+ def title(self) -> str:
177
+ """The title of a document."""
178
+ return chained_get(self._json, ['coredata', 'dc:title'])
179
+
180
+ @property
181
+ def url(self) -> str:
182
+ """The url of a document."""
183
+ return chained_get(self._json, ['coredata', 'prism:url'])
184
+
185
+ @property
186
+ def volume(self) -> Optional[str]:
187
+ """The prism:volume of a document."""
188
+ vol = chained_get(self._json, ['coredata', 'prism:volume'])
189
+ return make_int_if_possible(vol)
190
+
191
+ def __init__(self,
192
+ identifier: Union[int, str],
193
+ refresh: Union[bool, int] = False,
194
+ view: str = 'META',
195
+ id_type: Optional[str] = None,
196
+ **kwds: str
197
+ ):
198
+ """Interaction with the Article Retrieval API.
199
+
200
+ :param identifier: The indentifier of an article.
201
+ :param refresh: Whether to refresh the cached file if it exists or not.
202
+ If int is passed, cached file will be refreshed if the
203
+ number of days since last modification exceeds that value.
204
+ :param view: The view of the file that should be downloaded. Allowed values:
205
+ 'META', 'META_ABS', 'META_ABS_REF', 'FULL', 'ENTITLED'. Default: 'META'.
206
+ :param id_type: The type of used ID. Allowed values: `None`, 'eid', 'pii',
207
+ 'scopus_id', 'pubmed_id' and 'doi'. If the value is `None`,
208
+ pybliometrics tries to infer the ID type itself.
209
+ """
210
+ identifier = str(identifier)
211
+ check_parameter_value(view, VIEWS['ArticleRetrieval'], "view")
212
+ if id_type is None:
213
+ id_type = detect_id_type(identifier)
214
+ else:
215
+ allowed_id_types = ('eid', 'pii', 'scopus_id', 'pubmed_id', 'doi')
216
+ check_parameter_value(id_type, allowed_id_types, "id_type")
217
+
218
+ self._view = view
219
+ self._refresh = refresh
220
+
221
+ Retrieval.__init__(self, identifier=identifier, id_type=id_type, **kwds)
222
+ if self._view != "ENTITLED":
223
+ self._json = self._json['full-text-retrieval-response']
224
+
225
+ def __str__(self):
226
+ s = ''
227
+ if self._view in ('FULL', 'META_ABS', 'META'):
228
+ if self.authors:
229
+ if len(self.authors) > 1:
230
+ authors = list_authors(self.authors)
231
+ else:
232
+ a = self.authors[0]
233
+ authors = str(a.given_name) + ' ' + str(a.surname)
234
+ else:
235
+ authors = "(No author found)"
236
+ # All other information
237
+ s += f'{authors}: "{self.title}", {self.publicationName}'
238
+ s += f', {self.volume}' if self.volume else ''
239
+ s += ', '
240
+ s += parse_pages(self)
241
+ s += f'({self.coverDate[:4]}).'
242
+ if self.doi:
243
+ s += f' https://doi.org/{self.doi}.'
244
+ return s