Skip to content

Commit

Permalink
add tests and refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
goktug97 committed Apr 14, 2021
1 parent 9c5042b commit 04371c8
Show file tree
Hide file tree
Showing 4 changed files with 281 additions and 38 deletions.
74 changes: 37 additions & 37 deletions pipcs/pipcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ class InvalidChoiceError(Exception):


class RequiredError(Exception):
"""Raised if a user doesn't set :class:`pipcs.required` variable in the inherited config. It is also raised if a :class:`pipcs.required` variable is not set during :meth:`pipcs.Config.check_config`.
"""Raised if a user doesn't set :class:`pipcs.required` variable
in the inherited config. It is also raised if a :class:`pipcs.required`
variable is not set during :meth:`pipcs.Config.check_config`.
"""
pass

Expand All @@ -19,9 +21,13 @@ class ConditionError(Exception):


T = TypeVar('T')


class required:
"""Mark a variable as required."""
pass


Required = Union[Type[required], T]


Expand Down Expand Up @@ -93,7 +99,9 @@ def __invert__(self):


class Choices(Generic[T]):
"""Specify valid choices for a variable. :class:`pipcs.InvalidChoiceError` error will be raised when the user tries to set the variable to a non-valid choice in the inherited configuration.
"""Specify valid choices for a variable.
:class:`pipcs.InvalidChoiceError` error will be raised when the user
tries to set the variable to a non-valid choice in the inherited configuration.
Args:
choices (List[T]): Valid choices for the configuration variable.
Expand Down Expand Up @@ -195,13 +203,16 @@ class Example():
else:
self.check_value(k, v)

@staticmethod
def check_value(key, value):
def check_value(self, key, value):
if isinstance(value, Config):
value.check_config()
elif isinstance(value, Choices):
if value.default is required:
raise RequiredError(f'{key} is required!')
elif isinstance(value, Condition):
if value._compare(self):
if value.value is required:
raise RequiredError(f'{key} is required!')
elif value is required:
raise RequiredError(f'{key} is required!')

Expand Down Expand Up @@ -276,19 +287,15 @@ def to_dict(self, check=False):
def __setattr__(self, key, value):
self[key] = value

def add_config(self, cls, name=None):
def add_config(self, cls, name, check=True):
if self.get(name):
if name is None:
raise ValueError
parent = self.get(name)
parent = self.get_value(name)
config_class = type(cls.__name__, (Config,), dict(cls.__dict__))
members = [var for var in vars(config_class) if not var.startswith('__')]
if hasattr(config_class, '__annotations__'):
_annotations = {**parent.__annotations__, **config_class.__annotations__}
else:
_annotations = parent.__annotations__
if name is None:
name = parent._name
annotations = {}
for member in members:
if member in _annotations:
Expand All @@ -297,8 +304,9 @@ def add_config(self, cls, name=None):
config_class._name = name
datacls = dataclass(config_class)()
datacls.__annotations__ = config_class.__annotations__
merged_config = parent.update(Config(datacls))
merged_config.check_config()
merged_config = parent.update_config(Config(datacls))
if check:
merged_config.check_config()
self[name] = merged_config
else:
config_class = type(cls.__name__, (Config,), dict(cls.__dict__))
Expand All @@ -312,40 +320,32 @@ def add_config(self, cls, name=None):
v._name = k
return self[name]

def add(self, name=None):
def add(self, name, check=True):
def _add(wrapped_class):
return self.add_config(wrapped_class, name)
return self.add_config(wrapped_class, name, check)
return _add

def __call__(self, name=None):
def __call__(self, name):
return self.add(name)

def update(self, other):
newdict = dict(self)
def update_config(self, other):
newdict = Config(self)
for k, v in other.items():
if not hasattr(self, k):
if isinstance(v, Choices):
newdict[k] = v.default
else:
newdict[k] = v
else:
newdict[k] = v
if hasattr(self, k):
if isinstance(self[k], Config):
newdict[k] = self[k].update(v)
newdict[k] = self[k].update_config(v)
elif isinstance(self[k], abc.Mapping):
newdict[k] = {**self[k], **v}
elif isinstance(self[k], Condition):
if self[k]._compare(other):
newdict[k] = v
elif isinstance(v, Choices):
if self[k].default is required:
raise RequiredError(f'{k} is required, valid choices: {self[k].choices}')
else:
newdict[k] = self[k].default
elif isinstance(self[k], Choices):
if v not in self[k].choices:
raise InvalidChoiceError(f'{v} is not valid for {k}, valid choices: {self[k].choices}')
else:
newdict[k] = v
else:
newdict[k] = v
return Config(newdict)

def update_choices(a, b):
for k, v in a.items():
if isinstance(v, Config):
update_choices(v, b[k])
elif isinstance(b[k], Choices):
b[k] = v.default
update_choices(self, newdict)
return newdict
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
long_description = f.read()

setup(name='pipcs',
version=f'1.1.1',
version=f'1.1.2',
description='pipcs is python configuration system',
author='Göktuğ Karakaşlı',
author_email='karakasligk@gmail.com',
Expand Down
Empty file added tests/__init__.py
Empty file.
243 changes: 243 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
import unittest

from pipcs import Config, Choices, Condition, required, Required
from pipcs import InvalidChoiceError, RequiredError

class TestChoices(unittest.TestCase):
def setUp(self):
self.config = Config()
@self.config('test')
class Test():
variable1: Choices[int] = Choices([1, 2, 3])
variable2: Choices[int] = Choices([1, 2, 3], default=1)

def test_default(self):
self.assertEqual(self.config.test.variable1.default, required)
self.assertEqual(self.config.test.variable2.default, 1)

config = Config(self.config)
@config('test')
class Test():
variable1 = 1
self.assertEqual(config.test.variable2, 1)

def test_valid_default(self):
config = Config()
with self.assertRaises(InvalidChoiceError):
@config('test')
class Test():
variable: Choices[int] = Choices([1, 2, 3], default=4)

def test_required_error(self):
with self.assertRaises(RequiredError):
self.config.test.get_value('variable1', check=True)

config = Config(self.config)
with self.assertRaises(RequiredError):
@config('test')
class Test():
variable2 = 1

def test_to_dict(self):
config = self.config.to_dict()
self.assertEqual(config['test']['variable1'], required)
self.assertEqual(config['test']['variable2'], 1)

def test_invalid_choices(self):
config = Config(self.config)
with self.assertRaises(InvalidChoiceError):
@config('test')
class Test():
variable1 = 4


class TestCondition(unittest.TestCase):
def setUp(self):
self.config = Config()
@self.config('test')
class Test():
variable1: Choices[int] = Choices([1, 2, 3], default=3)
variable2: Choices[int] = Choices([1, 2, 3], default=1)
conditional_variable1: Condition[Required[int]] = Condition(required, variable1 == 1)
conditional_variable2: Condition[int] = Condition(1, variable1 == 2)
conditional_variable3: Condition[Required[int]] = Condition(required, variable1 == 3)
conditional_variable4: Condition[int] = Condition(1, variable1 == 3) & Condition(1, variable2 == 1)
conditional_variable5: Condition[int] = Condition(1, variable1 == 3) | Condition(1, variable2 == 1)
conditional_variable6: Condition[int] = ~Condition(1, variable1 == 3)

self.user_config1 = Config(self.config)
@self.user_config1('test')
class Test():
variable1 = 1
conditional_variable1 = 2

self.user_config2 = Config(self.config)
@self.user_config2('test')
class Test():
variable1 = 2

def test_value(self):
self.assertEqual(self.user_config1.test.conditional_variable1, 2)
self.assertEqual(self.config.test.conditional_variable1.value, required)
self.assertEqual(self.user_config2.test.conditional_variable2, 1)
self.assertEqual(self.config.test.conditional_variable3, required)

def test_in(self):
self.assertTrue('conditional_variable1' in self.user_config1.test.to_dict())
self.assertFalse('conditional_variable1' in self.user_config2.test.to_dict())
self.assertTrue('conditional_variable3' in self.config.test.to_dict())

self.assertTrue('conditional_variable2' in self.user_config2.test.to_dict())
self.assertFalse('conditional_variable2' in self.user_config1.test.to_dict())

def test_required_error(self):
with self.assertRaises(RequiredError):
self.config.to_dict(check=True)

try:
self.user_config2.test.to_dict(check=True)
except RequiredError:
self.fail('Raised RequiredError')

with self.assertRaises(RequiredError):
config = Config(self.config)
@config('test')
class Test():
variable1 = 1

try:
config = Config(self.config)
@config('test')
class Test():
variable1 = 2
except RequiredError:
self.fail('Raised RequiredError')

def test_logic(self):
config = Config(self.config)
@config('test')
class Test():
conditional_variable3 = 1
variable1 = 3
variable2 = 1

self.assertTrue('conditional_variable4' in config.test.to_dict())
self.assertTrue('conditional_variable5' in config.test.to_dict())
self.assertFalse('conditional_variable6' in config.test.to_dict())

config = Config(self.config)
@config('test')
class Test():
conditional_variable1 = 1
variable1 = 1
variable2 = 1

self.assertFalse('conditional_variable4' in config.test.to_dict())
self.assertTrue('conditional_variable5' in config.test.to_dict())
self.assertTrue('conditional_variable6' in config.test.to_dict())

config = Config(self.config)
@config('test')
class Test():
conditional_variable1 = 1
variable1 = 1
variable2 = 3

self.assertFalse('conditional_variable4' in config.test.to_dict())
self.assertFalse('conditional_variable5' in config.test.to_dict())
self.assertTrue('conditional_variable6' in config.test.to_dict())

config = Config(self.config)
@config('test')
class Test():
conditional_variable3 = 1
variable1 = 3
variable2 = 3

self.assertFalse('conditional_variable4' in config.test.to_dict())
self.assertTrue('conditional_variable5' in config.test.to_dict())
self.assertFalse('conditional_variable6' in config.test.to_dict())


class TestRequired(unittest.TestCase):
def setUp(self):
self.config = Config()
@self.config('test')
class Test():
variable: int = 2
required_variable: Required[int] = required

def test_inherit(self):
with self.assertRaises(RequiredError):
config = Config(self.config)
@config('test')
class Test():
variable = 1
try:
config = Config(self.config)
@config('test')
class Test():
required_variable = 1
except RequiredError:
self.fail('Raised RequiredError')

def test_to_dict(self):
with self.assertRaises(RequiredError):
self.config.to_dict(check=True)

config = self.config.to_dict()
self.assertEqual(self.config.test.required_variable, required)
self.assertEqual(config['test']['required_variable'], required)

class TestConfig(unittest.TestCase):
def setUp(self):
self.config = Config()
@self.config('test')
class Test():
variable: int = 1
choice_variable: Choices[int] = Choices([1, 2, 3], default=1)

def test_missing(self):
with self.assertRaises(AttributeError):
self.config.test.missing_variable

with self.assertRaises(AttributeError):
self.config.missing_config

def test_get_value(self):
self.config.get_value('test', check=True)

def test_to_dict(self):
self.config.to_dict(check=True)

def test_user_variables(self):
config = Config(self.config)
@config('test')
class Test():
user_variable: int = 2
no_typing_variable = 1
self.assertEqual(config.test.variable, 1)
self.assertEqual(config.test.user_variable, 2)
self.assertFalse(hasattr(config.test, 'no_typing_variable'))

def test_default_choice(self):
config = Config(self.config)
self.assertTrue(isinstance(config.test.choice_variable, Choices))
@config('test')
class Test():
pass

def test_update(self):
config = Config()
@config('test')
class Test():
variable: int = 2
choice_variable2: Choices[int] = Choices([1, 2, 3])

config = self.config.update_config(config)
self.assertEqual(config.test.variable, 2)
self.assertEqual(config.test.choice_variable, 1)
self.assertFalse(isinstance(config.test.choice_variable, Choices))
self.assertTrue(hasattr(config.test, 'choice_variable2'))
self.assertTrue(isinstance(config.test.choice_variable2, Choices))

0 comments on commit 04371c8

Please sign in to comment.