Skip to content

Commit

Permalink
Merge pull request #47 from jetavator/issue-46-Allow_multiple_subclas…
Browse files Browse the repository at this point in the history
…ses_of_RegistersSubclasses_to_have_the_same_register_as_value_if_they_have_different_inheritance_paths

Allow multiple subclasses of RegistersSubclasses to have the same register_as value if they have different inheritance paths
  • Loading branch information
jtv8 authored Dec 27, 2020
2 parents 4e01aab + 568a47f commit fcaddde
Show file tree
Hide file tree
Showing 5 changed files with 336 additions and 37 deletions.
237 changes: 237 additions & 0 deletions features/registered_subclasses.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
Feature: Generic mixin for registering subclasses by name

Scenario: Test basic usage

When we execute the following python code:
"""
from wysdom.mixins import RegistersSubclasses
class Animal(RegistersSubclasses):
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError()
class Dog(Animal, register_as="dog"):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal, register_as="cat"):
def speak(self):
return f"{self.name} says Miaow!"
"""
Then the following statements are true:
"""
Animal.registered_subclass("dog") is Dog
Animal.registered_subclass("cat") is Cat
type(Animal.registered_subclass_instance("dog", "Spot")) is Dog
type(Animal.registered_subclass_instance("cat", "Whiskers")) is Cat
Animal.registered_subclass_instance("dog", "Spot").speak() == "Spot says Woof!"
Animal.registered_subclass_instance("cat", "Whiskers").speak() == "Whiskers says Miaow!"
"""

Scenario: Allow more distant descendants to be registered

When we execute the following python code:
"""
from wysdom.mixins import RegistersSubclasses
class Animal(RegistersSubclasses):
def speak(self):
raise NotImplementedError()
class Pet(Animal):
def __init__(self, name):
self.name = name
class Dog(Pet, register_as="dog"):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Pet, register_as="cat"):
def speak(self):
return f"{self.name} says Miaow!"
"""
Then the following statements are true:
"""
Animal.registered_subclass("dog") is Dog
Animal.registered_subclass("cat") is Cat
Pet.registered_subclass("dog") is Dog
Pet.registered_subclass("cat") is Cat
type(Pet.registered_subclass_instance("dog", "Spot")) is Dog
type(Pet.registered_subclass_instance("cat", "Whiskers")) is Cat
Pet.registered_subclass_instance("dog", "Spot").speak() == "Spot says Woof!"
Pet.registered_subclass_instance("cat", "Whiskers").speak() == "Whiskers says Miaow!"
"""

Scenario: Raise KeyError when a class key is not defined

When we execute the following python code:
"""
from wysdom.mixins import RegistersSubclasses
class Animal(RegistersSubclasses):
def __init__(self, name):
self.name = name
"""
And we try to execute the following python code:
"""
Animal.registered_subclass("elephant")
"""
Then a KeyError is raised with text:
"""
The key 'elephant' matches no proper subclasses of <class 'Animal'>.
"""

Scenario: Raise KeyError when a class key is only defined on a superclass

When we execute the following python code:
"""
from wysdom.mixins import RegistersSubclasses
class Animal(RegistersSubclasses):
def speak(self):
raise NotImplementedError()
class Pet(Animal):
def __init__(self, name):
self.name = name
class Dog(Pet, register_as="dog"):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal, register_as="cat"):
def speak(self):
return f"Cat says Miaow!"
"""
And we try to execute the following python code:
"""
Pet.registered_subclass("cat")
"""
Then a KeyError is raised with text:
"""
The key 'cat' matches no proper subclasses of <class 'Pet'>.
"""

Scenario: Raise KeyError when a class key matches more than one subclass

When we execute the following python code:
"""
from wysdom.mixins import RegistersSubclasses
class Animal(RegistersSubclasses):
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError()
class Dog(Animal, register_as="animal"):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal, register_as="animal"):
def speak(self):
return f"{self.name} says Miaow!"
"""
And we try to execute the following python code:
"""
Animal.registered_subclass("animal")
"""
Then a KeyError is raised with text:
"""
The key 'animal' is ambiguous as it matches multiple proper subclasses of <class 'Animal'>:
[<class 'Dog'>, <class 'Cat'>]
"""

