Skip to content

Commit 559c9c8

Browse files
authored
Merge pull request CZ-NIC#847 from CZ-NIC/use-pydantic
Improve settings by using pydantic
2 parents 76debd6 + 9670f83 commit 559c9c8

4 files changed

Lines changed: 39 additions & 79 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on the [KeepAChangeLog] project.
66
[KeepAChangeLog]: https://keepachangelog.com/
77

88
## Unreleased
9+
### Changed
10+
- [#847] Using pydantic for settings instead of custom class
11+
12+
[#847]: https://github.com/CZ-NIC/pyoidc/pull/847
913

1014
## 1.5.0 [2022-12-14]
1115

doc/conf.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
import alabaster
2+
import os
3+
import sys
4+
5+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'src')))
26

37
extensions = [
48
'sphinx.ext.autodoc',
59
'sphinx.ext.napoleon',
10+
'sphinxcontrib.autodoc_pydantic',
611
]
712

813
autoclass_content = 'both' # Merge the __init__ docstring into the class docstring.
914
autodoc_member_order = 'bysource' # Order by source ordering
15+
autodoc_pydantic_model_show_config = True
16+
autodoc_pydantic_settings_show_json = False
1017

1118
templates_path = ['_templates']
1219

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def run_tests(self):
7878
extras_require={
7979
'develop': ["cherrypy==3.2.4", "pyOpenSSL"],
8080
'testing': tests_requires,
81-
'docs': ['Sphinx', 'sphinx-autobuild', 'alabaster'],
81+
'docs': ['Sphinx', 'sphinx-autobuild', 'alabaster', 'autodoc_pydantic'],
8282
'quality': ['pylama', 'isort', 'eradicate', 'mypy', 'black', 'bandit', 'readme_renderer[md]'],
8383
'types': ['types-requests'],
8484
'ldap_authn': ['python-ldap'],
@@ -87,6 +87,7 @@ def run_tests(self):
8787
install_requires=[
8888
"requests",
8989
"pycryptodomex",
90+
"pydantic",
9091
"pyjwkest>=1.3.6",
9192
"mako",
9293
"cryptography",

src/oic/utils/settings.py

Lines changed: 26 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -8,97 +8,48 @@
88
99
In order to configure some objects in PyOIDC, you need a settings object.
1010
If you need to add some settings, make sure that you settings class inherits from the appropriate class in this module.
11+
12+
The settings make use of `pydantic <https://docs.pydantic.dev/usage/settings/>`_ library.
13+
It is possible to instance them directly or use environment values to fill the settings.
1114
"""
12-
import typing
1315
from typing import Optional
1416
from typing import Tuple
1517
from typing import Union
1618

1719
import requests
20+
from pydantic import BaseSettings
1821

1922

20-
class SettingsException(Exception):
21-
"""Exception raised by misconfigured settings class."""
22-
23+
class PyoidcSettings(BaseSettings):
24+
"""Main class for all settings shared among consumer and client."""
2325

24-
class PyoidcSettings:
26+
verify_ssl: Union[bool, str] = True
2527
"""
26-
Main class for all settings shared among consumer and client.
27-
28-
Keyword Args:
29-
verify_ssl
30-
Control TLS server certificate validation.
31-
If set to True the certificate is validated against the global settings,
32-
if set to False, no validation is performed.
33-
If set to a filename this is used as a certificate bundle in openssl format.
34-
If set to a directory name this is used as a CA directory in the openssl format.
35-
client_cert
36-
Local cert to use as client side certificate.
37-
Can be a single file (containing the private key and the certificate) or a tuple of both file's path.
38-
timeout
39-
Timeout for requests library.
40-
Can be specified either as a single float or as a tuple of floats.
41-
For more details, refer to ``requests`` documentation.
28+
Control TLS server certificate validation:
4229
30+
* If set to True the certificate is validated against the global settings,
31+
* If set to False, no validation is performed.
32+
* If set to a filename this is used as a certificate bundle in openssl format.
33+
* If set to a directory name this is used as a CA directory in the openssl format.
4334
"""
44-
45-
def __init__(
46-
self,
47-
verify_ssl: Union[bool, str] = True,
48-
client_cert: Union[None, str, Tuple[str, str]] = None,
49-
timeout: Union[float, Tuple[float, float]] = 5,
50-
):
51-
self.verify_ssl = verify_ssl
52-
self.client_cert = client_cert
53-
self.timeout = timeout
54-
55-
def __setattr__(self, name, value):
56-
"""This attempts to check if value matches the expected value."""
57-
annotation = typing.get_type_hints(self.__init__)[name] # type: ignore
58-
# Expand Union -> Since 3.8, this can be written as typing.get_origin
59-
if getattr(annotation, "__origin__", annotation) is Union:
60-
expanded = tuple(an for an in annotation.__args__)
61-
else:
62-
expanded = (annotation,)
63-
# Convert Generics
64-
# FIXME: this doesn't check the args of the generic
65-
resolved = tuple(getattr(an, "__origin__", an) for an in expanded)
66-
# Add int if float is present
67-
if float in resolved:
68-
resolved = resolved + (int,)
69-
# FIXME: Add more valid substitution
70-
if isinstance(value, resolved):
71-
# FIXME: Handle bool being an instance of int...
72-
super().__setattr__(name, value)
73-
else:
74-
raise SettingsException(
75-
"%s has a type of %s, expected any of %s."
76-
% (name, type(value), resolved),
77-
)
78-
79-
80-
class ClientSettings(PyoidcSettings):
35+
client_cert: Union[None, str, Tuple[str, str]] = None
36+
"""
37+
Local cert to use as client side certificate.
38+
Can be a single file (containing the private key and the certificate) or a tuple of both file's path.
39+
"""
40+
timeout: Union[float, Tuple[float, float]] = 5
41+
"""
42+
Timeout for requests library.
43+
Can be specified either as a single float or as a tuple of floats.
44+
For more details, refer to ``requests`` documentation.
8145
"""
82-
Base settings for consumer shared among OAuth 2.0 and OpenID Connect.
8346

84-
Keyword Args:
85-
requests_session
86-
Instance of `requests.Session` with configuration options.
8747

88-
"""
48+
class ClientSettings(PyoidcSettings):
49+
"""Base settings for consumer shared among OAuth 2.0 and OpenID Connect."""
8950

90-
def __init__(
91-
self,
92-
verify_ssl: Union[bool, str] = True,
93-
client_cert: Union[None, str, Tuple[str, str]] = None,
94-
timeout: Union[float, Tuple[float, float]] = 5,
95-
requests_session: Optional[requests.Session] = None,
96-
):
97-
super().__init__(
98-
verify_ssl=verify_ssl, client_cert=client_cert, timeout=timeout
99-
)
100-
# For session persistence
101-
self.requests_session = requests_session
51+
requests_session: Optional[requests.Session] = None
52+
"""Instance of `requests.Session` with configuration options."""
10253

10354

10455
class OauthClientSettings(ClientSettings):
@@ -131,6 +82,3 @@ class OauthProviderSettings(OauthServerSettings):
13182

13283
class OicProviderSettings(OicServerSettings):
13384
"""Specific settings for OpenID Connect provider."""
134-
135-
# TODO: Decide on inheritance...
136-
# It might be better to have a mixin providing OIC specific stuff?

0 commit comments

Comments
 (0)