-
Notifications
You must be signed in to change notification settings - Fork 34
Expand file tree
/
Copy pathconfiguration.py
More file actions
786 lines (679 loc) · 25.6 KB
/
configuration.py
File metadata and controls
786 lines (679 loc) · 25.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
"""
Python SDK for OpenFGA
API version: 1.x
Website: https://openfga.dev
Documentation: https://openfga.dev/docs
Support: https://openfga.dev/community
License: [Apache-2.0](https://github.com/openfga/python-sdk/blob/main/LICENSE)
NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT.
"""
import copy
import http
import logging
import sys
import urllib
import urllib3
from openfga_sdk.exceptions import ApiValueError, FgaValidationException
from openfga_sdk.telemetry.attributes import TelemetryAttribute
from openfga_sdk.telemetry.configuration import (
TelemetryConfiguration,
TelemetryConfigurationType,
TelemetryMetricConfiguration,
TelemetryMetricsConfiguration,
)
from openfga_sdk.telemetry.counters import TelemetryCounter
from openfga_sdk.telemetry.histograms import TelemetryHistogram
from openfga_sdk.validation import is_well_formed_ulid_string
class RetryParams:
"""NOTE: This class is auto generated by OpenAPI Generator
Ref: https://openapi-generator.tech
Do not edit the class manually.
Retry configuration in case of HTTP too many request
:param max_retry: Maximum number of retry
:param min_wait_in_ms: Minimum wait (in ms) between retry
:param max_wait_in_sec: Maximum wait (in seconds) between retry
"""
def __init__(self, max_retry=3, min_wait_in_ms=100, max_wait_in_sec=120):
self._max_retry = max_retry
self._min_wait_in_ms = min_wait_in_ms
self._max_wait_in_sec = max_wait_in_sec
@property
def max_retry(self):
"""
Return the maximum number of retry
"""
if self._max_retry > 15:
raise FgaValidationException(
"RetryParams.max_retry exceeds maximum allowed limit of 15"
)
return self._max_retry
@max_retry.setter
def max_retry(self, value):
"""
Update the maximum number of retry
"""
if not isinstance(value, int) or value < 0:
raise FgaValidationException(
"RetryParams.max_retry must be an integer greater than or equal to 0"
)
if value > 15:
raise FgaValidationException(
"RetryParams.max_retry exceeds maximum allowed limit of 15"
)
self._max_retry = value
@property
def min_wait_in_ms(self):
"""
Return the minimum wait (in ms) in between retry
"""
return self._min_wait_in_ms
@min_wait_in_ms.setter
def min_wait_in_ms(self, value):
"""
Update the minimum wait (in ms) in between retry
"""
if not isinstance(value, int) or value < 0:
raise FgaValidationException(
"RetryParams.min_wait_in_ms must be an integer greater than or equal to 0"
)
self._min_wait_in_ms = value
@property
def max_wait_in_sec(self):
"""
Return the maximum allowed wait (in seconds) in between retry
"""
return self._max_wait_in_sec
@max_wait_in_sec.setter
def max_wait_in_sec(self, value):
"""
Update the maximum allowed wait (in seconds) in between retry
"""
if not isinstance(value, int) or value < 0:
raise FgaValidationException(
"RetryParams.max_wait_in_sec must be an integer greater than or equal to 0"
)
self._max_wait_in_sec = value
class Configuration:
"""NOTE: This class is auto generated by OpenAPI Generator
Ref: https://openapi-generator.tech
Do not edit the class manually.
:param api_scheme: Whether connection is 'https' or 'http'. Default as 'https'
.. deprecated:: 0.4.1
Use `api_url` instead.
:param api_host: Base url
.. deprecated:: 0.4.1
Use `api_url` instead.
:param store_id: ID of store for API
:param credentials: Configuration for obtaining authentication credential
:param retry_params: Retry parameters upon HTTP too many request
:param api_key: Dict to store API key(s).
Each entry in the dict specifies an API key.
The dict key is the name of the security scheme in the OAS specification.
The dict value is the API key secret.
:param api_key_prefix: Dict to store API prefix (e.g. Bearer)
The dict key is the name of the security scheme in the OAS specification.
The dict value is an API key prefix when generating the auth data.
:param username: Username for HTTP basic authentication
:param password: Password for HTTP basic authentication
:param discard_unknown_keys: Boolean value indicating whether to discard
unknown properties. A server may send a response that includes additional
properties that are not known by the client in the following scenarios:
1. The OpenAPI document is incomplete, i.e. it does not match the server
implementation.
2. The client was generated using an older version of the OpenAPI document
and the server has been upgraded since then.
If a schema in the OpenAPI document defines the additionalProperties attribute,
then all undeclared properties received by the server are injected into the
additional properties map. In that case, there are undeclared properties, and
nothing to discard.
:param server_index: Index to servers configuration.
:param server_variables: Mapping with string values to replace variables in
templated server configuration. The validation of enums is performed for
variables with defined enum values before.
:param server_operation_index: Mapping from operation ID to an index to server
configuration.
:param server_operation_variables: Mapping from operation ID to a mapping with
string values to replace variables in templated server configuration.
The validation of enums is performed for variables with defined enum values before.
:param ssl_ca_cert: str - the path to a file of concatenated CA certificates
in PEM format
:param api_url: str - the URL of the FGA server
:param timeout_millisec: int | None - the default timeout in milliseconds for requests
"""
_default = None
def __init__(
self,
api_scheme="https",
api_host=None,
store_id=None,
credentials=None,
retry_params=None,
api_key=None,
api_key_prefix=None,
username=None,
password=None,
discard_unknown_keys=False,
server_index=None,
server_variables=None,
server_operation_index=None,
server_operation_variables=None,
ssl_ca_cert=None,
api_url=None, # TODO: restructure when removing api_scheme/api_host
telemetry: (
dict[
TelemetryConfigurationType | str,
TelemetryMetricsConfiguration
| dict[
TelemetryHistogram | TelemetryCounter | str,
TelemetryMetricConfiguration
| dict[TelemetryAttribute | str, bool]
| None,
]
| None,
]
| None
) = None,
timeout_millisec: int | None = None,
headers: dict[str, str] | None = None,
):
"""Constructor"""
self._url = api_url
self._scheme = api_scheme
self._base_path = api_host
self._store_id = store_id
self._credentials = credentials
if retry_params is not None:
self._retry_params = retry_params
else:
# use the default parameters
self._retry_params = RetryParams()
self._timeout_millisec = timeout_millisec or 5000 * 60
"""Default Base url
"""
self.server_index = 0
self.server_operation_index = server_operation_index or {}
"""Default server index
"""
self.server_variables = server_variables or {}
self.server_operation_variables = server_operation_variables or {}
"""Default server variables
"""
self.temp_folder_path = None
"""Temp file folder for downloading files
"""
# Authentication Settings
self.api_key = {}
if api_key:
self.api_key = api_key
"""dict to store API key(s)
"""
self.api_key_prefix = {}
if api_key_prefix:
self.api_key_prefix = api_key_prefix
"""dict to store API prefix (e.g. Bearer)
"""
self.refresh_api_key_hook = None
"""function hook to refresh API key if expired
"""
self.username = username
"""Username for HTTP basic authentication
"""
self.password = password
"""Password for HTTP basic authentication
"""
self.discard_unknown_keys = discard_unknown_keys
self.logger = {}
"""Logging Settings
"""
self.logger["package_logger"] = logging.getLogger("openfga_sdk")
self.logger["urllib3_logger"] = logging.getLogger("urllib3")
self.logger_format = "%(asctime)s %(levelname)s %(message)s"
"""Log format
"""
self.logger_stream_handler = None
"""Log stream handler
"""
self.logger_file_handler = None
"""Log file handler
"""
self.logger_file = None
"""Debug file location
"""
self.debug = False
"""Debug switch
"""
self.verify_ssl = True
"""SSL/TLS verification
Set this to false to skip verifying SSL certificate when calling API
from https server.
"""
self.ssl_ca_cert = ssl_ca_cert
"""Set this to customize the certificate file to verify the peer.
"""
self.cert_file = None
"""client certificate file
"""
self.key_file = None
"""client key file
"""
self.assert_hostname = None
"""Set this to True/False to enable/disable SSL hostname verification.
"""
self.connection_pool_maxsize = 100
"""This value is passed to the aiohttp to limit simultaneous connections.
Default values is 100, None means no-limit.
"""
self.proxy = None
"""Proxy URL
"""
self.proxy_headers = None
"""Proxy headers
"""
self.safe_chars_for_path_param = ""
"""Safe chars for path_param
"""
self.retries = None
"""Adding retries to override urllib3 default value 3
"""
# Enable client side validation
self.client_side_validation = True
self.socket_options = None
"""Options to pass down to the underlying urllib3 socket
"""
self._telemetry: TelemetryConfiguration | None = None
if telemetry is None:
self._telemetry = TelemetryConfiguration(
TelemetryConfiguration.getSdkDefaults()
)
elif isinstance(telemetry, dict):
self._telemetry = TelemetryConfiguration(telemetry)
elif isinstance(telemetry, TelemetryConfiguration):
self._telemetry = telemetry
"""Telemetry configuration
"""
self._headers = headers or {}
"""Default headers to be sent with every request
"""
def __deepcopy__(self, memo):
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
for k, v in self.__dict__.items():
if k not in ("logger", "logger_file_handler"):
setattr(result, k, copy.deepcopy(v, memo))
# shallow copy of loggers
result.logger = copy.copy(self.logger)
# use setters to configure loggers
result.logger_file = self.logger_file
result.debug = self.debug
return result
@classmethod
def set_default(cls, default):
"""Set default instance of configuration.
It stores default configuration, which can be
returned by get_default_copy method.
:param default: object of Configuration
"""
cls._default = copy.deepcopy(default)
@classmethod
def get_default_copy(cls):
"""Return new instance of configuration.
This method returns newly created, based on default constructor,
object of Configuration class or returns a copy of default
configuration passed by the set_default method.
:return: The configuration object.
"""
if cls._default is not None:
return copy.deepcopy(cls._default)
return Configuration()
@property
def logger_file(self):
"""The logger file.
If the logger_file is None, then add stream handler and remove file
handler. Otherwise, add file handler and remove stream handler.
:param value: The logger_file path.
:type: str
"""
return self.__logger_file
@logger_file.setter
def logger_file(self, value):
"""The logger file.
If the logger_file is None, then add stream handler and remove file
handler. Otherwise, add file handler and remove stream handler.
:param value: The logger_file path.
:type: str
"""
self.__logger_file = value
if self.__logger_file:
# If set logging file,
# then add file handler and remove stream handler.
self.logger_file_handler = logging.FileHandler(self.__logger_file)
self.logger_file_handler.setFormatter(self.logger_formatter)
for _, logger in self.logger.items():
logger.addHandler(self.logger_file_handler)
@property
def debug(self):
"""Debug status
:param value: The debug status, True or False.
:type: bool
"""
return self.__debug
@debug.setter
def debug(self, value):
"""Debug status
:param value: The debug status, True or False.
:type: bool
"""
self.__debug = value
if self.__debug:
# if debug status is True, turn on debug logging
for _, logger in self.logger.items():
logger.setLevel(logging.DEBUG)
# turn on httplib debug
http.client.HTTPConnection.set_debuglevel(http.client.HTTPConnection, 1)
else:
# if debug status is False, turn off debug logging,
# setting log level to default `logging.WARNING`
for _, logger in self.logger.items():
logger.setLevel(logging.WARNING)
# turn off httplib debug
http.client.HTTPConnection.set_debuglevel(http.client.HTTPConnection, 0)
@property
def logger_format(self):
"""The logger format.
The logger_formatter will be updated when sets logger_format.
:param value: The format string.
:type: str
"""
return self.__logger_format
@logger_format.setter
def logger_format(self, value):
"""The logger format.
The logger_formatter will be updated when sets logger_format.
:param value: The format string.
:type: str
"""
self.__logger_format = value
self.logger_formatter = logging.Formatter(self.__logger_format)
@property
def telemetry(self) -> TelemetryConfiguration | None:
"""Return the telemetry configuration"""
return self._telemetry
@telemetry.setter
def telemetry(
self,
value: (
TelemetryConfiguration
| dict[
TelemetryConfigurationType | str,
TelemetryMetricsConfiguration
| dict[
TelemetryHistogram | TelemetryCounter | str,
TelemetryMetricConfiguration
| dict[TelemetryAttribute | str, bool]
| None,
]
| None,
]
| None
),
) -> None:
"""Set the telemetry configuration"""
if value is not None:
if isinstance(value, TelemetryConfiguration):
self._telemetry = value
return
if isinstance(value, dict):
self._telemetry = TelemetryConfiguration(value)
return
self._telemetry = None
def get_api_key_with_prefix(self, identifier, alias=None):
"""Gets API key (with prefix if set).
:param identifier: The identifier of apiKey.
:param alias: The alternative identifier of apiKey.
:return: The token for api key authentication.
"""
if self.refresh_api_key_hook is not None:
self.refresh_api_key_hook(self)
key = self.api_key.get(
identifier, self.api_key.get(alias) if alias is not None else None
)
if key:
prefix = self.api_key_prefix.get(identifier)
if prefix:
return f"{prefix} {key}"
else:
return key
def get_basic_auth_token(self):
"""Gets HTTP basic authentication header (string).
:return: The token for basic HTTP authentication.
"""
username = ""
if self.username is not None:
username = self.username
password = ""
if self.password is not None:
password = self.password
return urllib3.util.make_headers(basic_auth=username + ":" + password).get(
"authorization"
)
def auth_settings(self):
"""Gets Auth Settings dict for api client.
:return: The Auth Settings information dict.
"""
auth = {}
return auth
def to_debug_report(self):
"""Gets the essential information for debugging.
:return: The report for debugging.
"""
return (
"Python SDK Debug Report:\n"
f"OS: {sys.platform}\n"
f"Python Version: {sys.version}\n"
"Version of the API: 1.x\n"
"SDK Package Version: 0.9.6"
)
def get_host_settings(self):
"""Gets an array of host settings
:return: An array of host settings
"""
return [
{
"url": "",
"description": "No description provided",
}
]
def get_host_from_settings(self, index, variables=None, servers=None):
"""Gets host URL based on the index and variables
:param index: array index of the host settings
:param variables: hash of variable and the corresponding value
:param servers: an array of host settings or None
:return: URL based on host settings
"""
if index is None:
return self._base_path
variables = {} if variables is None else variables
servers = self.get_host_settings() if servers is None else servers
try:
server = servers[index]
except IndexError:
raise ValueError(
f"Invalid index {index} when selecting the host settings. "
f"Must be less than {len(servers)}"
)
url = server["url"]
# go through variables and replace placeholders
for variable_name, variable in server.get("variables", {}).items():
used_value = variables.get(variable_name, variable["default_value"])
if "enum_values" in variable and used_value not in variable["enum_values"]:
raise ValueError(
"The variable `{}` in the host URL has invalid value "
"{}. Must be {}.".format(
variable_name, variables[variable_name], variable["enum_values"]
)
)
url = url.replace("{" + variable_name + "}", used_value)
return url
def is_valid(self):
"""
Verify the configuration is valid.
Note that we are only doing basic validation to ensure input is sane.
"""
combined_url = self.api_url
if self.api_url is None:
if self.api_host is None or self.api_host == "":
raise FgaValidationException("api_host is required but not configured.")
if self.api_scheme is None or self.api_scheme == "":
raise FgaValidationException(
"api_scheme is required but not configured."
)
combined_url = self.api_scheme + "://" + self.api_host
parsed_url = None
try:
parsed_url = urllib.parse.urlparse(combined_url)
except ValueError:
if self.api_url is None:
raise ApiValueError(
f"Either api_scheme `{self.api_scheme}` or api_host `{self.api_host}` is invalid"
)
else:
raise ApiValueError(f"api_url `{self.api_url}` is invalid")
if self.api_url is None:
if parsed_url.scheme != "http" and parsed_url.scheme != "https":
raise ApiValueError(
f"api_scheme `{self.api_scheme}` must be either `http` or `https`"
)
if parsed_url.netloc == "":
raise ApiValueError(f"api_host `{self.api_host}` is invalid")
if parsed_url.path != "":
raise ApiValueError(
f"api_host `{self.api_scheme}` is not expected to have path specified"
)
if parsed_url.query != "":
raise ApiValueError(
f"api_host `{self.api_scheme}` is not expected to have query specified"
)
if (
self.store_id is not None
and self.store_id != ""
and is_well_formed_ulid_string(self.store_id) is False
):
raise FgaValidationException(
f"store_id ('{self.store_id}') is not in a valid ulid format"
)
if self._credentials is not None:
self._credentials.validate_credentials_config()
if self._timeout_millisec is not None:
if not isinstance(self._timeout_millisec, int):
raise FgaValidationException(
f"timeout_millisec unexpected type {self._timeout_millisec}"
)
ten_minutes = 10000 * 60
if self._timeout_millisec < 0 or self._timeout_millisec > ten_minutes:
raise FgaValidationException(
f"timeout_millisec not within reasonable range (0,60000), {self._timeout_millisec}"
)
if self._headers is not None:
for key, value in self._headers.items():
if not isinstance(key, str):
raise FgaValidationException(
f"header keys must be strings, got {type(key).__name__} for key {key}"
)
if not isinstance(value, str):
raise FgaValidationException(
f"header values must be strings, got {type(value).__name__} for key '{key}'"
)
@property
def api_scheme(self):
"""Return connection is https or http."""
return self._scheme
@api_scheme.setter
def api_scheme(self, value):
"""Update connection scheme (https or http)."""
if value is not None and value not in ["https", "http"]:
raise FgaValidationException(
f"api_scheme `{value}` must be either `http` or `https`"
)
self._scheme = value
@property
def api_host(self):
"""Return api_host."""
return self._base_path
@api_host.setter
def api_host(self, value):
"""Update configured host"""
self._base_path = value
@property
def api_url(self):
"""Return api_url"""
return self._url
@api_url.setter
def api_url(self, value):
"""Update configured api_url"""
self._url = value
@property
def store_id(self):
"""Return store id."""
return self._store_id
@store_id.setter
def store_id(self, value):
"""Update store id."""
self._store_id = value
@property
def credentials(self):
"""
Return configured credentials
"""
return self._credentials
@credentials.setter
def credentials(self, value):
"""Update credentials"""
self._credentials = value
@property
def retry_params(self):
"""
Return retry parameters
"""
return self._retry_params
@retry_params.setter
def retry_params(self, value):
"""
Update retry parameters
"""
self._retry_params = value
@property
def timeout_millisec(self):
"""
Return timeout milliseconds
"""
return self._timeout_millisec
@timeout_millisec.setter
def timeout_millisec(self, value):
"""
Update timeout milliseconds
"""
self._timeout_millisec = value
@property
def headers(self) -> dict[str, str]:
"""
Return default headers to be sent with every request.
Headers are key-value pairs that will be included in all API requests.
Common use cases include correlation IDs, API versioning, and tenant identification.
"""
return self._headers
@headers.setter
def headers(self, value: dict[str, str] | None) -> None:
"""
Update default headers to be sent with every request.
Args:
value: Dictionary of header names to values, or None to clear headers.
Both keys and values must be strings.
Raises:
FgaValidationException: If value is not a dict or None.
"""
if value is not None and not isinstance(value, dict):
raise FgaValidationException(
f"headers must be a dict or None, got {type(value).__name__}"
)
self._headers = value or {}