Scenario: Allow non-unique register_as values for different inheritance paths

When we execute the following python code:
"""
from wysdom.mixins import RegistersSubclasses
class Animal(RegistersSubclasses):
def speak(self):
raise NotImplementedError()
class Pet(Animal):
def __init__(self, name):
self.name = name
class Dog(Animal, register_as="dog"):
def speak(self):
return f"The dog says Woof!"
class PetDog(Pet, Dog, register_as="dog"):
def speak(self):
return f"{self.name} says Woof!"
"""

Then the following statements are true:
"""
Animal.registered_subclass("dog") is Dog
Pet.registered_subclass("dog") is PetDog
type(Animal.registered_subclass_instance("dog")) is Dog
type(Pet.registered_subclass_instance("dog", "Spot")) is PetDog
Animal.registered_subclass_instance("dog").speak() == "The dog says Woof!"
Pet.registered_subclass_instance("dog", "Spot").speak() == "Spot says Woof!"
"""


Scenario: Raise KeyError if return_common_superclass is False and class names are ambiguous

When we execute the following python code:
"""
from wysdom.mixins import RegistersSubclasses
class Animal(RegistersSubclasses):
def speak(self):
raise NotImplementedError()
class Pet(Animal):
def __init__(self, name):
self.name = name
class Dog(Animal, register_as="dog"):
def speak(self):
return f"The dog says Woof!"
class PetDog(Pet, Dog, register_as="dog"):
def speak(self):
return f"{self.name} says Woof!"
"""

And we try to execute the following python code:
"""
Animal.registered_subclass("dog", return_common_superclass=False)
"""
Then a KeyError is raised with text:
"""
The key 'dog' is ambiguous as it matches multiple proper subclasses of <class 'Animal'>:
[<class 'Dog'>, <class 'PetDog'>]
"""
39 changes: 31 additions & 8 deletions features/steps/steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,35 @@ def load_python_module(context, module):
def step_impl(context, module):
try:
load_python_module(context, module)
context.load_python_module_error = None
context.exception = None
except Exception as e:
context.load_python_module_error = e
context.exception = e


@then("a {exception_type} is raised with text")
def step_impl(context, exception_type):
if context.load_python_module_error is None:
if context.exception is None:
raise Exception("No exception was raised.")
if exception_type != context.load_python_module_error.__class__.__name__:
if exception_type != context.exception.__class__.__name__:
raise Exception(
f"Expected exception type {exception_type}, got {type(context.load_python_module_error)}."
f"Expected exception type {exception_type}, got {type(context.exception)}: " +
str(context.exception)
)
if context.text.strip() != str(context.load_python_module_error).strip():

def remove_whitespace(text):
return " ".join(
line.strip()
for line in context.text.splitlines()
).strip()

if remove_whitespace(context.text) != remove_whitespace(context.exception):
raise Exception(
f"Expected error message '{context.text}', got '{context.load_python_module_error}'."
f"""
Expected error message:
{context.text}
Got:
{context.exception}
"""
)


Expand All @@ -53,11 +66,21 @@ def step_impl(context, variable_name):


@when("we execute the following python code")
def step_impl(context):
def execute_python(context):
exec(context.text)
globals().update(locals())


@when("we try to execute the following python code")
def step_impl(context):
context.scenario.text = context.text
try:
execute_python(context)
context.exception = None
except Exception as e:
context.exception = e


@then("the following statements are true")
def step_impl(context):
assert callable(document)
Expand Down
2 changes: 1 addition & 1 deletion wysdom/__version__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
VERSION = (0, 1, 5)
VERSION = (0, 2, 0)

__version__ = '.'.join(map(str, VERSION))
Loading

0 comments on commit fcaddde

Please sign in to comment.