Yuppy
Python Programming for the Privileged Class
Install / Use
/learn @kuujo/YuppyREADME
Yuppy
Python Programming for the Privileged Class
Yuppy is released under the MIT License.
Yuppy is a small Python library that integrates seamlessly with your application to promote data integrity by supporting common object-oriented language features. It intends to provide fully integrated support for interfaces, abstract classes and methods, final classes and methods, and type hinting in a manner that preserves much of the dynamic nature of Python. Yuppy can improve the integrity of your data and the stability of your code without comprimising usability. It is easy to use and is intentionally designed to fit with the Python development culture, not circumvent it.
Table of contents
"But type checking is bad!"
Yuppy does type checking in a manner that is in keeping with the dynamic nature of Python. Yuppy interface checks can be based on duck-typing, so any class can serve as a Yuppy interface. This feature simply serves as a more efficient way to determine whether any given object walks and talks like a duck.
A Complete Example
from yuppy import *
# Yuppy classes must either use the base yuppy.ClassType metaclass or use
# the @yuppy.yuppy decorator.
# In this case, we're creating an abstract base class "Apple" with a
# couple of abstract methods for getting attributes.
@abstract
class Apple(object):
"""An abstract apple."""
@abstract
def get_color(self):
"""Gets the apple color."""
@abstract
def set_color(self):
"""Sets the apple color."""
# Create an interface for objects that can be eaten.
@interface
class IEatable(object):
"""An interface that supports eating."""
def eat(self):
"""Eats an apple."""
# Now, we can implement a concret "GreenApple" class. Note that we must
# implement the abstract get_color() and set_color() methods or else we
# have to explicitly declare the class once again @abstract. Similarly,
# we are implementing the IEatable interface, so we must implement all
# the IEatable methods or else declare the class abstract.
@implements(IEatable)
class GreenApple(Apple):
"""A concrete green apple."""
# Don't allow the green apple color to be changed.
color = const('green')
# Create a float weight.
weight = var(float)
# Override the get_color() abstract method.
def get_color(self):
return self.color
# Override the set_color() abstract method. We'll just raise an error.
def set_color(self, value):
raise AttributeError("Cannot set green apple color.")
# Implement a method to set the green apple weight. Here we use type
# hinting to ensure that the weight argument is an integer or float.
# Note that we can also use interfaces for type hinting, including any
# class that does not extend the yuppy.Interface class (which results
# in an attribute-based comparison, e.g. duck typing).
@params(weight=(int, float))
def set_weight(self, weight):
self.weight = weight
# Implement an implicit interface.
class ITree(object):
def add_apple(self, apple):
"""Adds an apple to the tree."""
def remove_apple(self, apple):
"""Removes an apple from the tree."""
# Even without extending the Interface class, we can use ITree as an interface.
@implements(ITree)
class Tree(object):
apples = var(set)
def __init__(self):
self.apples = set()
# We can use any class or interface for type hinting. If an Apple instance
# is not passed as the 'apple' argument, the argument will be compared to
# the Apple class to determine whether it is equivalent by attributes.
@params(apple=Apple)
def add_apple(self, apple):
self.apples.add(apple)
@params(apple=Apple)
def remove_apple(self, apple):
self.apples.remove(apple)
Of course, in the real world this would be an unrealistic example. Python's flexibility and features like data descriptors remove the need for getters and setters that are necessary in other languages (this is one reason that Yuppy does not attempt encapsulation). But, indeed, these features can be very useful in ultimately reducing the code required for error handling by helping ensure the integrity of data from the time it is set on an object or passed to an instance method.
Class Decorators
yuppy
Declares a Yuppy class definition.
yuppy(cls)
This decorator is not required to implement a Yuppy class. The recommended
alternative to using the yuppy decorator is to use the yuppy.ClassType
metaclass in your class definition. The decorator simply dynamically
extends any class to use the yuppy.ClassType metaclass in its definition.
from yuppy import ClassType, yuppy
@yuppy
class Apple(object):
"""This is a Yuppy class."""
class Apple(object):
"""This is also a Yuppy class."""
__metaclass__ = ClassType
abstract
Creates an abstract class.
abstract(cls)
Abstract classes are classes that cannot themselves be instantiated, but can be extended and instantiated. An abstract class can contain any number of abstract methods. When an abstract class is extended, the extending class must override all the abstract methods or else declare itself abstract.
Example
from yuppy import abstract
@abstract
class Apple(object):
"""An abstract apple."""
weight = var(float)
def get_weight(self):
return self.weight
def set_weight(self, weight):
self.weight = weight
class GreenApple(Apple):
"""A concrete green apple."""
We will be able to create instances of GreenApple, which inherits from
Apple, but any attempts to instantiate an Apple will result in a
TypeError.
>>> apple = GreenApple()
>>> apple.set_weight(1.0)
>>> apple.get_weight()
1.0
>>> apple = Apple()
TypeError: Cannot instantiate abstract class 'Apple'.
final
Declares a class definition to be final.
The final Yuppy decorator is, well, final, which allows users to define
classes that cannot be extended. This is a common feature in several
other object-oriented languages.
final(cls)
Example
from yuppy import final
@final
class Apple(object):
weight = var(float, default=None)
>>> apple = Apple()
>>> class GreenApple(Apple):
... pass
...
TypeError: ...
Member Decorators
variable
Creates a variable attribute.
variable([default=None[, validate=None[, *types]]])
var([default=None[, validate=None[, *types]]])
Example
from yuppy import yuppy, var
@yuppy
class Apple(object):
foo = var(int, default=None, validate=lambda x: x == 1)
>>> apple = Apple()
static
Creates a static attribute.
Static Yuppy members are equivalent to standard Python class members. This is
essentially the same parallel that exists between Python's class members
and static variables in many other object-oriented languages. With Yuppy we
can use the static decorator to create static methods or properties.
static([default=None[, validate=None[, *types]]])
Example
from yuppy import yuppy, static
@yuppy
class Apple(object):
"""An abstract apple."""
weight = static(float, default=None)
With static members, changes to a member variable will be applied to
all instances of the class. So, even after instantiating a new instance
of the class, the weight attribute value will remain the same.
>>> apple1 = Apple()
>>> apple1.weight
None
>>> apple1.weight = 2.0
>>> apple1.weight
2.0
>>> apple2 = Apple()
>>> apple2.weight
2.0
constant
Creates a constant attribute.
Constants are attributes which have a permanent value. They can be used for
any value which should never change within the application, such as an
application port number, for instance. With Yuppy we can use the const
decorator to create a constant, passing a single permanent value to the
constructor.
constant(value)
const(value)
Example
from yuppy import yuppy, const
@yuppy
class RedApple(object):
color = const('red')
>>> RedApple.color
'red'
>>> apple = RedApple()
>>> apple.color
'red'
>>> RedApple.color = 'blue'
AttributeError: Cannot override 'RedApple' attribute 'color' by assignment.
>>> RedApple.color
'red'
>>> apple.color
'red'
>>> apple = RedApple()
>>> apple.color
'red'
>>> apple.color = 'blue'
AttributeError: Cannot override 'Apple' attribute 'color' by assignment.
method
Creates a method attribute.
method(callback)
Example
from yuppy import yuppy, var, method
@yuppy
class Apple(object):
color = var(default='red')
@method
def getcolor(self):
return self.color
>>> apple = Apple()
>>> apple.getcolor()
'red'
abstract
Creates an abstract method.
abstract(method)
Abstract methods can be applied to any python class, even without
declaring the class to be abstract. This means that if the method is
not re-defined in a child class, an AttributeError will be raised if
the abstract method is accessed. Therefore, it is strongly recommended
that any class that contains abstract methods be declared abstract.
Example
from yuppy import abstract
@abstract
class Apple(object):
"""An abstract apple."""
@abstract
def get_color(self):
"""Gets the appl
