pyalex 0.13__py3-none-any.whl → 0.15__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 CHANGED
@@ -9,6 +9,10 @@ from pyalex.api import Author
9
9
  from pyalex.api import Authors
10
10
  from pyalex.api import Concept
11
11
  from pyalex.api import Concepts
12
+ from pyalex.api import Domain
13
+ from pyalex.api import Domains
14
+ from pyalex.api import Field
15
+ from pyalex.api import Fields
12
16
  from pyalex.api import Funder
13
17
  from pyalex.api import Funders
14
18
  from pyalex.api import Institution
@@ -19,10 +23,13 @@ from pyalex.api import Publisher
19
23
  from pyalex.api import Publishers
20
24
  from pyalex.api import Source
21
25
  from pyalex.api import Sources
22
- from pyalex.api import Venue
23
- from pyalex.api import Venues
26
+ from pyalex.api import Subfield
27
+ from pyalex.api import Subfields
28
+ from pyalex.api import Topic
29
+ from pyalex.api import Topics
24
30
  from pyalex.api import Work
25
31
  from pyalex.api import Works
32
+ from pyalex.api import autocomplete
26
33
  from pyalex.api import config
27
34
  from pyalex.api import invert_abstract
28
35
 
@@ -37,14 +44,21 @@ __all__ = [
37
44
  "Funders",
38
45
  "Publishers",
39
46
  "Publisher",
40
- "Venues",
41
- "Venue",
42
47
  "Institutions",
43
48
  "Institution",
44
49
  "Concepts",
45
50
  "Concept",
51
+ "Domains",
52
+ "Domain",
53
+ "Fields",
54
+ "Field",
55
+ "Subfields",
56
+ "Subfield",
57
+ "Topics",
58
+ "Topic",
46
59
  "People",
47
60
  "Journals",
61
+ "autocomplete",
48
62
  "config",
49
63
  "invert_abstract",
50
64
  ]
pyalex/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.13'
16
- __version_tuple__ = version_tuple = (0, 13)
15
+ __version__ = version = '0.15'
16
+ __version_tuple__ = version_tuple = (0, 15)
pyalex/api.py CHANGED
@@ -23,7 +23,7 @@ class AlexConfig(dict):
23
23
  config = AlexConfig(
24
24
  email=None,
25
25
  api_key=None,
26
- user_agent="pyalex/" + __version__,
26
+ user_agent="pyalex/{__version__}",
27
27
  openalex_url="https://api.openalex.org",
28
28
  max_retries=0,
29
29
  retry_backoff_factor=0.1,
@@ -31,12 +31,28 @@ config = AlexConfig(
31
31
  )
32
32
 
33
33
 
34
+ def _quote_oa_value(v):
35
+ """Prepare a value for the OpenAlex API.
36
+
37
+ Applies URL encoding to strings and converts booleans to lowercase strings.
38
+ """
39
+
40
+ # workaround for bug https://groups.google.com/u/1/g/openalex-users/c/t46RWnzZaXc
41
+ if isinstance(v, bool):
42
+ return str(v).lower()
43
+
44
+ if isinstance(v, str):
45
+ return quote_plus(v)
46
+
47
+ return v
48
+
49
+
34
50
  def _flatten_kv(d, prefix=""):
35
51
  if isinstance(d, dict):
36
52
  t = []
37
53
  for k, v in d.items():
38
54
  if isinstance(v, list):
39
- t.extend([f"{prefix}.{k}:{i}" for i in v])
55
+ t.extend([f"{prefix}.{k}:{_quote_oa_value(i)}" for i in v])
40
56
  else:
41
57
  new_prefix = f"{prefix}.{k}" if prefix else f"{k}"
42
58
  x = _flatten_kv(v, prefix=new_prefix)
@@ -44,10 +60,7 @@ def _flatten_kv(d, prefix=""):
44
60
 
45
61
  return ",".join(t)
46
62
  else:
47
- # workaround for bug https://groups.google.com/u/1/g/openalex-users/c/t46RWnzZaXc
48
- d = str(d).lower() if isinstance(d, bool) else d
49
-
50
- return f"{prefix}:{d}"
63
+ return f"{prefix}:{_quote_oa_value(d)}"
51
64
 
52
65
 
53
66
  def _params_merge(params, add_params):
@@ -198,7 +211,12 @@ class BaseOpenAlex:
198
211
  return self.filter(openalex_id="|".join(record_list)).get()
199
212
 
200
213
  def _full_collection_name(self):
201
- return config.openalex_url + "/" + self.__class__.__name__.lower()
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()}"
202
220
 
