pyalex 0.15.1__py3-none-any.whl → 0.17__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.
- pyalex/__init__.py +2 -0
- pyalex/_version.py +9 -4
- pyalex/api.py +651 -84
- {pyalex-0.15.1.dist-info → pyalex-0.17.dist-info}/METADATA +22 -11
- pyalex-0.17.dist-info/RECORD +8 -0
- {pyalex-0.15.1.dist-info → pyalex-0.17.dist-info}/WHEEL +1 -1
- pyalex-0.15.1.dist-info/RECORD +0 -8
- {pyalex-0.15.1.dist-info → pyalex-0.17.dist-info}/LICENSE +0 -0
- {pyalex-0.15.1.dist-info → pyalex-0.17.dist-info}/top_level.txt +0 -0
pyalex/__init__.py
CHANGED
|
@@ -18,6 +18,7 @@ from pyalex.api import Funders
|
|
|
18
18
|
from pyalex.api import Institution
|
|
19
19
|
from pyalex.api import Institutions
|
|
20
20
|
from pyalex.api import Journals
|
|
21
|
+
from pyalex.api import OpenAlexResponseList
|
|
21
22
|
from pyalex.api import People
|
|
22
23
|
from pyalex.api import Publisher
|
|
23
24
|
from pyalex.api import Publishers
|
|
@@ -61,4 +62,5 @@ __all__ = [
|
|
|
61
62
|
"autocomplete",
|
|
62
63
|
"config",
|
|
63
64
|
"invert_abstract",
|
|
65
|
+
"OpenAlexResponseList",
|
|
64
66
|
]
|
pyalex/_version.py
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
# file generated by
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
3
6
|
TYPE_CHECKING = False
|
|
4
7
|
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
6
11
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
12
|
else:
|
|
8
13
|
VERSION_TUPLE = object
|
|
@@ -12,5 +17,5 @@ __version__: str
|
|
|
12
17
|
__version_tuple__: VERSION_TUPLE
|
|
13
18
|
version_tuple: VERSION_TUPLE
|
|
14
19
|
|
|
15
|
-
__version__ = version = '0.
|
|
16
|
-
__version_tuple__ = version_tuple = (0,
|
|
20
|
+
__version__ = version = '0.17'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 17)
|
pyalex/api.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import warnings
|
|
3
3
|
from urllib.parse import quote_plus
|
|
4
|
+
from urllib.parse import urlunparse
|
|
4
5
|
|
|
5
6
|
import requests
|
|
6
7
|
from requests.auth import AuthBase
|
|
@@ -13,6 +14,26 @@ except ImportError:
|
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class AlexConfig(dict):
|
|
17
|
+
"""Configuration class for OpenAlex API.
|
|
18
|
+
|
|
19
|
+
Attributes
|
|
20
|
+
----------
|
|
21
|
+
email : str
|
|
22
|
+
Email address for API requests.
|
|
23
|
+
api_key : str
|
|
24
|
+
API key for authentication.
|
|
25
|
+
user_agent : str
|
|
26
|
+
User agent string for API requests.
|
|
27
|
+
openalex_url : str
|
|
28
|
+
Base URL for OpenAlex API.
|
|
29
|
+
max_retries : int
|
|
30
|
+
Maximum number of retries for API requests.
|
|
31
|
+
retry_backoff_factor : float
|
|
32
|
+
Backoff factor for retries.
|
|
33
|
+
retry_http_codes : list
|
|
34
|
+
List of HTTP status codes to retry on.
|
|
35
|
+
"""
|
|
36
|
+
|
|
16
37
|
def __getattr__(self, key):
|
|
17
38
|
return super().__getitem__(key)
|
|
18
39
|
|
|
@@ -31,40 +52,129 @@ config = AlexConfig(
|
|
|
31
52
|
)
|
|
32
53
|
|
|
33
54
|
|
|
55
|
+
class or_(dict):
|
|
56
|
+
"""Logical OR expression class."""
|
|
57
|
+
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class _LogicalExpression:
|
|
62
|
+
"""Base class for logical expressions.
|
|
63
|
+
|
|
64
|
+
Attributes
|
|
65
|
+
----------
|
|
66
|
+
token : str
|
|
67
|
+
Token representing the logical operation.
|
|
68
|
+
value : any
|
|
69
|
+
Value to be used in the logical expression.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
token = None
|
|
73
|
+
|
|
74
|
+
def __init__(self, value):
|
|
75
|
+
self.value = value
|
|
76
|
+
|
|
77
|
+
def __str__(self) -> str:
|
|
78
|
+
return f"{self.token}{self.value}"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class not_(_LogicalExpression):
|
|
82
|
+
"""Logical NOT expression class."""
|
|
83
|
+
|
|
84
|
+
token = "!"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class gt_(_LogicalExpression):
|
|
88
|
+
"""Logical greater than expression class."""
|
|
89
|
+
|
|
90
|
+
token = ">"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class lt_(_LogicalExpression):
|
|
94
|
+
"""Logical less than expression class."""
|
|
95
|
+
|
|
96
|
+
token = "<"
|
|
97
|
+
|
|
98
|
+
|
|
34
99
|
def _quote_oa_value(v):
|
|
35
100
|
"""Prepare a value for the OpenAlex API.
|
|
36
101
|
|
|
37
102
|
Applies URL encoding to strings and converts booleans to lowercase strings.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
v : any
|
|
107
|
+
Value to be prepared.
|
|
108
|
+
|
|
109
|
+
Returns
|
|
110
|
+
-------
|
|
111
|
+
any
|
|
112
|
+
Prepared value.
|
|
38
113
|
"""
|
|
39
114
|
|
|
40
115
|
# workaround for bug https://groups.google.com/u/1/g/openalex-users/c/t46RWnzZaXc
|
|
41
116
|
if isinstance(v, bool):
|
|
42
117
|
return str(v).lower()
|
|
43
118
|
|
|
119
|
+
if isinstance(v, _LogicalExpression) and isinstance(v.value, str):
|
|
120
|
+
v.value = quote_plus(v.value)
|
|
121
|
+
return v
|
|
122
|
+
|
|
44
123
|
if isinstance(v, str):
|
|
45
124
|
return quote_plus(v)
|
|
46
125
|
|
|
47
126
|
return v
|
|
48
127
|
|
|
49
128
|
|
|
50
|
-
def _flatten_kv(d, prefix=""):
|
|
129
|
+
def _flatten_kv(d, prefix=None, logical="+"):
|
|
130
|
+
"""Flatten a dictionary into a key-value string for the OpenAlex API.
|
|
131
|
+
|
|
132
|
+
Parameters
|
|
133
|
+
----------
|
|
134
|
+
d : dict
|
|
135
|
+
Dictionary to be flattened.
|
|
136
|
+
prefix : str, optional
|
|
137
|
+
Prefix for the keys.
|
|
138
|
+
logical : str, optional
|
|
139
|
+
Logical operator to join values.
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
str
|
|
144
|
+
Flattened key-value string.
|
|
145
|
+
"""
|
|
146
|
+
if prefix is None and not isinstance(d, dict):
|
|
147
|
+
raise ValueError("prefix should be set if d is not a dict")
|
|
148
|
+
|
|
51
149
|
if isinstance(d, dict):
|
|
150
|
+
logical_subd = "|" if isinstance(d, or_) else logical
|
|
151
|
+
|
|
52
152
|
t = []
|
|
53
153
|
for k, v in d.items():
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
x = _flatten_kv(v, prefix=new_prefix)
|
|
59
|
-
t.append(x)
|
|
154
|
+
x = _flatten_kv(
|
|
155
|
+
v, prefix=f"{prefix}.{k}" if prefix else f"{k}", logical=logical_subd
|
|
156
|
+
)
|
|
157
|
+
t.append(x)
|
|
60
158
|
|
|
61
159
|
return ",".join(t)
|
|
160
|
+
elif isinstance(d, list):
|
|
161
|
+
list_str = logical.join([f"{_quote_oa_value(i)}" for i in d])
|
|
162
|
+
return f"{prefix}:{list_str}"
|
|
62
163
|
else:
|
|
63
164
|
return f"{prefix}:{_quote_oa_value(d)}"
|
|
64
165
|
|
|
65
166
|
|
|
66
167
|
def _params_merge(params, add_params):
|
|
67
|
-
|
|
168
|
+
"""Merge additional parameters into existing parameters.
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
params : dict
|
|
173
|
+
Existing parameters.
|
|
174
|
+
add_params : dict
|
|
175
|
+
Additional parameters to be merged.
|
|
176
|
+
"""
|
|
177
|
+
for k in add_params.keys():
|
|
68
178
|
if (
|
|
69
179
|
k in params
|
|
70
180
|
and isinstance(params[k], dict)
|
|
@@ -92,6 +202,13 @@ def _params_merge(params, add_params):
|
|
|
92
202
|
|
|
93
203
|
|
|
94
204
|
def _get_requests_session():
|
|
205
|
+
"""Create a Requests session with automatic retry.
|
|
206
|
+
|
|
207
|
+
Returns
|
|
208
|
+
-------
|
|
209
|
+
requests.Session
|
|
210
|
+
Requests session with retry configuration.
|
|
211
|
+
"""
|
|
95
212
|
# create an Requests Session with automatic retry:
|
|
96
213
|
requests_session = requests.Session()
|
|
97
214
|
retries = Retry(
|
|
@@ -108,20 +225,108 @@ def _get_requests_session():
|
|
|
108
225
|
|
|
109
226
|
|
|
110
227
|
def invert_abstract(inv_index):
|
|
228
|
+
"""Invert OpenAlex abstract index.
|
|
229
|
+
|
|
230
|
+
Parameters
|
|
231
|
+
----------
|
|
232
|
+
inv_index : dict
|
|
233
|
+
Inverted index of the abstract.
|
|
234
|
+
|
|
235
|
+
Returns
|
|
236
|
+
-------
|
|
237
|
+
str
|
|
238
|
+
Inverted abstract.
|
|
239
|
+
"""
|
|
111
240
|
if inv_index is not None:
|
|
112
241
|
l_inv = [(w, p) for w, pos in inv_index.items() for p in pos]
|
|
113
242
|
return " ".join(map(lambda x: x[0], sorted(l_inv, key=lambda x: x[1])))
|
|
114
243
|
|
|
115
244
|
|
|
245
|
+
def _wrap_values_nested_dict(d, func):
|
|
246
|
+
"""Apply a function to all values in a nested dictionary.
|
|
247
|
+
|
|
248
|
+
Parameters
|
|
249
|
+
----------
|
|
250
|
+
d : dict
|
|
251
|
+
Nested dictionary.
|
|
252
|
+
func : function
|
|
253
|
+
Function to apply to the values.
|
|
254
|
+
|
|
255
|
+
Returns
|
|
256
|
+
-------
|
|
257
|
+
dict
|
|
258
|
+
Dictionary with the function applied to the values.
|
|
259
|
+
"""
|
|
260
|
+
for k, v in d.items():
|
|
261
|
+
if isinstance(v, dict):
|
|
262
|
+
d[k] = _wrap_values_nested_dict(v, func)
|
|
263
|
+
elif isinstance(v, list):
|
|
264
|
+
d[k] = [func(i) for i in v]
|
|
265
|
+
else:
|
|
266
|
+
d[k] = func(v)
|
|
267
|
+
|
|
268
|
+
return d
|
|
269
|
+
|
|
270
|
+
|
|
116
271
|
class QueryError(ValueError):
|
|
272
|
+
"""Exception raised for errors in the query."""
|
|
273
|
+
|
|
117
274
|
pass
|
|
118
275
|
|
|
119
276
|
|
|
120
277
|
class OpenAlexEntity(dict):
|
|
278
|
+
"""Base class for OpenAlex entities."""
|
|
279
|
+
|
|
121
280
|
pass
|
|
122
281
|
|
|
123
282
|
|
|
283
|
+
class OpenAlexResponseList(list):
|
|
284
|
+
"""A list of OpenAlexEntity objects with metadata.
|
|
285
|
+
|
|
286
|
+
Attributes:
|
|
287
|
+
meta: a dictionary with metadata about the results
|
|
288
|
+
resource_class: the class to use for each entity in the results
|
|
289
|
+
|
|
290
|
+
Arguments:
|
|
291
|
+
results: a list of OpenAlexEntity objects
|
|
292
|
+
meta: a dictionary with metadata about the results
|
|
293
|
+
resource_class: the class to use for each entity in the results
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
a OpenAlexResponseList object
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
def __init__(self, results, meta=None, resource_class=OpenAlexEntity):
|
|
300
|
+
self.resource_class = resource_class
|
|
301
|
+
self.meta = meta
|
|
302
|
+
|
|
303
|
+
super().__init__([resource_class(ent) for ent in results])
|
|
304
|
+
|
|
305
|
+
|
|
124
306
|
class Paginator:
|
|
307
|
+
"""Paginator for OpenAlex API results.
|
|
308
|
+
|
|
309
|
+
Attributes
|
|
310
|
+
----------
|
|
311
|
+
VALUE_CURSOR_START : str
|
|
312
|
+
Starting value for cursor pagination.
|
|
313
|
+
VALUE_NUMBER_START : int
|
|
314
|
+
Starting value for page pagination.
|
|
315
|
+
|
|
316
|
+
Parameters
|
|
317
|
+
----------
|
|
318
|
+
endpoint_class : class
|
|
319
|
+
Class of the endpoint to paginate.
|
|
320
|
+
method : str, optional
|
|
321
|
+
Pagination method ('cursor' or 'page').
|
|
322
|
+
value : any, optional
|
|
323
|
+
Starting value for pagination.
|
|
324
|
+
per_page : int, optional
|
|
325
|
+
Number of results per page.
|
|
326
|
+
n_max : int, optional
|
|
327
|
+
Maximum number of results.
|
|
328
|
+
"""
|
|
329
|
+
|
|
125
330
|
VALUE_CURSOR_START = "*"
|
|
126
331
|
VALUE_NUMBER_START = 1
|
|
127
332
|
|
|
@@ -157,32 +362,31 @@ class Paginator:
|
|
|
157
362
|
else:
|
|
158
363
|
raise ValueError()
|
|
159
364
|
|
|
160
|
-
|
|
161
|
-
return_meta=True, per_page=self.per_page, **pagination_params
|
|
162
|
-
)
|
|
365
|
+
r = self.endpoint_class.get(per_page=self.per_page, **pagination_params)
|
|
163
366
|
|
|
164
367
|
if self.method == "cursor":
|
|
165
|
-
self._next_value = meta["next_cursor"]
|
|
368
|
+
self._next_value = r.meta["next_cursor"]
|
|
166
369
|
|
|
167
370
|
if self.method == "page":
|
|
168
|
-
if len(
|
|
169
|
-
self._next_value = meta["page"] + 1
|
|
371
|
+
if len(r) > 0:
|
|
372
|
+
self._next_value = r.meta["page"] + 1
|
|
170
373
|
else:
|
|
171
374
|
self._next_value = None
|
|
172
375
|
|
|
173
|
-
self.n = self.n + len(
|
|
376
|
+
self.n = self.n + len(r)
|
|
174
377
|
|
|
175
|
-
return
|
|
378
|
+
return r
|
|
176
379
|
|
|
177
380
|
|
|
178
381
|
class OpenAlexAuth(AuthBase):
|
|
179
|
-
"""OpenAlex auth class based on requests auth
|
|
180
|
-
|
|
181
|
-
Includes the email, api_key and user-agent headers.
|
|
382
|
+
"""OpenAlex auth class based on requests auth.
|
|
182
383
|
|
|
183
|
-
|
|
184
|
-
config: an AlexConfig object
|
|
384
|
+
Includes the email, api_key, and user-agent headers.
|
|
185
385
|
|
|
386
|
+
Parameters
|
|
387
|
+
----------
|
|
388
|
+
config : AlexConfig
|
|
389
|
+
Configuration object for OpenAlex API.
|
|
186
390
|
"""
|
|
187
391
|
|
|
188
392
|
def __init__(self, config):
|
|
@@ -202,74 +406,102 @@ class OpenAlexAuth(AuthBase):
|
|
|
202
406
|
|
|
203
407
|
|
|
204
408
|
class BaseOpenAlex:
|
|
205
|
-
"""Base class for OpenAlex objects.
|
|
409
|
+
"""Base class for OpenAlex objects.
|
|
410
|
+
|
|
411
|
+
Parameters
|
|
412
|
+
----------
|
|
413
|
+
params : dict, optional
|
|
414
|
+
Parameters for the API request.
|
|
415
|
+
"""
|
|
206
416
|
|
|
207
417
|
def __init__(self, params=None):
|
|
208
418
|
self.params = params
|
|
209
419
|
|
|
210
|
-
def _get_multi_items(self, record_list):
|
|
211
|
-
return self.filter(openalex_id="|".join(record_list)).get()
|
|
212
|
-
|
|
213
|
-
def _full_collection_name(self):
|
|
214
|
-
if self.params is not None and "q" in self.params.keys():
|
|
215
|
-
return (
|
|
216
|
-
f"{config.openalex_url}/autocomplete/{self.__class__.__name__.lower()}"
|
|
217
|
-
)
|
|
218
|
-
else:
|
|
219
|
-
return f"{config.openalex_url}/{self.__class__.__name__.lower()}"
|
|
220
|
-
|
|
221
420
|
def __getattr__(self, key):
|
|
222
421
|
if key == "groupby":
|
|
223
422
|
raise AttributeError(
|
|
224
|
-
"Object has no attribute 'groupby'.
|
|
423
|
+
"Object has no attribute 'groupby'. Did you mean 'group_by'?"
|
|
225
424
|
)
|
|
226
425
|
|
|
227
426
|
if key == "filter_search":
|
|
228
427
|
raise AttributeError(
|
|
229
|
-
"Object has no attribute 'filter_search'. "
|
|
230
|
-
"Did you mean 'search_filter'?"
|
|
428
|
+
"Object has no attribute 'filter_search'. Did you mean 'search_filter'?"
|
|
231
429
|
)
|
|
232
430
|
|
|
233
431
|
return getattr(self, key)
|
|
234
432
|
|
|
235
433
|
def __getitem__(self, record_id):
|
|
236
434
|
if isinstance(record_id, list):
|
|
237
|
-
|
|
435
|
+
if len(record_id) > 100:
|
|
436
|
+
raise ValueError("OpenAlex does not support more than 100 ids")
|
|
238
437
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
438
|
+
return self.filter_or(openalex_id=record_id).get(per_page=len(record_id))
|
|
439
|
+
elif isinstance(record_id, str):
|
|
440
|
+
self.params = record_id
|
|
441
|
+
return self._get_from_url(self.url)
|
|
442
|
+
else:
|
|
443
|
+
raise ValueError("record_id should be a string or a list of strings")
|
|
444
|
+
|
|
445
|
+
def _url_query(self):
|
|
446
|
+
if isinstance(self.params, list):
|
|
447
|
+
return self.filter_or(openalex_id=self.params)
|
|
448
|
+
elif isinstance(self.params, dict):
|
|
449
|
+
l_params = []
|
|
450
|
+
for k, v in self.params.items():
|
|
451
|
+
if v is None:
|
|
452
|
+
pass
|
|
453
|
+
elif isinstance(v, list):
|
|
454
|
+
l_params.append(
|
|
455
|
+
"{}={}".format(k, ",".join(map(_quote_oa_value, v)))
|
|
456
|
+
)
|
|
457
|
+
elif k in ["filter", "sort"]:
|
|
458
|
+
l_params.append(f"{k}={_flatten_kv(v)}")
|
|
459
|
+
else:
|
|
460
|
+
l_params.append(f"{k}={_quote_oa_value(v)}")
|
|
461
|
+
|
|
462
|
+
if l_params:
|
|
463
|
+
return "&".join(l_params)
|
|
464
|
+
|
|
465
|
+
else:
|
|
466
|
+
return ""
|
|
242
467
|
|
|
243
468
|
@property
|
|
244
469
|
def url(self):
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
for k, v in self.params.items():
|
|
250
|
-
if v is None:
|
|
251
|
-
pass
|
|
252
|
-
elif isinstance(v, list):
|
|
253
|
-
l_params.append("{}={}".format(k, ",".join(map(_quote_oa_value, v))))
|
|
254
|
-
elif k in ["filter", "sort"]:
|
|
255
|
-
l_params.append(f"{k}={_flatten_kv(v)}")
|
|
256
|
-
else:
|
|
257
|
-
l_params.append(f"{k}={_quote_oa_value(v)}")
|
|
470
|
+
"""Return the URL for the API request.
|
|
471
|
+
|
|
472
|
+
The URL doens't include the identification, authentication,
|
|
473
|
+
and pagination parameters.
|
|
258
474
|
|
|
259
|
-
if l_params:
|
|
260
|
-
return "{}?{}".format(self._full_collection_name(), "&".join(l_params))
|
|
261
475
|
|
|
262
|
-
|
|
476
|
+
Returns
|
|
477
|
+
-------
|
|
478
|
+
str
|
|
479
|
+
URL for the API request.
|
|
480
|
+
"""
|
|
481
|
+
base_path = self.__class__.__name__.lower()
|
|
482
|
+
|
|
483
|
+
if isinstance(self.params, str):
|
|
484
|
+
path = f"{base_path}/{_quote_oa_value(self.params)}"
|
|
485
|
+
query = ""
|
|
486
|
+
else:
|
|
487
|
+
path = base_path
|
|
488
|
+
query = self._url_query()
|
|
489
|
+
|
|
490
|
+
return urlunparse(("https", "api.openalex.org", path, "", query, ""))
|
|
263
491
|
|
|
264
492
|
def count(self):
|
|
265
|
-
|
|
493
|
+
"""Get the count of results.
|
|
266
494
|
|
|
267
|
-
|
|
495
|
+
Returns
|
|
496
|
+
-------
|
|
497
|
+
int
|
|
498
|
+
Count of results.
|
|
499
|
+
"""
|
|
500
|
+
return self.get(per_page=1).meta["count"]
|
|
268
501
|
|
|
269
|
-
def _get_from_url(self, url
|
|
502
|
+
def _get_from_url(self, url):
|
|
270
503
|
res = _get_requests_session().get(url, auth=OpenAlexAuth(config))
|
|
271
504
|
|
|
272
|
-
# handle query errors
|
|
273
505
|
if res.status_code == 403:
|
|
274
506
|
if (
|
|
275
507
|
isinstance(res.json()["error"], str)
|
|
@@ -280,32 +512,61 @@ class BaseOpenAlex:
|
|
|
280
512
|
res.raise_for_status()
|
|
281
513
|
res_json = res.json()
|
|
282
514
|
|
|
283
|
-
# group-by or results page
|
|
284
515
|
if self.params and "group-by" in self.params:
|
|
285
|
-
|
|
516
|
+
return OpenAlexResponseList(
|
|
517
|
+
res_json["group_by"], res_json["meta"], self.resource_class
|
|
518
|
+
)
|
|
286
519
|
elif "results" in res_json:
|
|
287
|
-
|
|
520
|
+
return OpenAlexResponseList(
|
|
521
|
+
res_json["results"], res_json["meta"], self.resource_class
|
|
522
|
+
)
|
|
288
523
|
elif "id" in res_json:
|
|
289
|
-
|
|
524
|
+
return self.resource_class(res_json)
|
|
290
525
|
else:
|
|
291
526
|
raise ValueError("Unknown response format")
|
|
292
527
|
|
|
293
|
-
# return result and metadata
|
|
294
|
-
if return_meta:
|
|
295
|
-
return results, res_json["meta"]
|
|
296
|
-
else:
|
|
297
|
-
return results
|
|
298
|
-
|
|
299
528
|
def get(self, return_meta=False, page=None, per_page=None, cursor=None):
|
|
300
529
|
if per_page is not None and (per_page < 1 or per_page > 200):
|
|
301
530
|
raise ValueError("per_page should be a number between 1 and 200.")
|
|
302
531
|
|
|
303
|
-
self.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
532
|
+
if not isinstance(self.params, (str, list)):
|
|
533
|
+
self._add_params("per-page", per_page)
|
|
534
|
+
self._add_params("page", page)
|
|
535
|
+
self._add_params("cursor", cursor)
|
|
536
|
+
|
|
537
|
+
resp_list = self._get_from_url(self.url)
|
|
538
|
+
|
|
539
|
+
if return_meta:
|
|
540
|
+
warnings.warn(
|
|
541
|
+
"return_meta is deprecated, call .meta on the result",
|
|
542
|
+
DeprecationWarning,
|
|
543
|
+
stacklevel=2,
|
|
544
|
+
)
|
|
545
|
+
return resp_list, resp_list.meta
|
|
546
|
+
else:
|
|
547
|
+
return resp_list
|
|
307
548
|
|
|
308
549
|
def paginate(self, method="cursor", page=1, per_page=None, cursor="*", n_max=10000):
|
|
550
|
+
"""Paginate results from the API.
|
|
551
|
+
|
|
552
|
+
Parameters
|
|
553
|
+
----------
|
|
554
|
+
method : str, optional
|
|
555
|
+
Pagination method ('cursor' or 'page').
|
|
556
|
+
page : int, optional
|
|
557
|
+
Page number for pagination.
|
|
558
|
+
per_page : int, optional
|
|
559
|
+
Number of results per page.
|
|
560
|
+
cursor : str, optional
|
|
561
|
+
Cursor for pagination.
|
|
562
|
+
n_max : int, optional
|
|
563
|
+
Maximum number of results.
|
|
564
|
+
|
|
565
|
+
Returns
|
|
566
|
+
-------
|
|
567
|
+
Paginator
|
|
568
|
+
Paginator object.
|
|
569
|
+
"""
|
|
309
570
|
if method == "cursor":
|
|
310
571
|
if self.params.get("sample"):
|
|
311
572
|
raise ValueError("method should be 'page' when using sample")
|
|
@@ -320,9 +581,30 @@ class BaseOpenAlex:
|
|
|
320
581
|
)
|
|
321
582
|
|
|
322
583
|
def random(self):
|
|
584
|
+
"""Get a random result.
|
|
585
|
+
|
|
586
|
+
Returns
|
|
587
|
+
-------
|
|
588
|
+
OpenAlexEntity
|
|
589
|
+
Random result.
|
|
590
|
+
"""
|
|
323
591
|
return self.__getitem__("random")
|
|
324
592
|
|
|
325
|
-
def _add_params(self, argument, new_params):
|
|
593
|
+
def _add_params(self, argument, new_params, raise_if_exists=False):
|
|
594
|
+
"""Add parameters to the API request.
|
|
595
|
+
|
|
596
|
+
Parameters
|
|
597
|
+
----------
|
|
598
|
+
argument : str
|
|
599
|
+
Parameter name.
|
|
600
|
+
new_params : any
|
|
601
|
+
Parameter value.
|
|
602
|
+
raise_if_exists : bool, optional
|
|
603
|
+
Whether to raise an error if the parameter already exists.
|
|
604
|
+
"""
|
|
605
|
+
if raise_if_exists:
|
|
606
|
+
raise NotImplementedError("raise_if_exists is not implemented")
|
|
607
|
+
|
|
326
608
|
if self.params is None:
|
|
327
609
|
self.params = {argument: new_params}
|
|
328
610
|
elif argument in self.params and isinstance(self.params[argument], dict):
|
|
@@ -333,44 +615,247 @@ class BaseOpenAlex:
|
|
|
333
615
|
logging.debug("Params updated:", self.params)
|
|
334
616
|
|
|
335
617
|
def filter(self, **kwargs):
|
|
618
|
+
"""Add filter parameters to the API request.
|
|
619
|
+
|
|
620
|
+
Parameters
|
|
621
|
+
----------
|
|
622
|
+
**kwargs : dict
|
|
623
|
+
Filter parameters.
|
|
624
|
+
|
|
625
|
+
Returns
|
|
626
|
+
-------
|
|
627
|
+
BaseOpenAlex
|
|
628
|
+
Updated object.
|
|
629
|
+
"""
|
|
336
630
|
self._add_params("filter", kwargs)
|
|
337
631
|
return self
|
|
338
632
|
|
|
633
|
+
def filter_and(self, **kwargs):
|
|
634
|
+
"""Add AND filter parameters to the API request.
|
|
635
|
+
|
|
636
|
+
Parameters
|
|
637
|
+
----------
|
|
638
|
+
**kwargs : dict
|
|
639
|
+
Filter parameters.
|
|
640
|
+
|
|
641
|
+
Returns
|
|
642
|
+
-------
|
|
643
|
+
BaseOpenAlex
|
|
644
|
+
Updated object.
|
|
645
|
+
"""
|
|
646
|
+
return self.filter(**kwargs)
|
|
647
|
+
|
|
648
|
+
def filter_or(self, **kwargs):
|
|
649
|
+
"""Add OR filter parameters to the API request.
|
|
650
|
+
|
|
651
|
+
Parameters
|
|
652
|
+
----------
|
|
653
|
+
**kwargs : dict
|
|
654
|
+
Filter parameters.
|
|
655
|
+
|
|
656
|
+
Returns
|
|
657
|
+
-------
|
|
658
|
+
BaseOpenAlex
|
|
659
|
+
Updated object.
|
|
660
|
+
"""
|
|
661
|
+
self._add_params("filter", or_(kwargs), raise_if_exists=False)
|
|
662
|
+
return self
|
|
663
|
+
|
|
664
|
+
def filter_not(self, **kwargs):
|
|
665
|
+
"""Add NOT filter parameters to the API request.
|
|
666
|
+
|
|
667
|
+
Parameters
|
|
668
|
+
----------
|
|
669
|
+
**kwargs : dict
|
|
670
|
+
Filter parameters.
|
|
671
|
+
|
|
672
|
+
Returns
|
|
673
|
+
-------
|
|
674
|
+
BaseOpenAlex
|
|
675
|
+
Updated object.
|
|
676
|
+
"""
|
|
677
|
+
self._add_params("filter", _wrap_values_nested_dict(kwargs, not_))
|
|
678
|
+
return self
|
|
679
|
+
|
|
680
|
+
def filter_gt(self, **kwargs):
|
|
681
|
+
"""Add greater than filter parameters to the API request.
|
|
682
|
+
|
|
683
|
+
Parameters
|
|
684
|
+
----------
|
|
685
|
+
**kwargs : dict
|
|
686
|
+
Filter parameters.
|
|
687
|
+
|
|
688
|
+
Returns
|
|
689
|
+
-------
|
|
690
|
+
BaseOpenAlex
|
|
691
|
+
Updated object.
|
|
692
|
+
"""
|
|
693
|
+
self._add_params("filter", _wrap_values_nested_dict(kwargs, gt_))
|
|
694
|
+
return self
|
|
695
|
+
|
|
696
|
+
def filter_lt(self, **kwargs):
|
|
697
|
+
"""Add less than filter parameters to the API request.
|
|
698
|
+
|
|
699
|
+
Parameters
|
|
700
|
+
----------
|
|
701
|
+
**kwargs : dict
|
|
702
|
+
Filter parameters.
|
|
703
|
+
|
|
704
|
+
Returns
|
|
705
|
+
-------
|
|
706
|
+
BaseOpenAlex
|
|
707
|
+
Updated object.
|
|
708
|
+
"""
|
|
709
|
+
self._add_params("filter", _wrap_values_nested_dict(kwargs, lt_))
|
|
710
|
+
return self
|
|
711
|
+
|
|
339
712
|
def search_filter(self, **kwargs):
|
|
713
|
+
"""Add search filter parameters to the API request.
|
|
714
|
+
|
|
715
|
+
Parameters
|
|
716
|
+
----------
|
|
717
|
+
**kwargs : dict
|
|
718
|
+
Filter parameters.
|
|
719
|
+
|
|
720
|
+
Returns
|
|
721
|
+
-------
|
|
722
|
+
BaseOpenAlex
|
|
723
|
+
Updated object.
|
|
724
|
+
"""
|
|
340
725
|
self._add_params("filter", {f"{k}.search": v for k, v in kwargs.items()})
|
|
341
726
|
return self
|
|
342
727
|
|
|
343
728
|
def sort(self, **kwargs):
|
|
729
|
+
"""Add sort parameters to the API request.
|
|
730
|
+
|
|
731
|
+
Parameters
|
|
732
|
+
----------
|
|
733
|
+
**kwargs : dict
|
|
734
|
+
Sort parameters.
|
|
735
|
+
|
|
736
|
+
Returns
|
|
737
|
+
-------
|
|
738
|
+
BaseOpenAlex
|
|
739
|
+
Updated object.
|
|
740
|
+
"""
|
|
344
741
|
self._add_params("sort", kwargs)
|
|
345
742
|
return self
|
|
346
743
|
|
|
347
744
|
def group_by(self, group_key):
|
|
745
|
+
"""Add group-by parameters to the API request.
|
|
746
|
+
|
|
747
|
+
Parameters
|
|
748
|
+
----------
|
|
749
|
+
group_key : str
|
|
750
|
+
Group-by key.
|
|
751
|
+
|
|
752
|
+
Returns
|
|
753
|
+
-------
|
|
754
|
+
BaseOpenAlex
|
|
755
|
+
Updated object.
|
|
756
|
+
"""
|
|
348
757
|
self._add_params("group-by", group_key)
|
|
349
758
|
return self
|
|
350
759
|
|
|
351
760
|
def search(self, s):
|
|
761
|
+
"""Add search parameters to the API request.
|
|
762
|
+
|
|
763
|
+
Parameters
|
|
764
|
+
----------
|
|
765
|
+
s : str
|
|
766
|
+
Search string.
|
|
767
|
+
|
|
768
|
+
Returns
|
|
769
|
+
-------
|
|
770
|
+
BaseOpenAlex
|
|
771
|
+
Updated object.
|
|
772
|
+
"""
|
|
352
773
|
self._add_params("search", s)
|
|
353
774
|
return self
|
|
354
775
|
|
|
355
776
|
def sample(self, n, seed=None):
|
|
777
|
+
"""Add sample parameters to the API request.
|
|
778
|
+
|
|
779
|
+
Parameters
|
|
780
|
+
----------
|
|
781
|
+
n : int
|
|
782
|
+
Number of samples.
|
|
783
|
+
seed : int, optional
|
|
784
|
+
Seed for sampling.
|
|
785
|
+
|
|
786
|
+
Returns
|
|
787
|
+
-------
|
|
788
|
+
BaseOpenAlex
|
|
789
|
+
Updated object.
|
|
790
|
+
"""
|
|
356
791
|
self._add_params("sample", n)
|
|
357
792
|
self._add_params("seed", seed)
|
|
358
793
|
return self
|
|
359
794
|
|
|
360
795
|
def select(self, s):
|
|
796
|
+
"""Add select parameters to the API request.
|
|
797
|
+
|
|
798
|
+
Parameters
|
|
799
|
+
----------
|
|
800
|
+
s : str
|
|
801
|
+
Select string.
|
|
802
|
+
|
|
803
|
+
Returns
|
|
804
|
+
-------
|
|
805
|
+
BaseOpenAlex
|
|
806
|
+
Updated object.
|
|
807
|
+
"""
|
|
361
808
|
self._add_params("select", s)
|
|
362
809
|
return self
|
|
363
810
|
|
|
364
|
-
def autocomplete(self, s,
|
|
365
|
-
"""
|
|
811
|
+
def autocomplete(self, s, return_meta=False):
|
|
812
|
+
"""Return the OpenAlex autocomplete results.
|
|
813
|
+
|
|
814
|
+
Parameters
|
|
815
|
+
----------
|
|
816
|
+
s : str
|
|
817
|
+
String to autocomplete.
|
|
818
|
+
return_meta : bool, optional
|
|
819
|
+
Whether to return metadata.
|
|
820
|
+
|
|
821
|
+
Returns
|
|
822
|
+
-------
|
|
823
|
+
OpenAlexResponseList
|
|
824
|
+
List of autocomplete results.
|
|
825
|
+
"""
|
|
826
|
+
|
|
366
827
|
self._add_params("q", s)
|
|
367
|
-
|
|
828
|
+
|
|
829
|
+
resp_list = self._get_from_url(
|
|
830
|
+
urlunparse(
|
|
831
|
+
(
|
|
832
|
+
"https",
|
|
833
|
+
"api.openalex.org",
|
|
834
|
+
f"autocomplete/{self.__class__.__name__.lower()}",
|
|
835
|
+
"",
|
|
836
|
+
self._url_query(),
|
|
837
|
+
"",
|
|
838
|
+
)
|
|
839
|
+
)
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
if return_meta:
|
|
843
|
+
warnings.warn(
|
|
844
|
+
"return_meta is deprecated, call .meta on the result",
|
|
845
|
+
DeprecationWarning,
|
|
846
|
+
stacklevel=2,
|
|
847
|
+
)
|
|
848
|
+
return resp_list, resp_list.meta
|
|
849
|
+
else:
|
|
850
|
+
return resp_list
|
|
368
851
|
|
|
369
852
|
|
|
370
853
|
# The API
|
|
371
854
|
|
|
372
855
|
|
|
373
856
|
class Work(OpenAlexEntity):
|
|
857
|
+
"""Class representing a work entity in OpenAlex."""
|
|
858
|
+
|
|
374
859
|
def __getitem__(self, key):
|
|
375
860
|
if key == "abstract":
|
|
376
861
|
return invert_abstract(self["abstract_inverted_index"])
|
|
@@ -378,6 +863,18 @@ class Work(OpenAlexEntity):
|
|
|
378
863
|
return super().__getitem__(key)
|
|
379
864
|
|
|
380
865
|
def ngrams(self, return_meta=False):
|
|
866
|
+
"""Get n-grams for the work.
|
|
867
|
+
|
|
868
|
+
Parameters
|
|
869
|
+
----------
|
|
870
|
+
return_meta : bool, optional
|
|
871
|
+
Whether to return metadata.
|
|
872
|
+
|
|
873
|
+
Returns
|
|
874
|
+
-------
|
|
875
|
+
OpenAlexResponseList
|
|
876
|
+
List of n-grams.
|
|
877
|
+
"""
|
|
381
878
|
openalex_id = self["id"].split("/")[-1]
|
|
382
879
|
n_gram_url = f"{config.openalex_url}/works/{openalex_id}/ngrams"
|
|
383
880
|
|
|
@@ -385,105 +882,162 @@ class Work(OpenAlexEntity):
|
|
|
385
882
|
res.raise_for_status()
|
|
386
883
|
results = res.json()
|
|
387
884
|
|
|
388
|
-
|
|
885
|
+
resp_list = OpenAlexResponseList(results["ngrams"], results["meta"])
|
|
886
|
+
|
|
389
887
|
if return_meta:
|
|
390
|
-
|
|
888
|
+
warnings.warn(
|
|
889
|
+
"return_meta is deprecated, call .meta on the result",
|
|
890
|
+
DeprecationWarning,
|
|
891
|
+
stacklevel=2,
|
|
892
|
+
)
|
|
893
|
+
return resp_list, resp_list.meta
|
|
391
894
|
else:
|
|
392
|
-
return
|
|
895
|
+
return resp_list
|
|
393
896
|
|
|
394
897
|
|
|
395
898
|
class Works(BaseOpenAlex):
|
|
899
|
+
"""Class representing a collection of work entities in OpenAlex."""
|
|
900
|
+
|
|
396
901
|
resource_class = Work
|
|
397
902
|
|
|
398
903
|
|
|
399
904
|
class Author(OpenAlexEntity):
|
|
905
|
+
"""Class representing an author entity in OpenAlex."""
|
|
906
|
+
|
|
400
907
|
pass
|
|
401
908
|
|
|
402
909
|
|
|
403
910
|
class Authors(BaseOpenAlex):
|
|
911
|
+
"""Class representing a collection of author entities in OpenAlex."""
|
|
912
|
+
|
|
404
913
|
resource_class = Author
|
|
405
914
|
|
|
406
915
|
|
|
407
916
|
class Source(OpenAlexEntity):
|
|
917
|
+
"""Class representing a source entity in OpenAlex."""
|
|
918
|
+
|
|
408
919
|
pass
|
|
409
920
|
|
|
410
921
|
|
|
411
922
|
class Sources(BaseOpenAlex):
|
|
923
|
+
"""Class representing a collection of source entities in OpenAlex."""
|
|
924
|
+
|
|
412
925
|
resource_class = Source
|
|
413
926
|
|
|
414
927
|
|
|
415
928
|
class Institution(OpenAlexEntity):
|
|
929
|
+
"""Class representing an institution entity in OpenAlex."""
|
|
930
|
+
|
|
416
931
|
pass
|
|
417
932
|
|
|
418
933
|
|
|
419
934
|
class Institutions(BaseOpenAlex):
|
|
935
|
+
"""Class representing a collection of institution entities in OpenAlex."""
|
|
936
|
+
|
|
420
937
|
resource_class = Institution
|
|
421
938
|
|
|
422
939
|
|
|
423
940
|
class Domain(OpenAlexEntity):
|
|
941
|
+
"""Class representing a domain entity in OpenAlex."""
|
|
942
|
+
|
|
424
943
|
pass
|
|
425
944
|
|
|
426
945
|
|
|
427
946
|
class Domains(BaseOpenAlex):
|
|
947
|
+
"""Class representing a collection of domain entities in OpenAlex."""
|
|
948
|
+
|
|
428
949
|
resource_class = Domain
|
|
429
950
|
|
|
430
951
|
|
|
431
952
|
class Field(OpenAlexEntity):
|
|
953
|
+
"""Class representing a field entity in OpenAlex."""
|
|
954
|
+
|
|
432
955
|
pass
|
|
433
956
|
|
|
434
957
|
|
|
435
958
|
class Fields(BaseOpenAlex):
|
|
959
|
+
"""Class representing a collection of field entities in OpenAlex."""
|
|
960
|
+
|
|
436
961
|
resource_class = Field
|
|
437
962
|
|
|
438
963
|
|
|
439
964
|
class Subfield(OpenAlexEntity):
|
|
965
|
+
"""Class representing a subfield entity in OpenAlex."""
|
|
966
|
+
|
|
440
967
|
pass
|
|
441
968
|
|
|
442
969
|
|
|
443
970
|
class Subfields(BaseOpenAlex):
|
|
971
|
+
"""Class representing a collection of subfield entities in OpenAlex."""
|
|
972
|
+
|
|
444
973
|
resource_class = Subfield
|
|
445
974
|
|
|
446
975
|
|
|
447
976
|
class Topic(OpenAlexEntity):
|
|
977
|
+
"""Class representing a topic entity in OpenAlex."""
|
|
978
|
+
|
|
448
979
|
pass
|
|
449
980
|
|
|
450
981
|
|
|
451
982
|
class Topics(BaseOpenAlex):
|
|
983
|
+
"""Class representing a collection of topic entities in OpenAlex."""
|
|
984
|
+
|
|
452
985
|
resource_class = Topic
|
|
453
986
|
|
|
454
987
|
|
|
455
988
|
class Publisher(OpenAlexEntity):
|
|
989
|
+
"""Class representing a publisher entity in OpenAlex."""
|
|
990
|
+
|
|
456
991
|
pass
|
|
457
992
|
|
|
458
993
|
|
|
459
994
|
class Publishers(BaseOpenAlex):
|
|
995
|
+
"""Class representing a collection of publisher entities in OpenAlex."""
|
|
996
|
+
|
|
460
997
|
resource_class = Publisher
|
|
461
998
|
|
|
462
999
|
|
|
463
1000
|
class Funder(OpenAlexEntity):
|
|
1001
|
+
"""Class representing a funder entity in OpenAlex."""
|
|
1002
|
+
|
|
464
1003
|
pass
|
|
465
1004
|
|
|
466
1005
|
|
|
467
1006
|
class Funders(BaseOpenAlex):
|
|
1007
|
+
"""Class representing a collection of funder entities in OpenAlex."""
|
|
1008
|
+
|
|
468
1009
|
resource_class = Funder
|
|
469
1010
|
|
|
470
1011
|
|
|
471
1012
|
class Autocomplete(OpenAlexEntity):
|
|
1013
|
+
"""Class representing an autocomplete entity in OpenAlex."""
|
|
1014
|
+
|
|
472
1015
|
pass
|
|
473
1016
|
|
|
474
1017
|
|
|
475
1018
|
class autocompletes(BaseOpenAlex):
|
|
476
|
-
"""Class to autocomplete without being based on the type of entity"""
|
|
1019
|
+
"""Class to autocomplete without being based on the type of entity."""
|
|
477
1020
|
|
|
478
1021
|
resource_class = Autocomplete
|
|
479
1022
|
|
|
480
1023
|
def __getitem__(self, key):
|
|
481
1024
|
return self._get_from_url(
|
|
482
|
-
|
|
1025
|
+
urlunparse(
|
|
1026
|
+
(
|
|
1027
|
+
"https",
|
|
1028
|
+
"api.openalex.org",
|
|
1029
|
+
"autocomplete",
|
|
1030
|
+
"",
|
|
1031
|
+
f"q={quote_plus(key)}",
|
|
1032
|
+
"",
|
|
1033
|
+
)
|
|
1034
|
+
)
|
|
483
1035
|
)
|
|
484
1036
|
|
|
485
1037
|
|
|
486
1038
|
class Concept(OpenAlexEntity):
|
|
1039
|
+
"""Class representing a concept entity in OpenAlex."""
|
|
1040
|
+
|
|
487
1041
|
def __init__(self, *args, **kwargs):
|
|
488
1042
|
warnings.warn(
|
|
489
1043
|
"Concept is deprecated by OpenAlex and replaced by topics.",
|
|
@@ -494,6 +1048,8 @@ class Concept(OpenAlexEntity):
|
|
|
494
1048
|
|
|
495
1049
|
|
|
496
1050
|
class Concepts(BaseOpenAlex):
|
|
1051
|
+
"""Class representing a collection of concept entities in OpenAlex."""
|
|
1052
|
+
|
|
497
1053
|
resource_class = Concept
|
|
498
1054
|
|
|
499
1055
|
def __init__(self, *args, **kwargs):
|
|
@@ -506,7 +1062,18 @@ class Concepts(BaseOpenAlex):
|
|
|
506
1062
|
|
|
507
1063
|
|
|
508
1064
|
def autocomplete(s):
|
|
509
|
-
"""
|
|
1065
|
+
"""Autocomplete with any type of entity.
|
|
1066
|
+
|
|
1067
|
+
Parameters
|
|
1068
|
+
----------
|
|
1069
|
+
s : str
|
|
1070
|
+
String to autocomplete.
|
|
1071
|
+
|
|
1072
|
+
Returns
|
|
1073
|
+
-------
|
|
1074
|
+
OpenAlexResponseList
|
|
1075
|
+
List of autocomplete results.
|
|
1076
|
+
"""
|
|
510
1077
|
return autocompletes()[s]
|
|
511
1078
|
|
|
512
1079
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: pyalex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17
|
|
4
4
|
Summary: Python interface to the OpenAlex database
|
|
5
5
|
Author-email: Jonathan de Bruin <jonathandebruinos@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -17,10 +17,10 @@ License-File: LICENSE
|
|
|
17
17
|
Requires-Dist: requests
|
|
18
18
|
Requires-Dist: urllib3
|
|
19
19
|
Provides-Extra: lint
|
|
20
|
-
Requires-Dist: ruff
|
|
20
|
+
Requires-Dist: ruff; extra == "lint"
|
|
21
21
|
Provides-Extra: test
|
|
22
|
-
Requires-Dist: pytest
|
|
23
|
-
Requires-Dist: pytest-xdist
|
|
22
|
+
Requires-Dist: pytest; extra == "test"
|
|
23
|
+
Requires-Dist: pytest-xdist; extra == "test"
|
|
24
24
|
|
|
25
25
|
<p align="center">
|
|
26
26
|
<img alt="PyAlex - a Python wrapper for OpenAlex" src="https://github.com/J535D165/pyalex/raw/main/pyalex_repocard.svg">
|
|
@@ -126,7 +126,7 @@ Works()["W2741809807"]["open_access"]
|
|
|
126
126
|
The previous works also for Authors, Sources, Institutions, Concepts and Topics
|
|
127
127
|
|
|
128
128
|
```python
|
|
129
|
-
Authors()["
|
|
129
|
+
Authors()["A5027479191"]
|
|
130
130
|
Authors()["https://orcid.org/0000-0002-4297-0502"] # same
|
|
131
131
|
```
|
|
132
132
|
|
|
@@ -139,7 +139,6 @@ Works().random()
|
|
|
139
139
|
Authors().random()
|
|
140
140
|
Sources().random()
|
|
141
141
|
Institutions().random()
|
|
142
|
-
Concepts().random()
|
|
143
142
|
Topics().random()
|
|
144
143
|
Publishers().random()
|
|
145
144
|
Funders().random()
|
|
@@ -183,11 +182,11 @@ Works().count()
|
|
|
183
182
|
For lists of entities, you can return the result as well as the metadata. By default, only the results are returned.
|
|
184
183
|
|
|
185
184
|
```python
|
|
186
|
-
|
|
185
|
+
topics = Topics().get()
|
|
187
186
|
```
|
|
188
187
|
|
|
189
188
|
```python
|
|
190
|
-
print(meta)
|
|
189
|
+
print(topics.meta)
|
|
191
190
|
{'count': 65073, 'db_response_time_ms': 16, 'page': 1, 'per_page': 25}
|
|
192
191
|
```
|
|
193
192
|
|
|
@@ -383,6 +382,10 @@ Works()["W2023271753"].ngrams()
|
|
|
383
382
|
All results from PyAlex can be serialized. For example, save the results to a JSON file:
|
|
384
383
|
|
|
385
384
|
```python
|
|
385
|
+
import json
|
|
386
|
+
from pathlib import Path
|
|
387
|
+
from pyalex import Work
|
|
388
|
+
|
|
386
389
|
with open(Path("works.json"), "w") as f:
|
|
387
390
|
json.dump(Works().get(), f)
|
|
388
391
|
|
|
@@ -394,7 +397,7 @@ with open(Path("works.json")) as f:
|
|
|
394
397
|
|
|
395
398
|
A list of awesome use cases of the OpenAlex dataset.
|
|
396
399
|
|
|
397
|
-
### Cited publications (referenced
|
|
400
|
+
### Cited publications (works referenced by this paper, outgoing citations)
|
|
398
401
|
|
|
399
402
|
```python
|
|
400
403
|
from pyalex import Works
|
|
@@ -405,6 +408,13 @@ w = Works()["W2741809807"]
|
|
|
405
408
|
Works()[w["referenced_works"]]
|
|
406
409
|
```
|
|
407
410
|
|
|
411
|
+
### Citing publications (other works that reference this paper, incoming citations)
|
|
412
|
+
|
|
413
|
+
```python
|
|
414
|
+
from pyalex import Works
|
|
415
|
+
Works().filter(cites="W2741809807").get()
|
|
416
|
+
```
|
|
417
|
+
|
|
408
418
|
### Get works of a single author
|
|
409
419
|
|
|
410
420
|
```python
|
|
@@ -463,6 +473,7 @@ R users can use the excellent [OpenAlexR](https://github.com/ropensci/openalexR)
|
|
|
463
473
|
|
|
464
474
|
> This library is a community contribution. The authors of this Python library aren't affiliated with OpenAlex.
|
|
465
475
|
|
|
476
|
+
This library is maintained by [J535D165](https://github.com/J535D165) and [PeterLombaers](https://github.com/PeterLombaers).
|
|
466
477
|
Feel free to reach out with questions, remarks, and suggestions. The
|
|
467
|
-
[issue tracker](/issues) is a good starting point. You can also
|
|
478
|
+
[issue tracker](/issues) is a good starting point. You can also reach out via
|
|
468
479
|
[jonathandebruinos@gmail.com](mailto:jonathandebruinos@gmail.com).
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pyalex/__init__.py,sha256=upMXti6aJF6lz8J4EbdnQa13GhJzFGre7fnS_tj8NOw,1539
|
|
2
|
+
pyalex/_version.py,sha256=t3d5dJC864lqQ-TUIs6gpWRp7YVH04dI08mpFt6wvR0,508
|
|
3
|
+
pyalex/api.py,sha256=05clKZlIcH7g7G5d7tELxrfUIyyzdPKwbRinZ1Pliy4,26783
|
|
4
|
+
pyalex-0.17.dist-info/LICENSE,sha256=Mhf5MImRYP06a1EPVJCpkpTstOOEfGajN3T_Fz4izMg,1074
|
|
5
|
+
pyalex-0.17.dist-info/METADATA,sha256=3K3i_n7uMyF91DfrwFI5t0X5jaYD8lD2HcySMNHcfbg,14208
|
|
6
|
+
pyalex-0.17.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
|
7
|
+
pyalex-0.17.dist-info/top_level.txt,sha256=D0An8hWy9e0xPhTaT6K-yuJKVeVV3bYGxZ6Y-v2WXSU,7
|
|
8
|
+
pyalex-0.17.dist-info/RECORD,,
|
pyalex-0.15.1.dist-info/RECORD
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
pyalex/__init__.py,sha256=52XK8om6IVD1Yiq_HYOCR6PUY56sPRHutGM03NOrGMQ,1467
|
|
2
|
-
pyalex/_version.py,sha256=po5_rvCFTU8xU9IC56wyK0-zfBgz_U4xX6CO2mv9Mzs,413
|
|
3
|
-
pyalex/api.py,sha256=JTOx0P037IOhhYVAigQO5uPsd1HMQC-SM3wny0_52uU,13236
|
|
4
|
-
pyalex-0.15.1.dist-info/LICENSE,sha256=Mhf5MImRYP06a1EPVJCpkpTstOOEfGajN3T_Fz4izMg,1074
|
|
5
|
-
pyalex-0.15.1.dist-info/METADATA,sha256=JSa8cKcjZUZ-lDo28WyuFN1GHEEWUHWLvSLaKh3IP10,13859
|
|
6
|
-
pyalex-0.15.1.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
|
|
7
|
-
pyalex-0.15.1.dist-info/top_level.txt,sha256=D0An8hWy9e0xPhTaT6K-yuJKVeVV3bYGxZ6Y-v2WXSU,7
|
|
8
|
-
pyalex-0.15.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|