pyalex 0.16__py3-none-any.whl → 0.18__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 +591 -81
- {pyalex-0.16.dist-info → pyalex-0.18.dist-info}/METADATA +3 -3
- pyalex-0.18.dist-info/RECORD +8 -0
- {pyalex-0.16.dist-info → pyalex-0.18.dist-info}/WHEEL +1 -1
- pyalex-0.16.dist-info/RECORD +0 -8
- {pyalex-0.16.dist-info → pyalex-0.18.dist-info}/LICENSE +0 -0
- {pyalex-0.16.dist-info → pyalex-0.18.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
|
|
|
@@ -181,12 +338,12 @@ class Paginator:
|
|
|
181
338
|
self.value = value
|
|
182
339
|
self.per_page = per_page
|
|
183
340
|
self.n_max = n_max
|
|
341
|
+
self.n = 0
|
|
184
342
|
|
|
185
343
|
self._next_value = value
|
|
344
|
+
self._session = _get_requests_session()
|
|
186
345
|
|
|
187
346
|
def __iter__(self):
|
|
188
|
-
self.n = 0
|
|
189
|
-
|
|
190
347
|
return self
|
|
191
348
|
|
|
192
349
|
def _is_max(self):
|
|
@@ -199,38 +356,46 @@ class Paginator:
|
|
|
199
356
|
raise StopIteration
|
|
200
357
|
|
|
201
358
|
if self.method == "cursor":
|
|
202
|
-
|
|
359
|
+
self.endpoint_class._add_params("cursor", self._next_value)
|
|
203
360
|
elif self.method == "page":
|
|
204
|
-
|
|
361
|
+
self.endpoint_class._add_params("page", self._next_value)
|
|
205
362
|
else:
|
|
206
|
-
raise ValueError()
|
|
363
|
+
raise ValueError("Method should be 'cursor' or 'page'")
|
|
207
364
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
365
|
+
if self.per_page is not None and (
|
|
366
|
+
not isinstance(self.per_page, int)
|
|
367
|
+
or (self.per_page < 1 or self.per_page > 200)
|
|
368
|
+
):
|
|
369
|
+
raise ValueError("per_page should be a integer between 1 and 200")
|
|
370
|
+
|
|
371
|
+
if self.per_page is not None:
|
|
372
|
+
self.endpoint_class._add_params("per-page", self.per_page)
|
|
373
|
+
|
|
374
|
+
r = self.endpoint_class._get_from_url(self.endpoint_class.url, self._session)
|
|
211
375
|
|
|
212
376
|
if self.method == "cursor":
|
|
213
|
-
self._next_value = meta["next_cursor"]
|
|
377
|
+
self._next_value = r.meta["next_cursor"]
|
|
214
378
|
|
|
215
379
|
if self.method == "page":
|
|
216
|
-
if len(
|
|
217
|
-
self._next_value = meta["page"] + 1
|
|
380
|
+
if len(r) > 0:
|
|
381
|
+
self._next_value = r.meta["page"] + 1
|
|
218
382
|
else:
|
|
219
383
|
self._next_value = None
|
|
220
384
|
|
|
221
|
-
self.n = self.n + len(
|
|
385
|
+
self.n = self.n + len(r)
|
|
222
386
|
|
|
223
|
-
return
|
|
387
|
+
return r
|
|
224
388
|
|
|
225
389
|
|
|
226
390
|
class OpenAlexAuth(AuthBase):
|
|
227
|
-
"""OpenAlex auth class based on requests auth
|
|
228
|
-
|
|
229
|
-
Includes the email, api_key and user-agent headers.
|
|
391
|
+
"""OpenAlex auth class based on requests auth.
|
|
230
392
|
|
|
231
|
-
|
|
232
|
-
config: an AlexConfig object
|
|
393
|
+
Includes the email, api_key, and user-agent headers.
|
|
233
394
|
|
|
395
|
+
Parameters
|
|
396
|
+
----------
|
|
397
|
+
config : AlexConfig
|
|
398
|
+
Configuration object for OpenAlex API.
|
|
234
399
|
"""
|
|
235
400
|
|
|
236
401
|
def __init__(self, config):
|
|
@@ -250,29 +415,26 @@ class OpenAlexAuth(AuthBase):
|
|
|
250
415
|
|
|
251
416
|
|
|
252
417
|
class BaseOpenAlex:
|
|
253
|
-
"""Base class for OpenAlex objects.
|
|
418
|
+
"""Base class for OpenAlex objects.
|
|
419
|
+
|
|
420
|
+
Parameters
|
|
421
|
+
----------
|
|
422
|
+
params : dict, optional
|
|
423
|
+
Parameters for the API request.
|
|
424
|
+
"""
|
|
254
425
|
|
|
255
426
|
def __init__(self, params=None):
|
|
256
427
|
self.params = params
|
|
257
428
|
|
|
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
429
|
def __getattr__(self, key):
|
|
267
430
|
if key == "groupby":
|
|
268
431
|
raise AttributeError(
|
|
269
|
-
"Object has no attribute 'groupby'.
|
|
432
|
+
"Object has no attribute 'groupby'. Did you mean 'group_by'?"
|
|
270
433
|
)
|
|
271
434
|
|
|
272
435
|
if key == "filter_search":
|
|
273
436
|
raise AttributeError(
|
|
274
|
-
"Object has no attribute 'filter_search'. "
|
|
275
|
-
"Did you mean 'search_filter'?"
|
|
437
|
+
"Object has no attribute 'filter_search'. Did you mean 'search_filter'?"
|
|
276
438
|
)
|
|
277
439
|
|
|
278
440
|
return getattr(self, key)
|
|
@@ -283,42 +445,75 @@ class BaseOpenAlex:
|
|
|
283
445
|
raise ValueError("OpenAlex does not support more than 100 ids")
|
|
284
446
|
|
|
285
447
|
return self.filter_or(openalex_id=record_id).get(per_page=len(record_id))
|
|
448
|
+
elif isinstance(record_id, str):
|
|
449
|
+
self.params = record_id
|
|
450
|
+
return self._get_from_url(self.url)
|
|
451
|
+
else:
|
|
452
|
+
raise ValueError("record_id should be a string or a list of strings")
|
|
453
|
+
|
|
454
|
+
def _url_query(self):
|
|
455
|
+
if isinstance(self.params, list):
|
|
456
|
+
return self.filter_or(openalex_id=self.params)
|
|
457
|
+
elif isinstance(self.params, dict):
|
|
458
|
+
l_params = []
|
|
459
|
+
for k, v in self.params.items():
|
|
460
|
+
if v is None:
|
|
461
|
+
pass
|
|
462
|
+
elif isinstance(v, list):
|
|
463
|
+
l_params.append(
|
|
464
|
+
"{}={}".format(k, ",".join(map(_quote_oa_value, v)))
|
|
465
|
+
)
|
|
466
|
+
elif k in ["filter", "sort"]:
|
|
467
|
+
l_params.append(f"{k}={_flatten_kv(v)}")
|
|
468
|
+
else:
|
|
469
|
+
l_params.append(f"{k}={_quote_oa_value(v)}")
|
|
470
|
+
|
|
471
|
+
if l_params:
|
|
472
|
+
return "&".join(l_params)
|
|
286
473
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
return_meta=False,
|
|
290
|
-
)
|
|
474
|
+
else:
|
|
475
|
+
return ""
|
|
291
476
|
|
|
292
477
|
@property
|
|
293
478
|
def url(self):
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
l_params.append(f"{k}={_quote_oa_value(v)}")
|
|
479
|
+
"""Return the URL for the API request.
|
|
480
|
+
|
|
481
|
+
The URL doens't include the identification, authentication,
|
|
482
|
+
and pagination parameters.
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
Returns
|
|
486
|
+
-------
|
|
487
|
+
str
|
|
488
|
+
URL for the API request.
|
|
489
|
+
"""
|
|
490
|
+
base_path = self.__class__.__name__.lower()
|
|
307
491
|
|
|
308
|
-
if
|
|
309
|
-
|
|
492
|
+
if isinstance(self.params, str):
|
|
493
|
+
path = f"{base_path}/{_quote_oa_value(self.params)}"
|
|
494
|
+
query = ""
|
|
495
|
+
else:
|
|
496
|
+
path = base_path
|
|
497
|
+
query = self._url_query()
|
|
310
498
|
|
|
311
|
-
return
|
|
499
|
+
return urlunparse(("https", "api.openalex.org", path, "", query, ""))
|
|
312
500
|
|
|
313
501
|
def count(self):
|
|
314
|
-
|
|
502
|
+
"""Get the count of results.
|
|
503
|
+
|
|
504
|
+
Returns
|
|
505
|
+
-------
|
|
506
|
+
int
|
|
507
|
+
Count of results.
|
|
508
|
+
"""
|
|
509
|
+
return self.get(per_page=1).meta["count"]
|
|
315
510
|
|
|
316
|
-
|
|
511
|
+
def _get_from_url(self, url, session=None):
|
|
512
|
+
if session is None:
|
|
513
|
+
session = _get_requests_session()
|
|
317
514
|
|
|
318
|
-
|
|
319
|
-
res = _get_requests_session().get(url, auth=OpenAlexAuth(config))
|
|
515
|
+
res = session.get(url, auth=OpenAlexAuth(config))
|
|
320
516
|
|
|
321
|
-
# handle query errors
|
|
322
517
|
if res.status_code == 403:
|
|
323
518
|
if (
|
|
324
519
|
isinstance(res.json()["error"], str)
|
|
@@ -329,34 +524,65 @@ class BaseOpenAlex:
|
|
|
329
524
|
res.raise_for_status()
|
|
330
525
|
res_json = res.json()
|
|
331
526
|
|
|
332
|
-
# group-by or results page
|
|
333
527
|
if self.params and "group-by" in self.params:
|
|
334
|
-
|
|
528
|
+
return OpenAlexResponseList(
|
|
529
|
+
res_json["group_by"], res_json["meta"], self.resource_class
|
|
530
|
+
)
|
|
335
531
|
elif "results" in res_json:
|
|
336
|
-
|
|
532
|
+
return OpenAlexResponseList(
|
|
533
|
+
res_json["results"], res_json["meta"], self.resource_class
|
|
534
|
+
)
|
|
337
535
|
elif "id" in res_json:
|
|
338
|
-
|
|
536
|
+
return self.resource_class(res_json)
|
|
339
537
|
else:
|
|
340
538
|
raise ValueError("Unknown response format")
|
|
341
539
|
|
|
342
|
-
# return result and metadata
|
|
343
|
-
if return_meta:
|
|
344
|
-
return results, res_json["meta"]
|
|
345
|
-
else:
|
|
346
|
-
return results
|
|
347
|
-
|
|
348
540
|
def get(self, return_meta=False, page=None, per_page=None, cursor=None):
|
|
349
|
-
if per_page is not None and (
|
|
350
|
-
|
|
541
|
+
if per_page is not None and (
|
|
542
|
+
not isinstance(per_page, int) or (per_page < 1 or per_page > 200)
|
|
543
|
+
):
|
|
544
|
+
raise ValueError("per_page should be an integer between 1 and 200")
|
|
545
|
+
|
|
546
|
+
if not isinstance(self.params, (str, list)):
|
|
547
|
+
self._add_params("per-page", per_page)
|
|
548
|
+
self._add_params("page", page)
|
|
549
|
+
self._add_params("cursor", cursor)
|
|
351
550
|
|
|
352
|
-
self.
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
551
|
+
resp_list = self._get_from_url(self.url)
|
|
552
|
+
|
|
553
|
+
if return_meta:
|
|
554
|
+
warnings.warn(
|
|
555
|
+
"return_meta is deprecated, call .meta on the result",
|
|
556
|
+
DeprecationWarning,
|
|
557
|
+
stacklevel=2,
|
|
558
|
+
)
|
|
559
|
+
return resp_list, resp_list.meta
|
|
560
|
+
else:
|
|
561
|
+
return resp_list
|
|
356
562
|
|
|
357
563
|
def paginate(self, method="cursor", page=1, per_page=None, cursor="*", n_max=10000):
|
|
564
|
+
"""Paginate results from the API.
|
|
565
|
+
|
|
566
|
+
Parameters
|
|
567
|
+
----------
|
|
568
|
+
method : str, optional
|
|
569
|
+
Pagination method ('cursor' or 'page').
|
|
570
|
+
page : int, optional
|
|
571
|
+
Page number for pagination.
|
|
572
|
+
per_page : int, optional
|
|
573
|
+
Number of results per page.
|
|
574
|
+
cursor : str, optional
|
|
575
|
+
Cursor for pagination.
|
|
576
|
+
n_max : int, optional
|
|
577
|
+
Maximum number of results.
|
|
578
|
+
|
|
579
|
+
Returns
|
|
580
|
+
-------
|
|
581
|
+
Paginator
|
|
582
|
+
Paginator object.
|
|
583
|
+
"""
|
|
358
584
|
if method == "cursor":
|
|
359
|
-
if self.params.get("sample"):
|
|
585
|
+
if isinstance(self.params, dict) and self.params.get("sample"):
|
|
360
586
|
raise ValueError("method should be 'page' when using sample")
|
|
361
587
|
value = cursor
|
|
362
588
|
elif method == "page":
|
|
@@ -369,9 +595,27 @@ class BaseOpenAlex:
|
|
|
369
595
|
)
|
|
370
596
|
|
|
371
597
|
def random(self):
|
|
598
|
+
"""Get a random result.
|
|
599
|
+
|
|
600
|
+
Returns
|
|
601
|
+
-------
|
|
602
|
+
OpenAlexEntity
|
|
603
|
+
Random result.
|
|
604
|
+
"""
|
|
372
605
|
return self.__getitem__("random")
|
|
373
606
|
|
|
374
607
|
def _add_params(self, argument, new_params, raise_if_exists=False):
|
|
608
|
+
"""Add parameters to the API request.
|
|
609
|
+
|
|
610
|
+
Parameters
|
|
611
|
+
----------
|
|
612
|
+
argument : str
|
|
613
|
+
Parameter name.
|
|
614
|
+
new_params : any
|
|
615
|
+
Parameter value.
|
|
616
|
+
raise_if_exists : bool, optional
|
|
617
|
+
Whether to raise an error if the parameter already exists.
|
|
618
|
+
"""
|
|
375
619
|
if raise_if_exists:
|
|
376
620
|
raise NotImplementedError("raise_if_exists is not implemented")
|
|
377
621
|
|
|
@@ -385,63 +629,247 @@ class BaseOpenAlex:
|
|
|
385
629
|
logging.debug("Params updated:", self.params)
|
|
386
630
|
|
|
387
631
|
def filter(self, **kwargs):
|
|
632
|
+
"""Add filter parameters to the API request.
|
|
633
|
+
|
|
634
|
+
Parameters
|
|
635
|
+
----------
|
|
636
|
+
**kwargs : dict
|
|
637
|
+
Filter parameters.
|
|
638
|
+
|
|
639
|
+
Returns
|
|
640
|
+
-------
|
|
641
|
+
BaseOpenAlex
|
|
642
|
+
Updated object.
|
|
643
|
+
"""
|
|
388
644
|
self._add_params("filter", kwargs)
|
|
389
645
|
return self
|
|
390
646
|
|
|
391
647
|
def filter_and(self, **kwargs):
|
|
648
|
+
"""Add AND filter parameters to the API request.
|
|
649
|
+
|
|
650
|
+
Parameters
|
|
651
|
+
----------
|
|
652
|
+
**kwargs : dict
|
|
653
|
+
Filter parameters.
|
|
654
|
+
|
|
655
|
+
Returns
|
|
656
|
+
-------
|
|
657
|
+
BaseOpenAlex
|
|
658
|
+
Updated object.
|
|
659
|
+
"""
|
|
392
660
|
return self.filter(**kwargs)
|
|
393
661
|
|
|
394
662
|
def filter_or(self, **kwargs):
|
|
663
|
+
"""Add OR filter parameters to the API request.
|
|
664
|
+
|
|
665
|
+
Parameters
|
|
666
|
+
----------
|
|
667
|
+
**kwargs : dict
|
|
668
|
+
Filter parameters.
|
|
669
|
+
|
|
670
|
+
Returns
|
|
671
|
+
-------
|
|
672
|
+
BaseOpenAlex
|
|
673
|
+
Updated object.
|
|
674
|
+
"""
|
|
395
675
|
self._add_params("filter", or_(kwargs), raise_if_exists=False)
|
|
396
676
|
return self
|
|
397
677
|
|
|
398
678
|
def filter_not(self, **kwargs):
|
|
679
|
+
"""Add NOT filter parameters to the API request.
|
|
680
|
+
|
|
681
|
+
Parameters
|
|
682
|
+
----------
|
|
683
|
+
**kwargs : dict
|
|
684
|
+
Filter parameters.
|
|
685
|
+
|
|
686
|
+
Returns
|
|
687
|
+
-------
|
|
688
|
+
BaseOpenAlex
|
|
689
|
+
Updated object.
|
|
690
|
+
"""
|
|
399
691
|
self._add_params("filter", _wrap_values_nested_dict(kwargs, not_))
|
|
400
692
|
return self
|
|
401
693
|
|
|
402
694
|
def filter_gt(self, **kwargs):
|
|
695
|
+
"""Add greater than filter parameters to the API request.
|
|
696
|
+
|
|
697
|
+
Parameters
|
|
698
|
+
----------
|
|
699
|
+
**kwargs : dict
|
|
700
|
+
Filter parameters.
|
|
701
|
+
|
|
702
|
+
Returns
|
|
703
|
+
-------
|
|
704
|
+
BaseOpenAlex
|
|
705
|
+
Updated object.
|
|
706
|
+
"""
|
|
403
707
|
self._add_params("filter", _wrap_values_nested_dict(kwargs, gt_))
|
|
404
708
|
return self
|
|
405
709
|
|
|
406
710
|
def filter_lt(self, **kwargs):
|
|
711
|
+
"""Add less than filter parameters to the API request.
|
|
712
|
+
|
|
713
|
+
Parameters
|
|
714
|
+
----------
|
|
715
|
+
**kwargs : dict
|
|
716
|
+
Filter parameters.
|
|
717
|
+
|
|
718
|
+
Returns
|
|
719
|
+
-------
|
|
720
|
+
BaseOpenAlex
|
|
721
|
+
Updated object.
|
|
722
|
+
"""
|
|
407
723
|
self._add_params("filter", _wrap_values_nested_dict(kwargs, lt_))
|
|
408
724
|
return self
|
|
409
725
|
|
|
410
726
|
def search_filter(self, **kwargs):
|
|
727
|
+
"""Add search filter parameters to the API request.
|
|
728
|
+
|
|
729
|
+
Parameters
|
|
730
|
+
----------
|
|
731
|
+
**kwargs : dict
|
|
732
|
+
Filter parameters.
|
|
733
|
+
|
|
734
|
+
Returns
|
|
735
|
+
-------
|
|
736
|
+
BaseOpenAlex
|
|
737
|
+
Updated object.
|
|
738
|
+
"""
|
|
411
739
|
self._add_params("filter", {f"{k}.search": v for k, v in kwargs.items()})
|
|
412
740
|
return self
|
|
413
741
|
|
|
414
742
|
def sort(self, **kwargs):
|
|
743
|
+
"""Add sort parameters to the API request.
|
|
744
|
+
|
|
745
|
+
Parameters
|
|
746
|
+
----------
|
|
747
|
+
**kwargs : dict
|
|
748
|
+
Sort parameters.
|
|
749
|
+
|
|
750
|
+
Returns
|
|
751
|
+
-------
|
|
752
|
+
BaseOpenAlex
|
|
753
|
+
Updated object.
|
|
754
|
+
"""
|
|
415
755
|
self._add_params("sort", kwargs)
|
|
416
756
|
return self
|
|
417
757
|
|
|
418
758
|
def group_by(self, group_key):
|
|
759
|
+
"""Add group-by parameters to the API request.
|
|
760
|
+
|
|
761
|
+
Parameters
|
|
762
|
+
----------
|
|
763
|
+
group_key : str
|
|
764
|
+
Group-by key.
|
|
765
|
+
|
|
766
|
+
Returns
|
|
767
|
+
-------
|
|
768
|
+
BaseOpenAlex
|
|
769
|
+
Updated object.
|
|
770
|
+
"""
|
|
419
771
|
self._add_params("group-by", group_key)
|
|
420
772
|
return self
|
|
421
773
|
|
|
422
774
|
def search(self, s):
|
|
775
|
+
"""Add search parameters to the API request.
|
|
776
|
+
|
|
777
|
+
Parameters
|
|
778
|
+
----------
|
|
779
|
+
s : str
|
|
780
|
+
Search string.
|
|
781
|
+
|
|
782
|
+
Returns
|
|
783
|
+
-------
|
|
784
|
+
BaseOpenAlex
|
|
785
|
+
Updated object.
|
|
786
|
+
"""
|
|
423
787
|
self._add_params("search", s)
|
|
424
788
|
return self
|
|
425
789
|
|
|
426
790
|
def sample(self, n, seed=None):
|
|
791
|
+
"""Add sample parameters to the API request.
|
|
792
|
+
|
|
793
|
+
Parameters
|
|
794
|
+
----------
|
|
795
|
+
n : int
|
|
796
|
+
Number of samples.
|
|
797
|
+
seed : int, optional
|
|
798
|
+
Seed for sampling.
|
|
799
|
+
|
|
800
|
+
Returns
|
|
801
|
+
-------
|
|
802
|
+
BaseOpenAlex
|
|
803
|
+
Updated object.
|
|
804
|
+
"""
|
|
427
805
|
self._add_params("sample", n)
|
|
428
806
|
self._add_params("seed", seed)
|
|
429
807
|
return self
|
|
430
808
|
|
|
431
809
|
def select(self, s):
|
|
810
|
+
"""Add select parameters to the API request.
|
|
811
|
+
|
|
812
|
+
Parameters
|
|
813
|
+
----------
|
|
814
|
+
s : str
|
|
815
|
+
Select string.
|
|
816
|
+
|
|
817
|
+
Returns
|
|
818
|
+
-------
|
|
819
|
+
BaseOpenAlex
|
|
820
|
+
Updated object.
|
|
821
|
+
"""
|
|
432
822
|
self._add_params("select", s)
|
|
433
823
|
return self
|
|
434
824
|
|
|
435
|
-
def autocomplete(self, s,
|
|
436
|
-
"""
|
|
825
|
+
def autocomplete(self, s, return_meta=False):
|
|
826
|
+
"""Return the OpenAlex autocomplete results.
|
|
827
|
+
|
|
828
|
+
Parameters
|
|
829
|
+
----------
|
|
830
|
+
s : str
|
|
831
|
+
String to autocomplete.
|
|
832
|
+
return_meta : bool, optional
|
|
833
|
+
Whether to return metadata.
|
|
834
|
+
|
|
835
|
+
Returns
|
|
836
|
+
-------
|
|
837
|
+
OpenAlexResponseList
|
|
838
|
+
List of autocomplete results.
|
|
839
|
+
"""
|
|
840
|
+
|
|
437
841
|
self._add_params("q", s)
|
|
438
|
-
|
|
842
|
+
|
|
843
|
+
resp_list = self._get_from_url(
|
|
844
|
+
urlunparse(
|
|
845
|
+
(
|
|
846
|
+
"https",
|
|
847
|
+
"api.openalex.org",
|
|
848
|
+
f"autocomplete/{self.__class__.__name__.lower()}",
|
|
849
|
+
"",
|
|
850
|
+
self._url_query(),
|
|
851
|
+
"",
|
|
852
|
+
)
|
|
853
|
+
)
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
if return_meta:
|
|
857
|
+
warnings.warn(
|
|
858
|
+
"return_meta is deprecated, call .meta on the result",
|
|
859
|
+
DeprecationWarning,
|
|
860
|
+
stacklevel=2,
|
|
861
|
+
)
|
|
862
|
+
return resp_list, resp_list.meta
|
|
863
|
+
else:
|
|
864
|
+
return resp_list
|
|
439
865
|
|
|
440
866
|
|
|
441
867
|
# The API
|
|
442
868
|
|
|
443
869
|
|
|
444
870
|
class Work(OpenAlexEntity):
|
|
871
|
+
"""Class representing a work entity in OpenAlex."""
|
|
872
|
+
|
|
445
873
|
def __getitem__(self, key):
|
|
446
874
|
if key == "abstract":
|
|
447
875
|
return invert_abstract(self["abstract_inverted_index"])
|
|
@@ -449,6 +877,18 @@ class Work(OpenAlexEntity):
|
|
|
449
877
|
return super().__getitem__(key)
|
|
450
878
|
|
|
451
879
|
def ngrams(self, return_meta=False):
|
|
880
|
+
"""Get n-grams for the work.
|
|
881
|
+
|
|
882
|
+
Parameters
|
|
883
|
+
----------
|
|
884
|
+
return_meta : bool, optional
|
|
885
|
+
Whether to return metadata.
|
|
886
|
+
|
|
887
|
+
Returns
|
|
888
|
+
-------
|
|
889
|
+
OpenAlexResponseList
|
|
890
|
+
List of n-grams.
|
|
891
|
+
"""
|
|
452
892
|
openalex_id = self["id"].split("/")[-1]
|
|
453
893
|
n_gram_url = f"{config.openalex_url}/works/{openalex_id}/ngrams"
|
|
454
894
|
|
|
@@ -456,105 +896,162 @@ class Work(OpenAlexEntity):
|
|
|
456
896
|
res.raise_for_status()
|
|
457
897
|
results = res.json()
|
|
458
898
|
|
|
459
|
-
|
|
899
|
+
resp_list = OpenAlexResponseList(results["ngrams"], results["meta"])
|
|
900
|
+
|
|
460
901
|
if return_meta:
|
|
461
|
-
|
|
902
|
+
warnings.warn(
|
|
903
|
+
"return_meta is deprecated, call .meta on the result",
|
|
904
|
+
DeprecationWarning,
|
|
905
|
+
stacklevel=2,
|
|
906
|
+
)
|
|
907
|
+
return resp_list, resp_list.meta
|
|
462
908
|
else:
|
|
463
|
-
return
|
|
909
|
+
return resp_list
|
|
464
910
|
|
|
465
911
|
|
|
466
912
|
class Works(BaseOpenAlex):
|
|
913
|
+
"""Class representing a collection of work entities in OpenAlex."""
|
|
914
|
+
|
|
467
915
|
resource_class = Work
|
|
468
916
|
|
|
469
917
|
|
|
470
918
|
class Author(OpenAlexEntity):
|
|
919
|
+
"""Class representing an author entity in OpenAlex."""
|
|
920
|
+
|
|
471
921
|
pass
|
|
472
922
|
|
|
473
923
|
|
|
474
924
|
class Authors(BaseOpenAlex):
|
|
925
|
+
"""Class representing a collection of author entities in OpenAlex."""
|
|
926
|
+
|
|
475
927
|
resource_class = Author
|
|
476
928
|
|
|
477
929
|
|
|
478
930
|
class Source(OpenAlexEntity):
|
|
931
|
+
"""Class representing a source entity in OpenAlex."""
|
|
932
|
+
|
|
479
933
|
pass
|
|
480
934
|
|
|
481
935
|
|
|
482
936
|
class Sources(BaseOpenAlex):
|
|
937
|
+
"""Class representing a collection of source entities in OpenAlex."""
|
|
938
|
+
|
|
483
939
|
resource_class = Source
|
|
484
940
|
|
|
485
941
|
|
|
486
942
|
class Institution(OpenAlexEntity):
|
|
943
|
+
"""Class representing an institution entity in OpenAlex."""
|
|
944
|
+
|
|
487
945
|
pass
|
|
488
946
|
|
|
489
947
|
|
|
490
948
|
class Institutions(BaseOpenAlex):
|
|
949
|
+
"""Class representing a collection of institution entities in OpenAlex."""
|
|
950
|
+
|
|
491
951
|
resource_class = Institution
|
|
492
952
|
|
|
493
953
|
|
|
494
954
|
class Domain(OpenAlexEntity):
|
|
955
|
+
"""Class representing a domain entity in OpenAlex."""
|
|
956
|
+
|
|
495
957
|
pass
|
|
496
958
|
|
|
497
959
|
|
|
498
960
|
class Domains(BaseOpenAlex):
|
|
961
|
+
"""Class representing a collection of domain entities in OpenAlex."""
|
|
962
|
+
|
|
499
963
|
resource_class = Domain
|
|
500
964
|
|
|
501
965
|
|
|
502
966
|
class Field(OpenAlexEntity):
|
|
967
|
+
"""Class representing a field entity in OpenAlex."""
|
|
968
|
+
|
|
503
969
|
pass
|
|
504
970
|
|
|
505
971
|
|
|
506
972
|
class Fields(BaseOpenAlex):
|
|
973
|
+
"""Class representing a collection of field entities in OpenAlex."""
|
|
974
|
+
|
|
507
975
|
resource_class = Field
|
|
508
976
|
|
|
509
977
|
|
|
510
978
|
class Subfield(OpenAlexEntity):
|
|
979
|
+
"""Class representing a subfield entity in OpenAlex."""
|
|
980
|
+
|
|
511
981
|
pass
|
|
512
982
|
|
|
513
983
|
|
|
514
984
|
class Subfields(BaseOpenAlex):
|
|
985
|
+
"""Class representing a collection of subfield entities in OpenAlex."""
|
|
986
|
+
|
|
515
987
|
resource_class = Subfield
|
|
516
988
|
|
|
517
989
|
|
|
518
990
|
class Topic(OpenAlexEntity):
|
|
991
|
+
"""Class representing a topic entity in OpenAlex."""
|
|
992
|
+
|
|
519
993
|
pass
|
|
520
994
|
|
|
521
995
|
|
|
522
996
|
class Topics(BaseOpenAlex):
|
|
997
|
+
"""Class representing a collection of topic entities in OpenAlex."""
|
|
998
|
+
|
|
523
999
|
resource_class = Topic
|
|
524
1000
|
|
|
525
1001
|
|
|
526
1002
|
class Publisher(OpenAlexEntity):
|
|
1003
|
+
"""Class representing a publisher entity in OpenAlex."""
|
|
1004
|
+
|
|
527
1005
|
pass
|
|
528
1006
|
|
|
529
1007
|
|
|
530
1008
|
class Publishers(BaseOpenAlex):
|
|
1009
|
+
"""Class representing a collection of publisher entities in OpenAlex."""
|
|
1010
|
+
|
|
531
1011
|
resource_class = Publisher
|
|
532
1012
|
|
|
533
1013
|
|
|
534
1014
|
class Funder(OpenAlexEntity):
|
|
1015
|
+
"""Class representing a funder entity in OpenAlex."""
|
|
1016
|
+
|
|
535
1017
|
pass
|
|
536
1018
|
|
|
537
1019
|
|
|
538
1020
|
class Funders(BaseOpenAlex):
|
|
1021
|
+
"""Class representing a collection of funder entities in OpenAlex."""
|
|
1022
|
+
|
|
539
1023
|
resource_class = Funder
|
|
540
1024
|
|
|
541
1025
|
|
|
542
1026
|
class Autocomplete(OpenAlexEntity):
|
|
1027
|
+
"""Class representing an autocomplete entity in OpenAlex."""
|
|
1028
|
+
|
|
543
1029
|
pass
|
|
544
1030
|
|
|
545
1031
|
|
|
546
1032
|
class autocompletes(BaseOpenAlex):
|
|
547
|
-
"""Class to autocomplete without being based on the type of entity"""
|
|
1033
|
+
"""Class to autocomplete without being based on the type of entity."""
|
|
548
1034
|
|
|
549
1035
|
resource_class = Autocomplete
|
|
550
1036
|
|
|
551
1037
|
def __getitem__(self, key):
|
|
552
1038
|
return self._get_from_url(
|
|
553
|
-
|
|
1039
|
+
urlunparse(
|
|
1040
|
+
(
|
|
1041
|
+
"https",
|
|
1042
|
+
"api.openalex.org",
|
|
1043
|
+
"autocomplete",
|
|
1044
|
+
"",
|
|
1045
|
+
f"q={quote_plus(key)}",
|
|
1046
|
+
"",
|
|
1047
|
+
)
|
|
1048
|
+
)
|
|
554
1049
|
)
|
|
555
1050
|
|
|
556
1051
|
|
|
557
1052
|
class Concept(OpenAlexEntity):
|
|
1053
|
+
"""Class representing a concept entity in OpenAlex."""
|
|
1054
|
+
|
|
558
1055
|
def __init__(self, *args, **kwargs):
|
|
559
1056
|
warnings.warn(
|
|
560
1057
|
"Concept is deprecated by OpenAlex and replaced by topics.",
|
|
@@ -565,6 +1062,8 @@ class Concept(OpenAlexEntity):
|
|
|
565
1062
|
|
|
566
1063
|
|
|
567
1064
|
class Concepts(BaseOpenAlex):
|
|
1065
|
+
"""Class representing a collection of concept entities in OpenAlex."""
|
|
1066
|
+
|
|
568
1067
|
resource_class = Concept
|
|
569
1068
|
|
|
570
1069
|
def __init__(self, *args, **kwargs):
|
|
@@ -577,7 +1076,18 @@ class Concepts(BaseOpenAlex):
|
|
|
577
1076
|
|
|
578
1077
|
|
|
579
1078
|
def autocomplete(s):
|
|
580
|
-
"""
|
|
1079
|
+
"""Autocomplete with any type of entity.
|
|
1080
|
+
|
|
1081
|
+
Parameters
|
|
1082
|
+
----------
|
|
1083
|
+
s : str
|
|
1084
|
+
String to autocomplete.
|
|
1085
|
+
|
|
1086
|
+
Returns
|
|
1087
|
+
-------
|
|
1088
|
+
OpenAlexResponseList
|
|
1089
|
+
List of autocomplete results.
|
|
1090
|
+
"""
|
|
581
1091
|
return autocompletes()[s]
|
|
582
1092
|
|
|
583
1093
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: pyalex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18
|
|
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=nGXfO1OzooW799_WlgmJZ-ipD8H1pUPrej1lNW4Um9M,508
|
|
3
|
+
pyalex/api.py,sha256=R5hDwrSlzmc3yia85DRF27DWJWpSw4b7DPqnUU_hGmw,27408
|
|
4
|
+
pyalex-0.18.dist-info/LICENSE,sha256=Mhf5MImRYP06a1EPVJCpkpTstOOEfGajN3T_Fz4izMg,1074
|
|
5
|
+
pyalex-0.18.dist-info/METADATA,sha256=GLWXRsSB6K9B5wAQpyHF1CCgJac9Sd6yvpcuZMHDWAg,14208
|
|
6
|
+
pyalex-0.18.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
|
7
|
+
pyalex-0.18.dist-info/top_level.txt,sha256=D0An8hWy9e0xPhTaT6K-yuJKVeVV3bYGxZ6Y-v2WXSU,7
|
|
8
|
+
pyalex-0.18.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
|