-
Notifications
You must be signed in to change notification settings - Fork 1
Config class #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Config class #21
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
fd4e86f
string and integer classes
NickPapONS 90848d8
Adding tests and more config work
NickPapONS dd309c6
Merge branch 'main' into #config-class
NickPapONS 0a09940
Config and tests completed
NickPapONS 98de26c
Addressing feedback
NickPapONS affdfdd
Fixing forgotten variable names
NickPapONS 5b6105a
Fixing unit tests again
NickPapONS ff4e4c3
Forgot a test
NickPapONS 1b1dcec
Addressing new feedback
NickPapONS File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,38 +1,82 @@ | ||
| from typing import Dict | ||
| from __future__ import annotations | ||
|
|
||
| import os | ||
| from typing import Any, Dict, List | ||
|
|
||
| from .properties.base import BaseProperty | ||
| from .properties.intproperty import IntegerProperty | ||
| from .properties.string import StringProperty | ||
|
|
||
| class Config: | ||
|
|
||
| def __init__(self): | ||
| self._properties_to_validate: List[BaseProperty] = [] | ||
|
|
||
| @staticmethod | ||
| def from_env(config_dict: Dict[str, BaseProperty]): | ||
| # TODO = read in and populate property classes as | ||
| # per the example in the main readme. | ||
| # You need to populate with dot notation in mind so: | ||
| # | ||
| # StringProperty("fieldname", "fieldvalue") | ||
| # | ||
| # should be accessed on Config/self, so: | ||
| # | ||
| # value = config.fieldvalue.value | ||
| # i.e | ||
| # config.fieldvalue = StringProperty("fieldname", "fieldvalue") | ||
| # | ||
| # Worth looking at the __setattr_ dunder method and a loop | ||
| # for how to do this. | ||
| # | ||
| # Do track the BaseProperty's that you add ready for | ||
| # assert_valid_config call. | ||
| ... | ||
| def from_env(config_dict: Dict[str, Dict[str, Any]]) -> Config: | ||
|
|
||
| config = Config() | ||
|
|
||
| for env_var_name, value in config_dict.items(): | ||
|
|
||
| value_for_property = os.environ.get(env_var_name, None) | ||
| assert value_for_property is not None, f'Required envionrment value "{env_var_name}" could not be found.' | ||
|
|
||
| if value["class"] == StringProperty: | ||
| if value["kwargs"]: | ||
| regex = value["kwargs"].get("regex") | ||
| min_len = value["kwargs"].get("min_len") | ||
| max_len = value["kwargs"].get("max_len") | ||
| else: | ||
| regex = None | ||
| min_len = None | ||
| max_len = None | ||
|
|
||
| stringprop = StringProperty( | ||
| _name = value["property"], | ||
| _value = value_for_property, | ||
| regex = regex, | ||
| min_len = min_len, | ||
| max_len = max_len | ||
| ) | ||
|
|
||
| prop_name = value["property"] | ||
| setattr(config, prop_name, stringprop) | ||
| config._properties_to_validate.append(stringprop) | ||
|
|
||
| elif value["class"] == IntegerProperty: | ||
| if value["kwargs"]: | ||
| min_val = value["kwargs"].get("min_val") | ||
| max_val = value["kwargs"].get("max_val") | ||
| else: | ||
| min_val = None | ||
| max_val = None | ||
|
|
||
| intprop = IntegerProperty( | ||
| _name = value["property"], | ||
| _value = value_for_property, | ||
| min_val = min_val, | ||
| max_val = max_val | ||
| ) | ||
|
|
||
| prop_name = value["property"] | ||
| setattr(config, prop_name, intprop) | ||
| config._properties_to_validate.append(intprop) | ||
|
|
||
| else: | ||
| prop_type = value["class"] | ||
| raise TypeError(f"Unsupported property type specified via 'property' field, got {prop_type}. Should be of type StringProperty or IntegerProperty") | ||
|
|
||
| return config | ||
|
|
||
|
|
||
| def assert_valid_config(self): | ||
| """ | ||
| Assert that then Config class has the properties that | ||
| provided properties. | ||
| """ | ||
| for property in self._properties_to_validate: | ||
| property.type_is_valid() | ||
| property.secondary_validation() | ||
|
|
||
| # For each of the properties you imbided above, run | ||
| # self.type_is_valid() | ||
| # self.secondary_validation() | ||
|
|
||
|
|
||
| self._properties_to_validate = [] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,2 @@ | ||
| from .string import StringProperty | ||
| from .string import StringProperty | ||
| from .intproperty import IntegerProperty |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| from typing import Optional | ||
| from dataclasses import dataclass | ||
| from .base import BaseProperty | ||
|
|
||
| @dataclass | ||
| class IntegerProperty(BaseProperty): | ||
| min_val: Optional[int] | ||
| max_val: Optional[int] | ||
|
|
||
| def type_is_valid(self): | ||
| """ | ||
| Validate that the property looks like | ||
| its of the correct type | ||
| """ | ||
| try: | ||
| int(self._value) | ||
| except Exception as err: | ||
| raise Exception(f"Cannot cast {self._name} value {self._value} to integer.") from err | ||
|
|
||
| def secondary_validation(self): | ||
| """ | ||
| Non type based validation you might want to | ||
| run against a configuration value of this kind. | ||
| """ | ||
| if not self._value: | ||
| raise ValueError(f"Integer value for {self._name} does not exist.") | ||
|
|
||
| if self.min_val and self._value < self.min_val: | ||
| raise ValueError(f"Integer value for {self._name} is lower than allowed minimum.") | ||
|
|
||
| if self.max_val and self._value > self.max_val: | ||
| raise ValueError(f"Integer value for {self._name} is higher than allowed maximum.") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| from _pytest.monkeypatch import monkeypatch | ||
| import pytest | ||
|
|
||
| from dpytools.config.config import Config | ||
| from dpytools.config.properties.string import StringProperty | ||
| from dpytools.config.properties.intproperty import IntegerProperty | ||
|
|
||
| def test_config_loader(monkeypatch): | ||
| """ | ||
| Tests that a config object can be created and its attributes | ||
| dynamically generated from an input config dictionary with the | ||
| expected contents. | ||
| """ | ||
|
|
||
| # Assigning environment variable values for config dictionary values | ||
| monkeypatch.setenv("SOME_STRING_ENV_VAR", "Some string value") | ||
| monkeypatch.setenv("SOME_URL_ENV_VAR", "https://test.com/some-url") | ||
| monkeypatch.setenv("SOME_INT_ENV_VAR", "6") | ||
|
|
||
| config_dictionary = { | ||
| "SOME_STRING_ENV_VAR": { | ||
| "class": StringProperty, | ||
| "property": "name1", | ||
| "kwargs": { | ||
| "regex": "string value", | ||
| "min_len": 10 | ||
| }, | ||
| }, | ||
| "SOME_URL_ENV_VAR": { | ||
| "class": StringProperty, | ||
| "property": "name2", | ||
| "kwargs": { | ||
| "regex": "https://.*", | ||
| "max_len": 100 | ||
| }, | ||
| }, | ||
| "SOME_INT_ENV_VAR": { | ||
| "class": IntegerProperty, | ||
| "property": "name3", | ||
| "kwargs": { | ||
| "min_val": 5, | ||
| "max_val": 27 | ||
| } | ||
| }, | ||
| } | ||
|
|
||
| config = Config.from_env(config_dictionary) | ||
|
|
||
| # Assertions | ||
|
|
||
| assert config.name1.name == "name1" | ||
| assert config.name1.value == "Some string value" | ||
| assert config.name1.min_len == 10 | ||
| assert config.name1.regex == "string value" | ||
|
|
||
| assert config.name2.name == "name2" | ||
| assert config.name2.value == "https://test.com/some-url" | ||
| assert config.name2.regex == "https://.*" | ||
| assert config.name2.max_len == 100 | ||
|
|
||
| assert config.name3.name == "name3" | ||
| assert config.name3.min_val == 5 | ||
| assert config.name3.max_val == 27 | ||
|
|
||
|
|
||
| def test_config_loader_no_values_error(): | ||
| """ | ||
| Tests that an exception will be raised when a config object | ||
| is created using the from_env() method but the environment | ||
| variable values have not been assigned (values are None). | ||
| """ | ||
|
|
||
| # No environment variable values assigned in this test | ||
|
|
||
| config_dictionary = { | ||
| "SOME_STRING_ENV_VAR": { | ||
| "class": StringProperty, | ||
| "property": "name1", | ||
| "kwargs": { | ||
| "min_len": 10 | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| with pytest.raises(Exception) as e: | ||
|
|
||
| config = Config.from_env(config_dictionary) | ||
|
|
||
| assert 'Required environment value "SOME_STRING_ENV_VAR" could not be found.' in str(e.value) | ||
|
|
||
|
|
||
| def test_config_loader_incorrect_type_error(monkeypatch): | ||
| """ | ||
| Tests that a TypeError will be raised when a config object | ||
| is created using the from_env() method but the type of an | ||
| attribute being created is not either a StringProperty or IntegerProperty. | ||
| """ | ||
|
|
||
| monkeypatch.setenv("SOME_STRING_ENV_VAR", "Some string value") | ||
|
|
||
| config_dictionary = { | ||
| "SOME_STRING_ENV_VAR": { | ||
| "class": int, | ||
| "property": "name1", | ||
| "kwargs": { | ||
| "min_val": 10, | ||
|
|
||
| }, | ||
| } | ||
| } | ||
|
|
||
| with pytest.raises(TypeError) as e: | ||
|
|
||
| config = Config.from_env(config_dictionary) | ||
|
|
||
| assert "Unsupported property type specified via 'property' field, got <class 'int'>. Should be of type StringProperty or IntegerProperty" in str(e.value) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.