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 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.17'
21
+ __version_tuple__ = version_tuple = (0, 17)
pyalex/api.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  import warnings
3
3
  from urllib.parse import quote_plus
4
+ from urllib.parse import urlunparse
4
5
 
5
6
  import requests
6
7
  from requests.auth import AuthBase
@@ -13,6 +14,26 @@ except ImportError:
13
14
 
14
15
 
15
16
  class AlexConfig(dict):
17
+ """Configuration class for OpenAlex API.
18
+
19
+ Attributes
20
+ ----------
21
+ email : str
22
+ Email address for API requests.
23
+ api_key : str
24
+ API key for authentication.
25
+ user_agent : str
26
+ User agent string for API requests.
27
+ openalex_url : str
28
+ Base URL for OpenAlex API.
29
+ max_retries : int
30
+ Maximum number of retries for API requests.
31
+ retry_backoff_factor : float
32
+ Backoff factor for retries.
33
+ retry_http_codes : list
34
+ List of HTTP status codes to retry on.
35
+ """
36
+
16
37
  def __getattr__(self, key):
17
38
  return super().__getitem__(key)
18
39
 
@@ -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
- results, meta = self.endpoint_class.get(
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(results) > 0:
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(results)
376
+ self.n = self.n + len(r)
222
377
 
223
- return results
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
- arguments:
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'. " "Did you mean 'group_by'?"
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
- return self._get_from_url(
288
- f"{self._full_collection_name()}/{_quote_oa_value(record_id)}",
289
- return_meta=False,
290
- )
465
+ else:
466
+ return ""
291
467
 
292
468
  @property
293
469
  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)}")
470
+ """Return the URL for the API request.
307
471
 
308
- if l_params:
309
- return "{}?{}".format(self._full_collection_name(), "&".join(l_params))
472
+ The URL doens't include the identification, authentication,
473
+ and pagination parameters.
310
474
 
311
- return self._full_collection_name()
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
- _, m = self.get(return_meta=True, per_page=1)
493
+ """Get the count of results.
315
494
 
316
- return m["count"]
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, return_meta=False):
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
- results = res_json["group_by"]
516
+ return OpenAlexResponseList(
517
+ res_json["group_by"], res_json["meta"], self.resource_class
518
+ )
335
519
  elif "results" in res_json:
336
- results = [self.resource_class(ent) for ent in res_json["results"]]
520
+ return OpenAlexResponseList(
521
+ res_json["results"], res_json["meta"], self.resource_class
522
+ )
337
523
  elif "id" in res_json:
338
- results = self.resource_class(res_json)
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._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)
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, **kwargs):
436
- """autocomplete the string s, for a specific type of entity"""
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
- return self.get(**kwargs)
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
- # return result and metadata
885
+ resp_list = OpenAlexResponseList(results["ngrams"], results["meta"])
886
+
460
887
  if return_meta:
461
- return results["ngrams"], results["meta"]
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 results["ngrams"]
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
- f"{config.openalex_url}/autocomplete?q={key}", return_meta=False
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
- """autocomplete with any type of entity"""
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.16
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
- 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=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,,
@@ -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