pyalex 0.16__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 +2 -2
- pyalex/api.py +568 -72
- {pyalex-0.16.dist-info → pyalex-0.17.dist-info}/METADATA +3 -3
- pyalex-0.17.dist-info/RECORD +8 -0
- {pyalex-0.16.dist-info → pyalex-0.17.dist-info}/WHEEL +1 -1
- pyalex-0.16.dist-info/RECORD +0 -8
- {pyalex-0.16.dist-info → pyalex-0.17.dist-info}/LICENSE +0 -0
- {pyalex-0.16.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
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
|
|
|
@@ -32,10 +53,22 @@ config = AlexConfig(
|
|
|
32
53
|
|
|
33
54
|
|
|
34
55
|
class or_(dict):
|
|
56
|
+
"""Logical OR expression class."""
|
|
57
|
+
|
|
35
58
|
pass
|
|
36
59
|
|
|
37
60
|
|
|
38
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
|
+
|
|
39
72
|
token = None
|
|
40
73
|
|
|
41
74
|
def __init__(self, value):
|
|
@@ -46,14 +79,20 @@ class _LogicalExpression:
|
|
|
46
79
|
|
|
47
80
|
|
|
48
81
|
class not_(_LogicalExpression):
|
|
82
|
+
"""Logical NOT expression class."""
|
|
83
|
+
|
|
49
84
|
token = "!"
|
|
50
85
|
|
|
51
86
|
|
|
52
87
|
class gt_(_LogicalExpression):
|
|
88
|
+
"""Logical greater than expression class."""
|
|
89
|
+
|
|
53
90
|
token = ">"
|
|
54
91
|
|
|
55
92
|
|
|
56
93
|
class lt_(_LogicalExpression):
|
|
94
|
+
"""Logical less than expression class."""
|
|
95
|
+
|
|
57
96
|
token = "<"
|
|
58
97
|
|
|
59
98
|
|
|
@@ -61,6 +100,16 @@ def _quote_oa_value(v):
|
|
|
61
100
|
"""Prepare a value for the OpenAlex API.
|
|
62
101
|
|
|
63
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.
|
|
64
113
|
"""
|
|
65
114
|
|
|
66
115
|
# workaround for bug https://groups.google.com/u/1/g/openalex-users/c/t46RWnzZaXc
|
|
@@ -78,6 +127,22 @@ def _quote_oa_value(v):
|
|
|
78
127
|
|
|
79
128
|
|
|
80
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
|
+
"""
|
|
81
146
|
if prefix is None and not isinstance(d, dict):
|
|
82
147
|
raise ValueError("prefix should be set if d is not a dict")
|
|
83
148
|
|
|
@@ -100,6 +165,15 @@ def _flatten_kv(d, prefix=None, logical="+"):
|
|
|
100
165
|
|
|
101
166
|
|
|
102
167
|
def _params_merge(params, add_params):
|
|
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
|
+
"""
|
|
103
177
|
for k in add_params.keys():
|
|
104
178
|
if (
|
|
105
179
|
k in params
|
|
@@ -128,6 +202,13 @@ def _params_merge(params, add_params):
|
|
|
128
202
|
|
|
129
203
|
|
|
130
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
|
+
"""
|
|
131
212
|
# create an Requests Session with automatic retry:
|
|
132
213
|
requests_session = requests.Session()
|
|
133
214
|
retries = Retry(
|
|
@@ -144,12 +225,38 @@ def _get_requests_session():
|
|
|
144
225
|
|
|
145
226
|
|
|
146
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
|
+
"""
|
|
147
240
|
if inv_index is not None:
|
|
148
241
|
l_inv = [(w, p) for w, pos in inv_index.items() for p in pos]
|
|
149
242
|
return " ".join(map(lambda x: x[0], sorted(l_inv, key=lambda x: x[1])))
|
|
150
243
|
|
|
151
244
|
|
|
152
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
|
+
"""
|
|
153
260
|
for k, v in d.items():
|
|
154
261
|
if isinstance(v, dict):
|
|
155
262
|
d[k] = _wrap_values_nested_dict(v, func)
|
|
@@ -162,14 +269,64 @@ def _wrap_values_nested_dict(d, func):
|
|
|
162
269
|
|
|
163
270
|
|
|
164
271
|
class QueryError(ValueError):
|
|
272
|
+
"""Exception raised for errors in the query."""
|
|
273
|
+
|
|
165
274
|
pass
|
|
166
275
|
|
|
167
276
|
|
|
168
277
|
class OpenAlexEntity(dict):
|
|
278
|
+
"""Base class for OpenAlex entities."""
|
|
279
|
+
|
|
169
280
|
pass
|
|
170
281
|
|
|
171
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
|
+
|
|
172
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
|
+
|
|
173
330
|
VALUE_CURSOR_START = "*"
|
|
174
331
|
VALUE_NUMBER_START = 1
|
|
175
332
|
|
|
@@ -205,32 +362,31 @@ class Paginator:
|
|
|
205
362
|
else:
|
|
206
363
|
raise ValueError()
|
|
207
364
|
|
|
208
|
-
|
|
209
|
-
return_meta=True, per_page=self.per_page, **pagination_params
|
|
210
|
-
)
|
|
365
|
+
r = self.endpoint_class.get(per_page=self.per_page, **pagination_params)
|
|
211
366
|
|
|
212
367
|
if self.method == "cursor":
|
|
213
|
-
self._next_value = meta["next_cursor"]
|
|
368
|
+
self._next_value = r.meta["next_cursor"]
|
|
214
369
|
|
|
215
370
|
if self.method == "page":
|
|
216
|
-
if len(
|
|
217
|
-
self._next_value = meta["page"] + 1
|
|
371
|
+
if len(r) > 0:
|
|
372
|
+
self._next_value = r.meta["page"] + 1
|
|
218
373
|
else:
|
|
219
374
|
self._next_value = None
|
|
220
375
|
|
|
221
|
-
self.n = self.n + len(
|
|
376
|
+
self.n = self.n + len(r)
|
|
222
377
|
|
|
223
|
-
return
|
|
378
|
+
return r
|
|
224
379
|
|
|
225
380
|
|
|
226
381
|
class OpenAlexAuth(AuthBase):
|
|
227
|
-
"""OpenAlex auth class based on requests auth
|
|
228
|
-
|
|
229
|
-
Includes the email, api_key and user-agent headers.
|
|
382
|
+
"""OpenAlex auth class based on requests auth.
|
|
230
383
|
|
|
231
|
-
|
|
232
|
-
config: an AlexConfig object
|
|
384
|
+
Includes the email, api_key, and user-agent headers.
|
|
233
385
|
|
|
386
|
+
Parameters
|
|
387
|
+
----------
|
|
388
|
+
config : AlexConfig
|
|
389
|
+
Configuration object for OpenAlex API.
|
|
234
390
|
"""
|
|
235
391
|
|
|
236
392
|
def __init__(self, config):
|
|
@@ -250,29 +406,26 @@ class OpenAlexAuth(AuthBase):
|
|
|
250
406
|
|
|
251
407
|
|
|
252
408
|
class BaseOpenAlex:
|
|
253
|
-
"""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
|
+
"""
|
|
254
416
|
|
|
255
417
|
def __init__(self, params=None):
|
|
256
418
|
self.params = params
|
|
257
419
|
|
|
258
|
-
def _full_collection_name(self):
|
|
259
|
-
if self.params is not None and "q" in self.params.keys():
|
|
260
|
-
return (
|
|
261
|
-
f"{config.openalex_url}/autocomplete/{self.__class__.__name__.lower()}"
|
|
262
|
-
)
|
|
263
|
-
else:
|
|
264
|
-
return f"{config.openalex_url}/{self.__class__.__name__.lower()}"
|
|
265
|
-
|
|
266
420
|
def __getattr__(self, key):
|
|
267
421
|
if key == "groupby":
|
|
268
422
|
raise AttributeError(
|
|
269
|
-
"Object has no attribute 'groupby'.
|
|
423
|
+
"Object has no attribute 'groupby'. Did you mean 'group_by'?"
|
|
270
424
|
)
|
|
271
425
|
|
|
272
426
|
if key == "filter_search":
|
|
273
427
|
raise AttributeError(
|
|
274
|
-
"Object has no attribute 'filter_search'. "
|
|
275
|
-
"Did you mean 'search_filter'?"
|
|
428
|
+
"Object has no attribute 'filter_search'. Did you mean 'search_filter'?"
|
|
276
429
|
)
|
|
277
430
|
|
|
278
431
|
return getattr(self, key)
|
|
@@ -283,42 +436,72 @@ class BaseOpenAlex:
|
|
|
283
436
|
raise ValueError("OpenAlex does not support more than 100 ids")
|
|
284
437
|
|
|
285
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)
|
|
286
464
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
return_meta=False,
|
|
290
|
-
)
|
|
465
|
+
else:
|
|
466
|
+
return ""
|
|
291
467
|
|
|
292
468
|
@property
|
|
293
469
|
def url(self):
|
|
294
|
-
|
|
295
|
-
return self._full_collection_name()
|
|
296
|
-
|
|
297
|
-
l_params = []
|
|
298
|
-
for k, v in self.params.items():
|
|
299
|
-
if v is None:
|
|
300
|
-
pass
|
|
301
|
-
elif isinstance(v, list):
|
|
302
|
-
l_params.append("{}={}".format(k, ",".join(map(_quote_oa_value, v))))
|
|
303
|
-
elif k in ["filter", "sort"]:
|
|
304
|
-
l_params.append(f"{k}={_flatten_kv(v)}")
|
|
305
|
-
else:
|
|
306
|
-
l_params.append(f"{k}={_quote_oa_value(v)}")
|
|
470
|
+
"""Return the URL for the API request.
|
|
307
471
|
|
|
308
|
-
|
|
309
|
-
|
|
472
|
+
The URL doens't include the identification, authentication,
|
|
473
|
+
and pagination parameters.
|
|
310
474
|
|
|
311
|
-
|
|
475
|
+
|
|
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, ""))
|
|
312
491
|
|
|
313
492
|
def count(self):
|
|
314
|
-
|
|
493
|
+
"""Get the count of results.
|
|
315
494
|
|
|
316
|
-
|
|
495
|
+
Returns
|
|
496
|
+
-------
|
|
497
|
+
int
|
|
498
|
+
Count of results.
|
|
499
|
+
"""
|
|
500
|
+
return self.get(per_page=1).meta["count"]
|
|
317
501
|
|
|
318
|
-
def _get_from_url(self, url
|
|
502
|
+
def _get_from_url(self, url):
|
|
319
503
|
res = _get_requests_session().get(url, auth=OpenAlexAuth(config))
|
|
320
504
|
|
|
321
|
-
# handle query errors
|
|
322
505
|
if res.status_code == 403:
|
|
323
506
|
if (
|
|
324
507
|
isinstance(res.json()["error"], str)
|
|
@@ -329,32 +512,61 @@ class BaseOpenAlex:
|
|
|
329
512
|
res.raise_for_status()
|
|
330
513
|
res_json = res.json()
|
|
331
514
|
|
|
332
|
-
# group-by or results page
|
|
333
515
|
if self.params and "group-by" in self.params:
|
|
334
|
-
|
|
516
|
+
return OpenAlexResponseList(
|
|
517
|
+
res_json["group_by"], res_json["meta"], self.resource_class
|
|
518
|
+
)
|
|
335
519
|
elif "results" in res_json:
|
|
336
|
-
|
|
520
|
+
return OpenAlexResponseList(
|
|
521
|
+
res_json["results"], res_json["meta"], self.resource_class
|
|
522
|
+
)
|
|
337
523
|
elif "id" in res_json:
|
|
338
|
-
|
|
524
|
+
return self.resource_class(res_json)
|
|
339
525
|
else:
|
|
340
526
|
raise ValueError("Unknown response format")
|
|
341
527
|
|
|
342
|
-
# return result and metadata
|
|
343
|
-
if return_meta:
|
|
344
|
-
return results, res_json["meta"]
|
|
345
|
-
else:
|
|
346
|
-
return results
|
|
347
|
-
|
|
348
528
|
def get(self, return_meta=False, page=None, per_page=None, cursor=None):
|
|
349
529
|
if per_page is not None and (per_page < 1 or per_page > 200):
|
|
350
530
|
raise ValueError("per_page should be a number between 1 and 200.")
|
|
351
531
|
|
|
352
|
-
self.
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
|
356
548
|
|
|
357
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
|
+
"""
|
|
358
570
|
if method == "cursor":
|
|
359
571
|
if self.params.get("sample"):
|
|
360
572
|
raise ValueError("method should be 'page' when using sample")
|
|
@@ -369,9 +581,27 @@ class BaseOpenAlex:
|
|
|
369
581
|
)
|
|
370
582
|
|
|
371
583
|
def random(self):
|
|
584
|
+
"""Get a random result.
|
|
585
|
+
|
|
586
|
+
Returns
|
|
587
|
+
-------
|
|
588
|
+
OpenAlexEntity
|
|
589
|
+
Random result.
|
|
590
|
+
"""
|
|
372
591
|
return self.__getitem__("random")
|
|
373
592
|
|
|
374
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
|
+
"""
|
|
375
605
|
if raise_if_exists:
|
|
376
606
|
raise NotImplementedError("raise_if_exists is not implemented")
|
|
377
607
|
|
|
@@ -385,63 +615,247 @@ class BaseOpenAlex:
|
|
|
385
615
|
logging.debug("Params updated:", self.params)
|
|
386
616
|
|
|
387
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
|
+
"""
|
|
388
630
|
self._add_params("filter", kwargs)
|
|
389
631
|
return self
|
|
390
632
|
|
|
391
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
|
+
"""
|
|
392
646
|
return self.filter(**kwargs)
|
|
393
647
|
|
|
394
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
|
+
"""
|
|
395
661
|
self._add_params("filter", or_(kwargs), raise_if_exists=False)
|
|
396
662
|
return self
|
|
397
663
|
|
|
398
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
|
+
"""
|
|
399
677
|
self._add_params("filter", _wrap_values_nested_dict(kwargs, not_))
|
|
400
678
|
return self
|
|
401
679
|
|
|
402
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
|
+
"""
|
|
403
693
|
self._add_params("filter", _wrap_values_nested_dict(kwargs, gt_))
|
|
404
694
|
return self
|
|
405
695
|
|
|
406
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
|
+
"""
|
|
407
709
|
self._add_params("filter", _wrap_values_nested_dict(kwargs, lt_))
|
|
408
710
|
return self
|
|
409
711
|
|
|
410
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
|
+
"""
|
|
411
725
|
self._add_params("filter", {f"{k}.search": v for k, v in kwargs.items()})
|
|
412
726
|
return self
|
|
413
727
|
|
|
414
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
|
+
"""
|
|
415
741
|
self._add_params("sort", kwargs)
|
|
416
742
|
return self
|
|
417
743
|
|
|
418
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
|
+
"""
|
|
419
757
|
self._add_params("group-by", group_key)
|
|
420
758
|
return self
|
|
421
759
|
|
|
422
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
|
+
"""
|
|
423
773
|
self._add_params("search", s)
|
|
424
774
|
return self
|
|
425
775
|
|
|
426
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
|
+
"""
|
|
427
791
|
self._add_params("sample", n)
|
|
428
792
|
self._add_params("seed", seed)
|
|
429
793
|
return self
|
|
430
794
|
|
|
431
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
|
+
"""
|
|
432
808
|
self._add_params("select", s)
|
|
433
809
|
return self
|
|
434
810
|
|
|
435
|
-
def autocomplete(self, s,
|
|
436
|
-
"""
|
|
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
|
+
|
|
437
827
|
self._add_params("q", s)
|
|
438
|
-
|
|
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
|
|
439
851
|
|
|
440
852
|
|
|
441
853
|
# The API
|
|
442
854
|
|
|
443
855
|
|
|
444
856
|
class Work(OpenAlexEntity):
|
|
857
|
+
"""Class representing a work entity in OpenAlex."""
|
|
858
|
+
|
|
445
859
|
def __getitem__(self, key):
|
|
446
860
|
if key == "abstract":
|
|
447
861
|
return invert_abstract(self["abstract_inverted_index"])
|
|
@@ -449,6 +863,18 @@ class Work(OpenAlexEntity):
|
|
|
449
863
|
return super().__getitem__(key)
|
|
450
864
|
|
|
451
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
|
+
"""
|
|
452
878
|
openalex_id = self["id"].split("/")[-1]
|
|
453
879
|
n_gram_url = f"{config.openalex_url}/works/{openalex_id}/ngrams"
|
|
454
880
|
|
|
@@ -456,105 +882,162 @@ class Work(OpenAlexEntity):
|
|
|
456
882
|
res.raise_for_status()
|
|
457
883
|
results = res.json()
|
|
458
884
|
|
|
459
|
-
|
|
885
|
+
resp_list = OpenAlexResponseList(results["ngrams"], results["meta"])
|
|
886
|
+
|
|
460
887
|
if return_meta:
|
|
461
|
-
|
|
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
|
|
462
894
|
else:
|
|
463
|
-
return
|
|
895
|
+
return resp_list
|
|
464
896
|
|
|
465
897
|
|
|
466
898
|
class Works(BaseOpenAlex):
|
|
899
|
+
"""Class representing a collection of work entities in OpenAlex."""
|
|
900
|
+
|
|
467
901
|
resource_class = Work
|
|
468
902
|
|
|
469
903
|
|
|
470
904
|
class Author(OpenAlexEntity):
|
|
905
|
+
"""Class representing an author entity in OpenAlex."""
|
|
906
|
+
|
|
471
907
|
pass
|
|
472
908
|
|
|
473
909
|
|
|
474
910
|
class Authors(BaseOpenAlex):
|
|
911
|
+
"""Class representing a collection of author entities in OpenAlex."""
|
|
912
|
+
|
|
475
913
|
resource_class = Author
|
|
476
914
|
|
|
477
915
|
|
|
478
916
|
class Source(OpenAlexEntity):
|
|
917
|
+
"""Class representing a source entity in OpenAlex."""
|
|
918
|
+
|
|
479
919
|
pass
|
|
480
920
|
|
|
481
921
|
|
|
482
922
|
class Sources(BaseOpenAlex):
|
|
923
|
+
"""Class representing a collection of source entities in OpenAlex."""
|
|
924
|
+
|
|
483
925
|
resource_class = Source
|
|
484
926
|
|
|
485
927
|
|
|
486
928
|
class Institution(OpenAlexEntity):
|
|
929
|
+
"""Class representing an institution entity in OpenAlex."""
|
|
930
|
+
|
|
487
931
|
pass
|
|
488
932
|
|
|
489
933
|
|
|
490
934
|
class Institutions(BaseOpenAlex):
|
|
935
|
+
"""Class representing a collection of institution entities in OpenAlex."""
|
|
936
|
+
|
|
491
937
|
resource_class = Institution
|
|
492
938
|
|
|
493
939
|
|
|
494
940
|
class Domain(OpenAlexEntity):
|
|
941
|
+
"""Class representing a domain entity in OpenAlex."""
|
|
942
|
+
|
|
495
943
|
pass
|
|
496
944
|
|
|
497
945
|
|
|
498
946
|
class Domains(BaseOpenAlex):
|
|
947
|
+
"""Class representing a collection of domain entities in OpenAlex."""
|
|
948
|
+
|
|
499
949
|
resource_class = Domain
|
|
500
950
|
|
|
501
951
|
|
|
502
952
|
class Field(OpenAlexEntity):
|
|
953
|
+
"""Class representing a field entity in OpenAlex."""
|
|
954
|
+
|
|
503
955
|
pass
|
|
504
956
|
|
|
505
957
|
|
|
506
958
|
class Fields(BaseOpenAlex):
|
|
959
|
+
"""Class representing a collection of field entities in OpenAlex."""
|
|
960
|
+
|
|
507
961
|
resource_class = Field
|
|
508
962
|
|
|
509
963
|
|
|
510
964
|
class Subfield(OpenAlexEntity):
|
|
965
|
+
"""Class representing a subfield entity in OpenAlex."""
|
|
966
|
+
|
|
511
967
|
pass
|
|
512
968
|
|
|
513
969
|
|
|
514
970
|
class Subfields(BaseOpenAlex):
|
|
971
|
+
"""Class representing a collection of subfield entities in OpenAlex."""
|
|
972
|
+
|
|
515
973
|
resource_class = Subfield
|
|
516
974
|
|
|
517
975
|
|
|
518
976
|
class Topic(OpenAlexEntity):
|
|
977
|
+
"""Class representing a topic entity in OpenAlex."""
|
|
978
|
+
|
|
519
979
|
pass
|
|
520
980
|
|
|
521
981
|
|
|
522
982
|
class Topics(BaseOpenAlex):
|
|
983
|
+
"""Class representing a collection of topic entities in OpenAlex."""
|
|
984
|
+
|
|
523
985
|
resource_class = Topic
|
|
524
986
|
|
|
525
987
|
|
|
526
988
|
class Publisher(OpenAlexEntity):
|
|
989
|
+
"""Class representing a publisher entity in OpenAlex."""
|
|
990
|
+
|
|
527
991
|
pass
|
|
528
992
|
|
|
529
993
|
|
|
530
994
|
class Publishers(BaseOpenAlex):
|
|
995
|
+
"""Class representing a collection of publisher entities in OpenAlex."""
|
|
996
|
+
|
|
531
997
|
resource_class = Publisher
|
|
532
998
|
|
|
533
999
|
|
|
534
1000
|
class Funder(OpenAlexEntity):
|
|
1001
|
+
"""Class representing a funder entity in OpenAlex."""
|
|
1002
|
+
|
|
535
1003
|
pass
|
|
536
1004
|
|
|
537
1005
|
|
|
538
1006
|
class Funders(BaseOpenAlex):
|
|
1007
|
+
"""Class representing a collection of funder entities in OpenAlex."""
|
|
1008
|
+
|
|
539
1009
|
resource_class = Funder
|
|
540
1010
|
|
|
541
1011
|
|
|
542
1012
|
class Autocomplete(OpenAlexEntity):
|
|
1013
|
+
"""Class representing an autocomplete entity in OpenAlex."""
|
|
1014
|
+
|
|
543
1015
|
pass
|
|
544
1016
|
|
|
545
1017
|
|
|
546
1018
|
class autocompletes(BaseOpenAlex):
|
|
547
|
-
"""Class to autocomplete without being based on the type of entity"""
|
|
1019
|
+
"""Class to autocomplete without being based on the type of entity."""
|
|
548
1020
|
|
|
549
1021
|
resource_class = Autocomplete
|
|
550
1022
|
|
|
551
1023
|
def __getitem__(self, key):
|
|
552
1024
|
return self._get_from_url(
|
|
553
|
-
|
|
1025
|
+
urlunparse(
|
|
1026
|
+
(
|
|
1027
|
+
"https",
|
|
1028
|
+
"api.openalex.org",
|
|
1029
|
+
"autocomplete",
|
|
1030
|
+
"",
|
|
1031
|
+
f"q={quote_plus(key)}",
|
|
1032
|
+
"",
|
|
1033
|
+
)
|
|
1034
|
+
)
|
|
554
1035
|
)
|
|
555
1036
|
|
|
556
1037
|
|
|
557
1038
|
class Concept(OpenAlexEntity):
|
|
1039
|
+
"""Class representing a concept entity in OpenAlex."""
|
|
1040
|
+
|
|
558
1041
|
def __init__(self, *args, **kwargs):
|
|
559
1042
|
warnings.warn(
|
|
560
1043
|
"Concept is deprecated by OpenAlex and replaced by topics.",
|
|
@@ -565,6 +1048,8 @@ class Concept(OpenAlexEntity):
|
|
|
565
1048
|
|
|
566
1049
|
|
|
567
1050
|
class Concepts(BaseOpenAlex):
|
|
1051
|
+
"""Class representing a collection of concept entities in OpenAlex."""
|
|
1052
|
+
|
|
568
1053
|
resource_class = Concept
|
|
569
1054
|
|
|
570
1055
|
def __init__(self, *args, **kwargs):
|
|
@@ -577,7 +1062,18 @@ class Concepts(BaseOpenAlex):
|
|
|
577
1062
|
|
|
578
1063
|
|
|
579
1064
|
def autocomplete(s):
|
|
580
|
-
"""
|
|
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
|
+
"""
|
|
581
1077
|
return autocompletes()[s]
|
|
582
1078
|
|
|
583
1079
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
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
|
|
@@ -182,11 +182,11 @@ Works().count()
|
|
|
182
182
|
For lists of entities, you can return the result as well as the metadata. By default, only the results are returned.
|
|
183
183
|
|
|
184
184
|
```python
|
|
185
|
-
|
|
185
|
+
topics = Topics().get()
|
|
186
186
|
```
|
|
187
187
|
|
|
188
188
|
```python
|
|
189
|
-
print(meta)
|
|
189
|
+
print(topics.meta)
|
|
190
190
|
{'count': 65073, 'db_response_time_ms': 16, 'page': 1, 'per_page': 25}
|
|
191
191
|
```
|
|
192
192
|
|
|
@@ -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.16.dist-info/RECORD
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
pyalex/__init__.py,sha256=52XK8om6IVD1Yiq_HYOCR6PUY56sPRHutGM03NOrGMQ,1467
|
|
2
|
-
pyalex/_version.py,sha256=5zc7xTIiU-8qDWOhJyQ0RIy_saYu1BECCONIFoa0eLw,508
|
|
3
|
-
pyalex/api.py,sha256=YqVMwNEkg_LQdIVzOSXHe4bRhfNFg_YmCJqG1MoY23Y,14993
|
|
4
|
-
pyalex-0.16.dist-info/LICENSE,sha256=Mhf5MImRYP06a1EPVJCpkpTstOOEfGajN3T_Fz4izMg,1074
|
|
5
|
-
pyalex-0.16.dist-info/METADATA,sha256=7qIHsL5k8LL01z7Yc8_zpuJnGH3Av23THNz4g6QLZWI,14224
|
|
6
|
-
pyalex-0.16.dist-info/WHEEL,sha256=nn6H5-ilmfVryoAQl3ZQ2l8SH5imPWFpm1A5FgEuFV4,91
|
|
7
|
-
pyalex-0.16.dist-info/top_level.txt,sha256=D0An8hWy9e0xPhTaT6K-yuJKVeVV3bYGxZ6Y-v2WXSU,7
|
|
8
|
-
pyalex-0.16.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|