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 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
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.16'
21
- __version_tuple__ = version_tuple = (0, 16)
20
+ __version__ = version = '0.18'
21
+ __version_tuple__ = version_tuple = (0, 18)
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
- pagination_params = {"cursor": self._next_value}
359
+ self.endpoint_class._add_params("cursor", self._next_value)
203
360
  elif self.method == "page":
204
- pagination_params = {"page": self._next_value}
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
- results, meta = self.endpoint_class.get(
209
- return_meta=True, per_page=self.per_page, **pagination_params
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(results) > 0:
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(results)
385
+ self.n = self.n + len(r)
222
386
 
223
- return results
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
- arguments:
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'. " "Did you mean 'group_by'?"
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
- return self._get_from_url(
288
- f"{self._full_collection_name()}/{_quote_oa_value(record_id)}",
289
- return_meta=False,
290
- )
474
+ else:
475
+ return ""
291
476
 
292
477
  @property
293
478
  def url(self):
294
- if not self.params:
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)}")
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 l_params:
309
- return "{}?{}".format(self._full_collection_name(), "&".join(l_params))
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 self._full_collection_name()
499
+ return urlunparse(("https", "api.openalex.org", path, "", query, ""))
312
500
 
313
501
  def count(self):
314
- _, m = self.get(return_meta=True, per_page=1)
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
- return m["count"]
511
+ def _get_from_url(self, url, session=None):
512
+ if session is None:
513
+ session = _get_requests_session()
317
514
 
318
- def _get_from_url(self, url, return_meta=False):
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
- results = res_json["group_by"]
528
+ return OpenAlexResponseList(
529
+ res_json["group_by"], res_json["meta"], self.resource_class
530
+ )
335
531
  elif "results" in res_json:
336
- results = [self.resource_class(ent) for ent in res_json["results"]]
532
+ return OpenAlexResponseList(
533
+ res_json["results"], res_json["meta"], self.resource_class
534
+ )
337
535
  elif "id" in res_json:
338
- results = self.resource_class(res_json)
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 (per_page < 1 or per_page > 200):
350
- raise ValueError("per_page should be a number between 1 and 200.")
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._add_params("per-page", per_page)
353
- self._add_params("page", page)
354
- self._add_params("cursor", cursor)
355
- return self._get_from_url(self.url, return_meta=return_meta)
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, **kwargs):
436
- """autocomplete the string s, for a specific type of entity"""
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
- return self.get(**kwargs)
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
- # return result and metadata
899
+ resp_list = OpenAlexResponseList(results["ngrams"], results["meta"])
900
+
460
901
  if return_meta:
461
- return results["ngrams"], results["meta"]
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 results["ngrams"]
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
- f"{config.openalex_url}/autocomplete?q={key}", return_meta=False
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
- """autocomplete with any type of entity"""
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.16
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
- results, meta = Topics().get(return_meta=True)
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.1)
2
+ Generator: setuptools (76.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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