freva-client 2505.0.0__py3-none-any.whl → 2506.0.0__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.

Potentially problematic release.


This version of freva-client might be problematic. Click here for more details.

@@ -7,7 +7,7 @@ import json
7
7
  from enum import Enum
8
8
  from pathlib import Path
9
9
  from tempfile import NamedTemporaryFile
10
- from typing import Dict, List, Literal, Optional, Tuple, Union, cast
10
+ from typing import Dict, List, Optional, Tuple, Union, cast
11
11
 
12
12
  import typer
13
13
  import xarray as xr
@@ -19,16 +19,6 @@ from freva_client.utils import exception_handler, logger
19
19
  from .cli_utils import parse_cli_args, version_callback
20
20
 
21
21
 
22
- def _auth(url: str, token: Optional[str]) -> None:
23
- if token:
24
- auth = Auth()
25
- auth.set_token(
26
- access_token=token, expires=auth.token_expiration_time.timestamp()
27
- )
28
- else:
29
- raise ValueError("`--access-token` is required for authentication.")
30
-
31
-
32
22
  class UniqKeys(str, Enum):
33
23
  """Literal implementation for the cli."""
34
24
 
@@ -65,19 +55,19 @@ class SelectMethod(str, Enum):
65
55
  """
66
56
  examples = {
67
57
  "time": ("2000 to 2012", "2010 to 2020"),
68
- "bbox": ("-10 10 -10 10", "0 5 0 5")
58
+ "bbox": ("-10 10 -10 10", "0 5 0 5"),
69
59
  }
70
60
  descriptions = {
71
61
  "time": {
72
62
  "unit": "time period",
73
63
  "start_end": "start or end period",
74
- "subset": "time period"
64
+ "subset": "time period",
75
65
  },
76
66
  "bbox": {
77
67
  "unit": "spatial extent",
78
68
  "start_end": "any part of the extent",
79
- "subset": "spatial extent"
80
- }
69
+ "subset": "spatial extent",
70
+ },
81
71
  }
82
72
 
83
73
  context_info = descriptions.get(context, descriptions["time"])
@@ -232,13 +222,10 @@ def metadata_search(
232
222
  result = databrowser.metadata_search(
233
223
  *(facets or []),
234
224
  time=time or "",
235
- time_select=cast(Literal["file", "flexible", "strict"], time_select.value),
225
+ time_select=time_select.value,
236
226
  bbox=bbox or None,
237
- bbox_select=cast(Literal["file", "flexible", "strict"], bbox_select.value),
238
- flavour=cast(
239
- Literal["freva", "cmip6", "cmip5", "cordex", "nextgems", "user"],
240
- flavour.value,
241
- ),
227
+ bbox_select=bbox_select.value,
228
+ flavour=flavour.value,
242
229
  host=host,
243
230
  extended_search=extended_search,
244
231
  multiversion=multiversion,
@@ -299,12 +286,14 @@ def data_search(
299
286
  help=SelectMethod.get_help("time"),
300
287
  ),
301
288
  zarr: bool = typer.Option(False, "--zarr", help="Create zarr stream files."),
302
- access_token: Optional[str] = typer.Option(
289
+ token_file: Optional[Path] = typer.Option(
303
290
  None,
304
- "--access-token",
291
+ "--token-file",
292
+ "-t",
305
293
  help=(
306
- "Use this access token for authentication"
307
- " when creating a zarr stream files."
294
+ "Instead of authenticating via code based authentication flow "
295
+ "you can set the path to the json file that contains a "
296
+ "`refresh token` containing a refresh_token key."
308
297
  ),
309
298
  ),
310
299
  time: Optional[str] = typer.Option(
@@ -375,14 +364,11 @@ def data_search(
375
364
  result = databrowser(
376
365
  *(facets or []),
377
366
  time=time or "",
378
- time_select=cast(Literal["file", "flexible", "strict"], time_select),
367
+ time_select=time_select.value,
379
368
  bbox=bbox or None,
380
- bbox_select=cast(Literal["file", "flexible", "strict"], bbox_select),
381
- flavour=cast(
382
- Literal["freva", "cmip6", "cmip5", "cordex", "nextgems", "user"],
383
- flavour.value,
384
- ),
385
- uniq_key=cast(Literal["uri", "file"], uniq_key.value),
369
+ bbox_select=bbox_select.value,
370
+ flavour=flavour.value,
371
+ uniq_key=uniq_key.value,
386
372
  host=host,
387
373
  fail_on_error=False,
388
374
  multiversion=multiversion,
@@ -390,7 +376,7 @@ def data_search(
390
376
  **(parse_cli_args(search_keys or [])),
391
377
  )
392
378
  if zarr:
393
- _auth(result._cfg.auth_url, access_token)
379
+ Auth(token_file).authenticate(host=host, _cli=True)
394
380
  if parse_json:
395
381
  print(json.dumps(sorted(result)))
396
382
  else:
@@ -479,12 +465,13 @@ def intake_catalogue(
479
465
  zarr: bool = typer.Option(
480
466
  False, "--zarr", help="Create zarr stream files, as catalogue targets."
481
467
  ),
482
- access_token: Optional[str] = typer.Option(
468
+ token_file: Optional[Path] = typer.Option(
483
469
  None,
484
- "--access-token",
470
+ "--token-file",
485
471
  help=(
486
- "Use this access token for authentication"
487
- " when creating a zarr based intake catalogue."
472
+ "Instead of authenticating via code based authentication flow "
473
+ "you can set the path to the json file that contains a "
474
+ "`refresh token` containing a refresh_token key."
488
475
  ),
489
476
  ),
490
477
  filename: Optional[Path] = typer.Option(
@@ -526,14 +513,11 @@ def intake_catalogue(
526
513
  result = databrowser(
527
514
  *(facets or []),
528
515
  time=time or "",
529
- time_select=cast(Literal["file", "flexible", "strict"], time_select),
516
+ time_select=time_select.value,
530
517
  bbox=bbox or None,
531
- bbox_select=cast(Literal["file", "flexible", "strict"], bbox_select),
532
- flavour=cast(
533
- Literal["freva", "cmip6", "cmip5", "cordex", "nextgems", "user"],
534
- flavour.value,
535
- ),
536
- uniq_key=cast(Literal["uri", "file"], uniq_key.value),
518
+ bbox_select=bbox_select.value,
519
+ flavour=flavour.value,
520
+ uniq_key=uniq_key.value,
537
521
  host=host,
538
522
  fail_on_error=False,
539
523
  multiversion=multiversion,
@@ -541,7 +525,7 @@ def intake_catalogue(
541
525
  **(parse_cli_args(search_keys or [])),
542
526
  )
543
527
  if zarr:
544
- _auth(result._cfg.auth_url, access_token)
528
+ Auth(token_file).authenticate(host=host, _cli=True)
545
529
  with NamedTemporaryFile(suffix=".json") as temp_f:
546
530
  result._create_intake_catalogue_file(str(filename or temp_f.name))
547
531
  if not filename:
@@ -549,8 +533,7 @@ def intake_catalogue(
549
533
 
550
534
 
551
535
  @databrowser_app.command(
552
- name="stac-catalogue",
553
- help="Create a static STAC catalogue from the search."
536
+ name="stac-catalogue", help="Create a static STAC catalogue from the search."
554
537
  )
555
538
  @exception_handler
556
539
  def stac_catalogue(
@@ -667,14 +650,11 @@ def stac_catalogue(
667
650
  result = databrowser(
668
651
  *(facets or []),
669
652
  time=time or "",
670
- time_select=cast(Literal["file", "flexible", "strict"], time_select),
653
+ time_select=time_select.value,
671
654
  bbox=bbox or None,
672
- bbox_select=cast(Literal["file", "flexible", "strict"], bbox_select),
673
- flavour=cast(
674
- Literal["freva", "cmip6", "cmip5", "cordex", "nextgems", "user"],
675
- flavour.value,
676
- ),
677
- uniq_key=cast(Literal["uri", "file"], uniq_key.value),
655
+ bbox_select=bbox_select.value,
656
+ flavour=flavour.value,
657
+ uniq_key=uniq_key.value,
678
658
  host=host,
679
659
  fail_on_error=False,
680
660
  multiversion=multiversion,
@@ -802,20 +782,19 @@ def count_values(
802
782
  result: Union[int, Dict[str, Dict[str, int]]] = 0
803
783
  search_kws = parse_cli_args(search_keys or [])
804
784
  time = cast(str, time or search_kws.pop("time", ""))
805
- bbox = cast(Optional[Tuple[float, float, float, float]],
806
- bbox or search_kws.pop("bbox", None))
785
+ bbox = cast(
786
+ Optional[Tuple[float, float, float, float]],
787
+ bbox or search_kws.pop("bbox", None),
788
+ )
807
789
  facets = facets or []
808
790
  if detail:
809
791
  result = databrowser.count_values(
810
792
  *facets,
811
793
  time=time or "",
812
- time_select=cast(Literal["file", "flexible", "strict"], time_select),
794
+ time_select=time_select.value,
813
795
  bbox=bbox or None,
814
- bbox_select=cast(Literal["file", "flexible", "strict"], bbox_select),
815
- flavour=cast(
816
- Literal["freva", "cmip6", "cmip5", "cordex", "nextgems", "user"],
817
- flavour.value,
818
- ),
796
+ bbox_select=bbox_select.value,
797
+ flavour=flavour.value,
819
798
  host=host,
820
799
  extended_search=extended_search,
821
800
  multiversion=multiversion,
@@ -827,15 +806,10 @@ def count_values(
827
806
  databrowser(
828
807
  *facets,
829
808
  time=time or "",
830
- time_select=cast(Literal["file", "flexible", "strict"], time_select),
809
+ time_select=time_select.value,
831
810
  bbox=bbox or None,
832
- bbox_select=cast(Literal["file", "flexible", "strict"], bbox_select),
833
- flavour=cast(
834
- Literal[
835
- "freva", "cmip6", "cmip5", "cordex", "nextgems", "user"
836
- ],
837
- flavour.value,
838
- ),
811
+ bbox_select=bbox_select.value,
812
+ flavour=flavour.value,
839
813
  host=host,
840
814
  multiversion=multiversion,
841
815
  fail_on_error=False,
@@ -884,18 +858,21 @@ def user_data_add(
884
858
  "the hostname is read from a config file."
885
859
  ),
886
860
  ),
887
- access_token: Optional[str] = typer.Option(
861
+ token_file: Optional[Path] = typer.Option(
888
862
  None,
889
- "--access-token",
890
- help="Access token for authentication when adding user data.",
863
+ "--token-file",
864
+ help=(
865
+ "Instead of authenticating via code based authentication flow "
866
+ "you can set the path to the json file that contains a "
867
+ "`refresh token` containing a refresh_token key."
868
+ ),
891
869
  ),
892
870
  verbose: int = typer.Option(0, "-v", help="Increase verbosity", count=True),
893
871
  ) -> None:
894
872
  """Add user data into the databrowser."""
895
873
  logger.set_verbosity(verbose)
896
874
  logger.debug("Checking if the user has the right to add data")
897
- result = databrowser(host=host)
898
- _auth(result._cfg.auth_url, access_token)
875
+ Auth(token_file).authenticate(host=host, _cli=True)
899
876
 
900
877
  facet_dict = {}
901
878
  if facets:
@@ -937,18 +914,21 @@ def user_data_delete(
937
914
  "the hostname is read from a config file."
938
915
  ),
939
916
  ),
940
- access_token: Optional[str] = typer.Option(
917
+ token_file: Optional[Path] = typer.Option(
941
918
  None,
942
- "--access-token",
943
- help="Access token for authentication when deleting user data.",
919
+ "--token-file",
920
+ help=(
921
+ "Instead of authenticating via code based authentication flow "
922
+ "you can set the path to the json file that contains a "
923
+ "`refresh token` containing a refresh_token key."
924
+ ),
944
925
  ),
945
926
  verbose: int = typer.Option(0, "-v", help="Increase verbosity", count=True),
946
927
  ) -> None:
947
928
  """Delete user data from the databrowser."""
948
929
  logger.set_verbosity(verbose)
949
930
  logger.debug("Checking if the user has the right to delete data")
950
- result = databrowser(host=host)
951
- _auth(result._cfg.auth_url, access_token)
931
+ Auth(token_file).authenticate(host=host, _cli=True)
952
932
 
953
933
  search_key_dict = {}
954
934
  if search_keys:
freva_client/query.py CHANGED
@@ -44,12 +44,12 @@ class databrowser:
44
44
  Parameters
45
45
  ~~~~~~~~~~
46
46
 
47
- *facets: str
47
+ facets: str
48
48
  If you are not sure about the correct search key's you can use
49
49
  positional arguments to search of any matching entries. For example
50
50
  'era5' would allow you to search for any entries
51
51
  containing era5, regardless of project, product etc.
52
- **search_keys: str
52
+ search_keys: str
53
53
  The search constraints applied in the data search. If not given
54
54
  the whole dataset will be queried.
55
55
  flavour: str, default: freva
@@ -128,7 +128,7 @@ class databrowser:
128
128
  databrowser class using the ``experiment`` search constraint.
129
129
  If you just 'print' the created object you will get a quick overview:
130
130
 
131
- .. execute_code::
131
+ .. code-block:: python
132
132
 
133
133
  from freva_client import databrowser
134
134
  db = databrowser(experiment="cmorph", uniq_key="uri")
@@ -137,7 +137,7 @@ class databrowser:
137
137
  After having created the search object you can acquire different kinds of
138
138
  information like the number of found objects:
139
139
 
140
- .. execute_code::
140
+ .. code-block:: python
141
141
 
142
142
  from freva_client import databrowser
143
143
  db = databrowser(experiment="cmorph", uniq_key="uri")
@@ -146,7 +146,7 @@ class databrowser:
146
146
 
147
147
  Or you can retrieve the combined metadata of the search objects.
148
148
 
149
- .. execute_code::
149
+ .. code-block:: python
150
150
 
151
151
  from freva_client import databrowser
152
152
  db = databrowser(experiment="cmorph", uniq_key="uri")
@@ -154,7 +154,7 @@ class databrowser:
154
154
 
155
155
  Most importantly you can retrieve the locations of all encountered objects
156
156
 
157
- .. execute_code::
157
+ .. code-block:: python
158
158
 
159
159
  from freva_client import databrowser
160
160
  db = databrowser(experiment="cmorph", uniq_key="uri")
@@ -167,7 +167,7 @@ class databrowser:
167
167
  You can also set a different flavour, for example according to cmip6
168
168
  standard:
169
169
 
170
- .. execute_code::
170
+ .. code-block:: python
171
171
 
172
172
  from freva_client import databrowser
173
173
  db = databrowser(flavour="cmip6", experiment_id="cmorph")
@@ -179,7 +179,7 @@ class databrowser:
179
179
  for getting all ocean reanalysis datasets you can apply the 'reana*'
180
180
  search key as a positional argument:
181
181
 
182
- .. execute_code::
182
+ .. code-block:: python
183
183
 
184
184
  from freva_client import databrowser
185
185
  db = databrowser("reana*", realm="ocean", flavour="cmip6")
@@ -195,17 +195,15 @@ class databrowser:
195
195
  before you are able to access the data. Refer also to the
196
196
  :py:meth:`freva_client.authenticate` method.
197
197
 
198
- .. execute_code::
198
+ .. code-block:: python
199
199
 
200
- from freva_client import authenticate, databrowser
201
- token_info = authenticate(username="janedoe")
200
+ from freva_client import databrowser
202
201
  db = databrowser(dataset="cmip6-fs", stream_zarr=True)
203
202
  zarr_files = list(db)
204
- print(zarr_files)
205
203
 
206
204
  After you have created the paths to the zarr files you can open them
207
205
 
208
- ::
206
+ .. code-block:: python
209
207
 
210
208
  import xarray as xr
211
209
  dset = xr.open_dataset(
@@ -291,7 +289,7 @@ class databrowser:
291
289
  headers = {}
292
290
  if self._stream_zarr:
293
291
  query_url = self._cfg.zarr_loader_url
294
- token = self._auth.check_authentication(auth_url=self._cfg.auth_url)
292
+ token = self._auth.authenticate(config=self._cfg)
295
293
  headers = {"Authorization": f"Bearer {token['access_token']}"}
296
294
  result = self._request("GET", query_url, headers=headers, stream=True)
297
295
  if result is not None:
@@ -350,7 +348,7 @@ class databrowser:
350
348
 
351
349
  Example
352
350
  ~~~~~~~
353
- .. execute_code::
351
+ .. code-block:: python
354
352
 
355
353
  from freva_client import databrowser
356
354
  print(len(databrowser(experiment="cmorph")))
@@ -367,7 +365,7 @@ class databrowser:
367
365
  kwargs: Dict[str, Any] = {"stream": True}
368
366
  url = self._cfg.intake_url
369
367
  if self._stream_zarr:
370
- token = self._auth.check_authentication(auth_url=self._cfg.auth_url)
368
+ token = self._auth.authenticate(config=self._cfg)
371
369
  url = self._cfg.zarr_loader_url
372
370
  kwargs["headers"] = {
373
371
  "Authorization": f"Bearer {token['access_token']}"
@@ -407,7 +405,7 @@ class databrowser:
407
405
  Let's create an intake-esm catalogue that points points allows for
408
406
  streaming the target data as zarr:
409
407
 
410
- .. execute_code::
408
+ .. code-block:: python
411
409
 
412
410
  from freva_client import databrowser
413
411
  db = databrowser(dataset="cmip6-hsm", stream_zarr=True)
@@ -435,7 +433,7 @@ class databrowser:
435
433
  The filename of the STAC catalogue. If not given
436
434
  or doesn't exist the STAC catalogue will be saved
437
435
  to the current working directory.
438
- **kwargs: Any
436
+ kwargs: Any
439
437
  Additional keyword arguments to be passed to the request.
440
438
 
441
439
  Returns
@@ -451,7 +449,7 @@ class databrowser:
451
449
  ~~~~~~~
452
450
  Let's create a static STAC catalogue:
453
451
 
454
- .. execute_code::
452
+ .. code-block:: python
455
453
 
456
454
  from tempfile import mktemp
457
455
  temp_path = mktemp(suffix=".zip")
@@ -521,7 +519,7 @@ class databrowser:
521
519
  Parameters
522
520
  ~~~~~~~~~~
523
521
 
524
- *facets: str
522
+ facets: str
525
523
  If you are not sure about the correct search key's you can use
526
524
  positional arguments to search of any matching entries. For example
527
525
  'era5' would allow you to search for any entries
@@ -574,7 +572,7 @@ class databrowser:
574
572
  fail_on_error: bool, default: False
575
573
  Make the call fail if the connection to the databrowser could not
576
574
  be established.
577
- **search_keys: str
575
+ search_keys: str
578
576
  The search constraints to be applied in the data search. If not given
579
577
  the whole dataset will be queried.
580
578
 
@@ -587,12 +585,12 @@ class databrowser:
587
585
  Example
588
586
  ~~~~~~~
589
587
 
590
- .. execute_code::
588
+ .. code-block:: python
591
589
 
592
590
  from freva_client import databrowser
593
591
  print(databrowser.count_values(experiment="cmorph"))
594
592
 
595
- .. execute_code::
593
+ .. code-block:: python
596
594
 
597
595
  from freva_client import databrowser
598
596
  print(databrowser.count_values("model"))
@@ -602,7 +600,7 @@ class databrowser:
602
600
  example for getting all ocean reanalysis datasets you can apply the
603
601
  'reana*' search key as a positional argument:
604
602
 
605
- .. execute_code::
603
+ .. code-block:: python
606
604
 
607
605
  from freva_client import databrowser
608
606
  print(databrowser.count_values("reana*", realm="ocean", flavour="cmip6"))
@@ -643,7 +641,7 @@ class databrowser:
643
641
 
644
642
  Reverse search: retrieving meta data from a known file
645
643
 
646
- .. execute_code::
644
+ .. code-block:: python
647
645
 
648
646
  from freva_client import databrowser
649
647
  db = databrowser(uri="slk:///arch/*/CPC/*")
@@ -681,7 +679,7 @@ class databrowser:
681
679
  Parameters
682
680
  ~~~~~~~~~~
683
681
 
684
- *facets: str
682
+ facets: str
685
683
  If you are not sure about the correct search key's you can use
686
684
  positional arguments to search of any matching entries. For example
687
685
  'era5' would allow you to search for any entries
@@ -734,7 +732,7 @@ class databrowser:
734
732
  fail_on_error: bool, default: False
735
733
  Make the call fail if the connection to the databrowser could not
736
734
  be established.
737
- **search_keys: str, list[str]
735
+ search_keys: str, list[str]
738
736
  The facets to be applied in the data search. If not given
739
737
  the whole dataset will be queried.
740
738
 
@@ -747,7 +745,7 @@ class databrowser:
747
745
  Example
748
746
  ~~~~~~~
749
747
 
750
- .. execute_code::
748
+ .. code-block:: python
751
749
 
752
750
  from freva_client import databrowser
753
751
  all_facets = databrowser.metadata_search(project='obs*')
@@ -755,7 +753,7 @@ class databrowser:
755
753
 
756
754
  You can also search for all metadata matching a search string:
757
755
 
758
- .. execute_code::
756
+ .. code-block:: python
759
757
 
760
758
  from freva_client import databrowser
761
759
  spec_facets = databrowser.metadata_search("obs*")
@@ -763,7 +761,7 @@ class databrowser:
763
761
 
764
762
  Get all models that have a given time step:
765
763
 
766
- .. execute_code::
764
+ .. code-block:: python
767
765
 
768
766
  from freva_client import databrowser
769
767
  model = databrowser.metadata_search(
@@ -774,7 +772,7 @@ class databrowser:
774
772
 
775
773
  Reverse search: retrieving meta data from a known file
776
774
 
777
- .. execute_code::
775
+ .. code-block:: python
778
776
 
779
777
  from freva_client import databrowser
780
778
  res = databrowser.metadata_search(file="/arch/*CPC/*")
@@ -785,7 +783,7 @@ class databrowser:
785
783
  example for getting all ocean reanalysis datasets you can apply the
786
784
  'reana*' search key as a positional argument:
787
785
 
788
- .. execute_code::
786
+ .. code-block:: python
789
787
 
790
788
  from freva_client import databrowser
791
789
  print(databrowser.metadata_search("reana*", realm="ocean", flavour="cmip6"))
@@ -795,7 +793,7 @@ class databrowser:
795
793
  version from a multi versioned datasets requires the ``multiversion``
796
794
  flag in combination with the ``version`` special attribute:
797
795
 
798
- .. execute_code::
796
+ .. code-block:: python
799
797
 
800
798
  from freva_client import databrowser
801
799
  res = databrowser.metadata_search(dataset="cmip6-fs",
@@ -852,7 +850,7 @@ class databrowser:
852
850
  Example
853
851
  ~~~~~~~
854
852
 
855
- .. execute_code::
853
+ .. code-block:: python
856
854
 
857
855
  from freva_client import databrowser
858
856
  print(databrowser.overview())
@@ -869,7 +867,7 @@ class databrowser:
869
867
  Example
870
868
  ~~~~~~~
871
869
 
872
- .. execute_code::
870
+ .. code-block:: python
873
871
 
874
872
  from freva_client import databrowser
875
873
  db = databrowser()
@@ -948,11 +946,10 @@ class databrowser:
948
946
 
949
947
  Adding user data:
950
948
 
951
- .. execute_code::
949
+ .. code-block:: python
952
950
 
953
- from freva_client import authenticate, databrowser
951
+ from freva_client import databrowser
954
952
  import xarray as xr
955
- token_info = authenticate(username="janedoe")
956
953
  filenames = (
957
954
  "../freva-rest/src/freva_rest/databrowser_api/mock/data/model/regional/cordex/output/EUR-11/"
958
955
  "GERICS/NCC-NorESM1-M/rcp85/r1i1p1/GERICS-REMO2015/v1/3hr/pr/v20181212/*.nc"
@@ -971,10 +968,9 @@ class databrowser:
971
968
 
972
969
  Deleting user data:
973
970
 
974
- .. execute_code::
971
+ .. code-block:: python
975
972
 
976
- from freva_client import authenticate, databrowser
977
- token_info = authenticate(username="janedoe")
973
+ from freva_client import databrowser
978
974
  databrowser.userdata(
979
975
  action="delete",
980
976
  metadata={"project": "cmip5", "experiment": "myFavExp"}
@@ -987,7 +983,7 @@ class databrowser:
987
983
  userdata_items = userdata_items or []
988
984
  metadata = metadata or {}
989
985
  url = f"{this._cfg.userdata_url}"
990
- token = this._auth.check_authentication(auth_url=this._cfg.auth_url)
986
+ token = this._auth.authenticate(config=this._cfg)
991
987
  headers = {"Authorization": f"Bearer {token['access_token']}"}
992
988
  payload_metadata: dict[str, Collection[Collection[str]]] = {}
993
989