203
221
  def __getattr__(self, key):
204
222
  if key == "groupby":
@@ -219,7 +237,7 @@ class BaseOpenAlex:
219
237
  return self._get_multi_items(record_id)
220
238
 
221
239
  return self._get_from_url(
222
- self._full_collection_name() + "/" + record_id, return_meta=False
240
+ f"{self._full_collection_name()}/{record_id}", return_meta=False
223
241
  )
224
242
 
225
243
  @property
@@ -232,15 +250,14 @@ class BaseOpenAlex:
232
250
  if v is None:
233
251
  pass
234
252
  elif isinstance(v, list):
235
- v_quote = [quote_plus(q) for q in v]
236
- l_params.append(k + "=" + ",".join(v_quote))
253
+ l_params.append("{}={}".format(k, ",".join(map(_quote_oa_value, v))))
237
254
  elif k in ["filter", "sort"]:
238
- l_params.append(k + "=" + _flatten_kv(v))
255
+ l_params.append(f"{k}={_flatten_kv(v)}")
239
256
  else:
240
- l_params.append(k + "=" + quote_plus(str(v)))
257
+ l_params.append(f"{k}={_quote_oa_value(v)}")
241
258
 
242
259
  if l_params:
243
- return self._full_collection_name() + "?" + "&".join(l_params)
260
+ return "{}?{}".format(self._full_collection_name(), "&".join(l_params))
244
261
 
245
262
  return self._full_collection_name()
246
263
 
@@ -286,11 +303,12 @@ class BaseOpenAlex:
286
303
  self._add_params("per-page", per_page)
287
304
  self._add_params("page", page)
288
305
  self._add_params("cursor", cursor)
289
-
290
306
  return self._get_from_url(self.url, return_meta=return_meta)
291
307
 
292
308
  def paginate(self, method="cursor", page=1, per_page=None, cursor="*", n_max=10000):
293
309
  if method == "cursor":
310
+ if self.params.get("sample"):
311
+ raise ValueError("method should be 'page' when using sample")
294
312
  value = cursor
295
313
  elif method == "page":
296
314
  value = page
@@ -343,6 +361,11 @@ class BaseOpenAlex:
343
361
  self._add_params("select", s)
344
362
  return self
345
363
 
364
+ def autocomplete(self, s, **kwargs):
365
+ """autocomplete the string s, for a specific type of entity"""
366
+ self._add_params("q", s)
367
+ return self.get(**kwargs)
368
+
346
369
 
347
370
  # The API
348
371
 
@@ -397,12 +420,36 @@ class Institutions(BaseOpenAlex):
397
420
  resource_class = Institution
398
421
 
399
422
 
400
- class Concept(OpenAlexEntity):
423
+ class Domain(OpenAlexEntity):
401
424
  pass
402
425
 
403
426
 
404
- class Concepts(BaseOpenAlex):
405
- resource_class = Concept
427
+ class Domains(BaseOpenAlex):
428
+ resource_class = Domain
429
+
430
+
431
+ class Field(OpenAlexEntity):
432
+ pass
433
+
434
+
435
+ class Fields(BaseOpenAlex):
436
+ resource_class = Field
437
+
438
+
439
+ class Subfield(OpenAlexEntity):
440
+ pass
441
+
442
+
443
+ class Subfields(BaseOpenAlex):
444
+ resource_class = Subfield
445
+
446
+
447
+ class Topic(OpenAlexEntity):
448
+ pass
449
+
450
+
451
+ class Topics(BaseOpenAlex):
452
+ resource_class = Topic
406
453
 
