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.
- pybliometrics/__init__.py +11 -0
- pybliometrics/exception.py +88 -0
- pybliometrics/sciencedirect/__init__.py +9 -0
- pybliometrics/sciencedirect/article_entitlement.py +95 -0
- pybliometrics/sciencedirect/article_metadata.py +171 -0
- pybliometrics/sciencedirect/article_retrieval.py +244 -0
- pybliometrics/sciencedirect/object_metadata.py +70 -0
- pybliometrics/sciencedirect/object_retrieval.py +54 -0
- pybliometrics/sciencedirect/sciencedirect_search.py +172 -0
- pybliometrics/sciencedirect/subject_classifications.py +47 -0
- pybliometrics/sciencedirect/tests/__init__.py +0 -0
- pybliometrics/sciencedirect/tests/test_ArticleEntitlement.py +30 -0
- pybliometrics/sciencedirect/tests/test_ArticleMetadata.py +132 -0
- pybliometrics/sciencedirect/tests/test_ArticleRetrieval.py +248 -0
- pybliometrics/sciencedirect/tests/test_ObjectMetadata.py +46 -0
- pybliometrics/sciencedirect/tests/test_ObjectRetrieval.py +32 -0
- pybliometrics/sciencedirect/tests/test_ScienceDirectSearch.py +79 -0
- pybliometrics/sciencedirect/tests/test_ScienceDirectSubjectClassifications.py +58 -0
- pybliometrics/scopus/__init__.py +13 -0
- pybliometrics/scopus/abstract_citation.py +311 -0
- pybliometrics/scopus/abstract_retrieval.py +975 -0
- pybliometrics/scopus/affiliation_retrieval.py +188 -0
- pybliometrics/scopus/affiliation_search.py +114 -0
- pybliometrics/scopus/author_retrieval.py +430 -0
- pybliometrics/scopus/author_search.py +125 -0
- pybliometrics/scopus/plumx_metrics.py +145 -0
- pybliometrics/scopus/scopus_search.py +243 -0
- pybliometrics/scopus/serial_search.py +180 -0
- pybliometrics/scopus/serial_title.py +313 -0
- pybliometrics/scopus/subject_classifications.py +99 -0
- pybliometrics/scopus/tests/__init__.py +0 -0
- pybliometrics/scopus/tests/test_AbstractRetrieval.py +549 -0
- pybliometrics/scopus/tests/test_AffiliationRetrieval.py +150 -0
- pybliometrics/scopus/tests/test_AffiliationSearch.py +33 -0
- pybliometrics/scopus/tests/test_AuthorRetrieval.py +313 -0
- pybliometrics/scopus/tests/test_AuthorSearch.py +35 -0
- pybliometrics/scopus/tests/test_CitationOverview.py +156 -0
- pybliometrics/scopus/tests/test_PlumXMetrics.py +99 -0
- pybliometrics/scopus/tests/test_ScopusSearch.py +105 -0
- pybliometrics/scopus/tests/test_SerialSearch.py +42 -0
- pybliometrics/scopus/tests/test_SerialTitle.py +188 -0
- pybliometrics/scopus/tests/test_SubjectClassifications.py +57 -0
- pybliometrics/superclasses/__init__.py +3 -0
- pybliometrics/superclasses/base.py +213 -0
- pybliometrics/superclasses/retrieval.py +51 -0
- pybliometrics/superclasses/search.py +68 -0
- pybliometrics/utils/__init__.py +6 -0
- pybliometrics/utils/checks.py +6 -0
- pybliometrics/utils/constants.py +120 -0
- pybliometrics/utils/create_config.py +76 -0
- pybliometrics/utils/get_content.py +196 -0
- pybliometrics/utils/parse_content.py +219 -0
- pybliometrics/utils/startup.py +159 -0
- pybliometrics/utils/tests/__init__.py +0 -0
- pybliometrics/utils/tests/test_startup.py +68 -0
- pybliometrics-4.2.dist-info/LICENSE +27 -0
- pybliometrics-4.2.dist-info/METADATA +144 -0
- pybliometrics-4.2.dist-info/RECORD +60 -0
- pybliometrics-4.2.dist-info/WHEEL +5 -0
- 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
|