Skip to content

Commit

Permalink
Merge pull request CZ-NIC#847 from CZ-NIC/use-pydantic
Browse files Browse the repository at this point in the history
Improve settings by using pydantic
  • Loading branch information
tpazderka committed Mar 13, 2023
2 parents 76debd6 + 9670f83 commit 559c9c8
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 79 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on the [KeepAChangeLog] project.
[KeepAChangeLog]: https://keepachangelog.com/

## Unreleased
### Changed
- [#847] Using pydantic for settings instead of custom class

[#847]: https://github.com/CZ-NIC/pyoidc/pull/847

## 1.5.0 [2022-12-14]

Expand Down
7 changes: 7 additions & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import alabaster
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'src')))

extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.napoleon',
'sphinxcontrib.autodoc_pydantic',
]

autoclass_content = 'both' # Merge the __init__ docstring into the class docstring.
autodoc_member_order = 'bysource' # Order by source ordering
autodoc_pydantic_model_show_config = True
autodoc_pydantic_settings_show_json = False

templates_path = ['_templates']

Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def run_tests(self):
extras_require={
'develop': ["cherrypy==3.2.4", "pyOpenSSL"],
'testing': tests_requires,
'docs': ['Sphinx', 'sphinx-autobuild', 'alabaster'],
'docs': ['Sphinx', 'sphinx-autobuild', 'alabaster', 'autodoc_pydantic'],
'quality': ['pylama', 'isort', 'eradicate', 'mypy', 'black', 'bandit', 'readme_renderer[md]'],
'types': ['types-requests'],
'ldap_authn': ['python-ldap'],
Expand All @@ -87,6 +87,7 @@ def run_tests(self):
install_requires=[
"requests",
"pycryptodomex",
"pydantic",
"pyjwkest>=1.3.6",
"mako",
"cryptography",
Expand Down
104 changes: 26 additions & 78 deletions src/oic/utils/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,97 +8,48 @@
In order to configure some objects in PyOIDC, you need a settings object.
If you need to add some settings, make sure that you settings class inherits from the appropriate class in this module.
The settings make use of `pydantic <https://docs.pydantic.dev/usage/settings/>`_ library.
It is possible to instance them directly or use environment values to fill the settings.
"""
import typing
from typing import Optional
from typing import Tuple
from typing import Union

import requests
from pydantic import BaseSettings


class SettingsException(Exception):
"""Exception raised by misconfigured settings class."""

class PyoidcSettings(BaseSettings):
"""Main class for all settings shared among consumer and client."""

class PyoidcSettings:
verify_ssl: Union[bool, str] = True
"""
Main class for all settings shared among consumer and client.
Keyword Args:
verify_ssl
Control TLS server certificate validation.
If set to True the certificate is validated against the global settings,
if set to False, no validation is performed.
If set to a filename this is used as a certificate bundle in openssl format.
If set to a directory name this is used as a CA directory in the openssl format.
client_cert
Local cert to use as client side certificate.
Can be a single file (containing the private key and the certificate) or a tuple of both file's path.
timeout
Timeout for requests library.
Can be specified either as a single float or as a tuple of floats.
For more details, refer to ``requests`` documentation.
Control TLS server certificate validation:
* If set to True the certificate is validated against the global settings,
* If set to False, no validation is performed.
* If set to a filename this is used as a certificate bundle in openssl format.
* If set to a directory name this is used as a CA directory in the openssl format.
"""

def __init__(
self,
verify_ssl: Union[bool, str] = True,
client_cert: Union[None, str, Tuple[str, str]] = None,
timeout: Union[float, Tuple[float, float]] = 5,
):
self.verify_ssl = verify_ssl
self.client_cert = client_cert
self.timeout = timeout

def __setattr__(self, name, value):
"""This attempts to check if value matches the expected value."""
annotation = typing.get_type_hints(self.__init__)[name] # type: ignore
# Expand Union -> Since 3.8, this can be written as typing.get_origin
if getattr(annotation, "__origin__", annotation) is Union:
expanded = tuple(an for an in annotation.__args__)
else:
expanded = (annotation,)
# Convert Generics
# FIXME: this doesn't check the args of the generic
resolved = tuple(getattr(an, "__origin__", an) for an in expanded)
# Add int if float is present
if float in resolved:
resolved = resolved + (int,)
# FIXME: Add more valid substitution
if isinstance(value, resolved):
# FIXME: Handle bool being an instance of int...
super().__setattr__(name, value)
else:
raise SettingsException(
"%s has a type of %s, expected any of %s."
% (name, type(value), resolved),
)


class ClientSettings(PyoidcSettings):
client_cert: Union[None, str, Tuple[str, str]] = None
"""
Local cert to use as client side certificate.
Can be a single file (containing the private key and the certificate) or a tuple of both file's path.
"""
timeout: Union[float, Tuple[float, float]] = 5
"""
Timeout for requests library.
Can be specified either as a single float or as a tuple of floats.
For more details, refer to ``requests`` documentation.
"""
Base settings for consumer shared among OAuth 2.0 and OpenID Connect.

Keyword Args:
requests_session
Instance of `requests.Session` with configuration options.

"""
class ClientSettings(PyoidcSettings):
"""Base settings for consumer shared among OAuth 2.0 and OpenID Connect."""

def __init__(
self,
verify_ssl: Union[bool, str] = True,
client_cert: Union[None, str, Tuple[str, str]] = None,
timeout: Union[float, Tuple[float, float]] = 5,
requests_session: Optional[requests.Session] = None,
):
super().__init__(
verify_ssl=verify_ssl, client_cert=client_cert, timeout=timeout
)
# For session persistence
self.requests_session = requests_session
requests_session: Optional[requests.Session] = None
"""Instance of `requests.Session` with configuration options."""


class OauthClientSettings(ClientSettings):
Expand Down Expand Up @@ -131,6 +82,3 @@ class OauthProviderSettings(OauthServerSettings):

class OicProviderSettings(OicServerSettings):
"""Specific settings for OpenID Connect provider."""

# TODO: Decide on inheritance...
# It might be better to have a mixin providing OIC specific stuff?

0 comments on commit 559c9c8

Please sign in to comment.