407
454
 
408
455
  class Publisher(OpenAlexEntity):
@@ -421,26 +468,46 @@ class Funders(BaseOpenAlex):
421
468
  resource_class = Funder
422
469
 
423
470
 
424
- def Venue(*args, **kwargs): # deprecated
425
- # warn about deprecation
426
- warnings.warn(
427
- "Venue is deprecated. Use Sources instead.",
428
- DeprecationWarning,
429
- stacklevel=2,
430
- )
471
+ class Autocomplete(OpenAlexEntity):
472
+ pass
431
473
 
432
- return Source(*args, **kwargs)
433
474
 
475
+ class autocompletes(BaseOpenAlex):
476
+ """Class to autocomplete without being based on the type of entity"""
477
+
478
+ resource_class = Autocomplete
479
+
480
+ def __getitem__(self, key):
481
+ return self._get_from_url(
482
+ f"{config.openalex_url}/autocomplete?q={key}", return_meta=False
483
+ )
484
+
485
+
486
+ class Concept(OpenAlexEntity):
487
+ def __init__(self, *args, **kwargs):
488
+ warnings.warn(
489
+ "Concept is deprecated by OpenAlex and replaced by topics.",
490
+ DeprecationWarning,
491
+ stacklevel=2,
492
+ )
493
+ super().__init__(*args, **kwargs)
494
+
495
+
496
+ class Concepts(BaseOpenAlex):
497
+ resource_class = Concept
498
+
499
+ def __init__(self, *args, **kwargs):
500
+ warnings.warn(
501
+ "Concepts is deprecated by OpenAlex and replaced by topics.",
502
+ DeprecationWarning,
503
+ stacklevel=2,
504
+ )
505
+ super().__init__(*args, **kwargs)
434
506
 
435
- def Venues(*args, **kwargs): # deprecated
436
- # warn about deprecation
437
- warnings.warn(
438
- "Venues is deprecated. Use Sources instead.",
439
- DeprecationWarning,
440
- stacklevel=2,
441
- )
442
507
 
443
- return Sources(*args, **kwargs)
508
+ def autocomplete(s):
509
+ """autocomplete with any type of entity"""
510
+ return autocompletes()[s]
444
511
 
445
512
 
446
513
  # aliases
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyalex
3
- Version: 0.13
3
+ Version: 0.15
4
4
  Summary: Python interface to the OpenAlex database
5
5
  Author-email: Jonathan de Bruin <jonathandebruinos@gmail.com>
6
6
  License: MIT
@@ -30,8 +30,6 @@ Requires-Dist: pytest-xdist ; extra == 'test'
30
30
 
31
31
  ![PyPI](https://img.shields.io/pypi/v/pyalex) [![DOI](https://zenodo.org/badge/557541347.svg)](https://zenodo.org/badge/latestdoi/557541347)
32
32
 
33
- [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/J535D165/pyalex/badge)](https://securityscorecards.dev/viewer/?uri=github.com/J535D165/pyalex)
34
-
35
33
 
36
34
  PyAlex is a Python library for [OpenAlex](https://openalex.org/). OpenAlex is
37
35
  an index of hundreds of millions of interconnected scholarly papers, authors,
@@ -49,7 +47,7 @@ The following features of OpenAlex are currently supported by PyAlex:
49
47
  - [x] Select fields
50
48
  - [x] Sample
51
49
  - [x] Pagination
52
- - [ ] [Autocomplete endpoint](https://docs.openalex.org/how-to-use-the-api/get-lists-of-entities/autocomplete-entities)
50
+ - [x] Autocomplete endpoint
53
51
  - [x] N-grams
54
52
  - [x] Authentication
55
53
 
@@ -71,10 +69,10 @@ pip install pyalex
71
69
 
72
70
  ## Getting started
73
71
 
74
- PyAlex offers support for all [Entity Objects](https://docs.openalex.org/api-entities/entities-overview): [Works](https://docs.openalex.org/api-entities/works), [Authors](https://docs.openalex.org/api-entities/authors), [Sources](https://docs.openalex.org/api-entities/sourcese), [Institutions](https://docs.openalex.org/api-entities/institutions), [Concepts](https://docs.openalex.org/api-entities/concepts), [Publishers](https://docs.openalex.org/api-entities/publishers), and [Funders](https://docs.openalex.org/api-entities/funders).
72
+ PyAlex offers support for all [Entity Objects](https://docs.openalex.org/api-entities/entities-overview): [Works](https://docs.openalex.org/api-entities/works), [Authors](https://docs.openalex.org/api-entities/authors), [Sources](https://docs.openalex.org/api-entities/sourcese), [Institutions](https://docs.openalex.org/api-entities/institutions), [Topics](https://docs.openalex.org/api-entities/topics), [Publishers](https://docs.openalex.org/api-entities/publishers), and [Funders](https://docs.openalex.org/api-entities/funders).
75
73
 
76
74
  ```python
77
- from pyalex import Works, Authors, Sources, Institutions, Concepts, Publishers, Funders
75
+ from pyalex import Works, Authors, Sources, Institutions, Topics, Publishers, Funders
78
76
  ```
79
77
 
80
78
  ### The polite pool
@@ -89,9 +87,21 @@ import pyalex
89
87
  pyalex.config.email = "mail@example.com"
90
88
  ```
91
89
 
90
+ ### Max retries
91
+
92
+ By default, PyAlex will raise an error at the first failure when querying the OpenAlex API. You can set `max_retries` to a number higher than 0 to allow PyAlex to retry when an error occurs. `retry_backoff_factor` is related to the delay between two retry, and `retry_http_codes` are the HTTP error codes that should trigger a retry.
93
+
94
+ ```python
95
+ from pyalex import config
96
+
97
+ config.max_retries = 0
98
+ config.retry_backoff_factor = 0.1
99
+ config.retry_http_codes = [429, 500, 503]
100
+ ```
101
+
92
102
  ### Get single entity
93
103
 
94
- Get a single Work, Author, Source, Institution, Concept, Publisher or Funder from OpenAlex by the
104
+ Get a single Work, Author, Source, Institution, Concept, Topic, Publisher or Funder from OpenAlex by the
95
105
  OpenAlex ID, or by DOI or ROR.
96
106
 
97
107
  ```python
@@ -113,7 +123,7 @@ Works()["W2741809807"]["open_access"]
113
123
  {'is_oa': True, 'oa_status': 'gold', 'oa_url': 'https://doi.org/10.7717/peerj.4375'}
114
124
  ```
115
125
 
116
- The previous works also for Authors, Venues, Institutions and Concepts
126
+ The previous works also for Authors, Sources, Institutions, Concepts and Topics
117
127
 
118
128
  ```python
119
129
  Authors()["A2887243803"]
@@ -122,7 +132,7 @@ Authors()["https://orcid.org/0000-0002-4297-0502"] # same
122
132
 
123
133
  #### Get random
124
134
 
125
- Get a [random Work, Author, Source, Institution, Concept, Publisher or Funder](https://docs.openalex.org/how-to-use-the-api/get-single-entities/random-result).
135
+ Get a [random Work, Author, Source, Institution, Concept, Topic, Publisher or Funder](https://docs.openalex.org/how-to-use-the-api/get-single-entities/random-result).
126
136
 
127
137
  ```python
128
138
  Works().random()
@@ -130,6 +140,7 @@ Authors().random()
130
140
  Sources().random()
131
141
  Institutions().random()
132
142
  Concepts().random()
143
+ Topics().random()
133
144
  Publishers().random()
134
145
  Funders().random()
135
146
  ```
@@ -172,7 +183,7 @@ Works().count()
172
183
  For lists of entities, you can return the result as well as the metadata. By default, only the results are returned.
173
184
 
174
185
  ```python
175
- results, meta = Concepts().get(return_meta=True)
186
+ results, meta = Topics().get(return_meta=True)
176
187
  ```
177
188
 
178
189
  ```python
@@ -331,6 +342,32 @@ for page in pager:
331
342
  ```
332
343
 
333
344
 
345
+ ### Autocomplete
346
+
347
+ OpenAlex reference: [Autocomplete entities](https://docs.openalex.org/how-to-use-the-api/get-lists-of-entities/autocomplete-entities).
348
+
349
+ Autocomplete a string:
350
+ ```python
351
+ from pyalex import autocomplete
352
+
353
+ autocomplete("stockholm resilience centre")
354
+ ```
355
+
356
+ Autocomplete a string to get a specific type of entities:
357
+ ```python
358
+ from pyalex import Institutions
359
+
360
+ Institutions().autocomplete("stockholm resilience centre")
361
+ ```
362
+
363
+ You can also use the filters to autocomplete:
364
+ ```python
365
+ from pyalex import Works
366
+
367
+ r = Works().filter(publication_year=2023).autocomplete("planetary boundaries")
368
+ ```
369
+
370
+
334
371
  ### Get N-grams
335
372
 
336
373
  OpenAlex reference: [Get N-grams](https://docs.openalex.org/api-entities/works/get-n-grams).
@@ -404,9 +441,6 @@ pyalex.config.api_key = "<MY_KEY>"
404
441
 
405
442
  ## Alternatives
406
443
 
407
- [Diophila](https://github.com/smierz/diophila) is a nice Python wrapper for OpenAlex. It takes a slightly
408
- different approach, especially interesting to those who don't like the pipe operations.
409
-
410
444
  R users can use the excellent [OpenAlexR](https://github.com/ropensci/openalexR) library.
411
445
 
412
446
  ## License
@@ -0,0 +1,8 @@
1
+ pyalex/__init__.py,sha256=52XK8om6IVD1Yiq_HYOCR6PUY56sPRHutGM03NOrGMQ,1467
2
+ pyalex/_version.py,sha256=poZz4oZoUd1b8WrBvqHJC0SHzGSqhHsFiUSMJDd_Hcw,408
3
+ pyalex/api.py,sha256=IhqRtjy5LtlYffjaHWxRBmKPSIaaXbGqt8qYhKo9YbE,13235
4
+ pyalex-0.15.dist-info/LICENSE,sha256=Mhf5MImRYP06a1EPVJCpkpTstOOEfGajN3T_Fz4izMg,1074
5
+ pyalex-0.15.dist-info/METADATA,sha256=pkDIkcjUnuqePUCdNdXZeRouKh9p16s5g0N0rcYhXxc,13583
6
+ pyalex-0.15.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
7
+ pyalex-0.15.dist-info/top_level.txt,sha256=D0An8hWy9e0xPhTaT6K-yuJKVeVV3bYGxZ6Y-v2WXSU,7
8
+ pyalex-0.15.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.3)
2
+ Generator: setuptools (72.2.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,8 +0,0 @@
1
- pyalex/__init__.py,sha256=UrEW9s9NbULtmmnYUUgfbrDujleV8XQLiMRYGdRnm9M,1137
2
- pyalex/_version.py,sha256=4Ti_UJ2UR2LyAc9zW9aeUVpFeq-DXxy65V2wynOuCi0,408
3
- pyalex/api.py,sha256=vefNV54OG1daTrKHSDq9Jcy4JhiYi0HSP3GsEzA47uk,11633
4
- pyalex-0.13.dist-info/LICENSE,sha256=Mhf5MImRYP06a1EPVJCpkpTstOOEfGajN3T_Fz4izMg,1074
5
- pyalex-0.13.dist-info/METADATA,sha256=vMMbnCUgNUaw99XlqucQZMfZsOZWrXXJOmRpPJsTdSU,12916
6
- pyalex-0.13.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
7
- pyalex-0.13.dist-info/top_level.txt,sha256=D0An8hWy9e0xPhTaT6K-yuJKVeVV3bYGxZ6Y-v2WXSU,7
8
- pyalex-0.13.dist-info/RECORD,,
File without changes