-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from InvestmentSystems/dev
0.2.0
- Loading branch information
Showing
14 changed files
with
464 additions
and
247 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains 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 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 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 |
---|---|---|
|
@@ -4,7 +4,8 @@ | |
|
||
__author__ = """Tom Rutherford""" | ||
__email__ = "[email protected]" | ||
__version__ = "0.1.4" | ||
__version__ = "0.2.0" | ||
|
||
from class_only_design.core import class_only | ||
from class_only_design.core import constant | ||
from class_only_design.api import class_only | ||
from class_only_design.api import constant | ||
from class_only_design.api import namespace |
This file contains 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,46 @@ | ||
import functools | ||
|
||
from class_only_design.meta import OnlyMeta | ||
from class_only_design.meta import MetaNamespace | ||
from class_only_design import util | ||
|
||
|
||
def class_only(cls): | ||
""" | ||
Class only is a class decorator that disallows instantiation or state change on a class object. | ||
""" | ||
classdict = {k: v for k, v in cls.__dict__.items()} | ||
new = OnlyMeta(cls.__name__, cls.__bases__, classdict) | ||
|
||
return new | ||
|
||
|
||
class constant: | ||
""" | ||
A method decorator similar to @property but for use on class only classes. The decorated method | ||
is only called once. Subsequent calls simply return the stored value. This is usefull for | ||
declaring a class level constant that is not actually created until it's used. | ||
""" | ||
|
||
def __init__(self, method): | ||
self.method = method | ||
self._value = None | ||
|
||
def __get__(self, instance, cls): | ||
if not self._value: | ||
self._value = self.method(cls) | ||
return self._value | ||
|
||
|
||
def namespace(cls): | ||
""" | ||
Class only is a class decorator that disallows instantiation or state change on a class object. | ||
""" | ||
|
||
classdict = {k: v for k, v in cls.__dict__.items()} | ||
classdict["_initializing_"] = True | ||
new = MetaNamespace(cls.__name__, cls.__bases__, classdict) | ||
new.nameof = util.KeyGetter(new) | ||
del new._initializing_ | ||
|
||
return new |
This file contains 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,3 @@ | ||
|
||
# These names are reserved for use by the namespace class machinery. They cannot appear in namespace classes. | ||
RESERVED_NAMES = {"nameof"} |
This file was deleted.
Oops, something went wrong.
This file contains 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,62 @@ | ||
from class_only_design import constants | ||
from class_only_design import util | ||
|
||
# This is inserted into decorated classes. Note, __new__ is implicitly converted to a staticmethod | ||
# during class creation. I'm doing so explicitly here so I have a reference I can check later. This | ||
# seems to prevent the implicit transformation, but I'm not sure if that's an implementation | ||
# detail. | ||
@staticmethod | ||
def __new__(*args, **kwargs): | ||
raise TypeError("Class Only classes cannot be instantiated") | ||
|
||
|
||
class OnlyMeta(type): | ||
def __new__(cls, name, bases, classdict): | ||
|
||
if "__init__" in classdict: | ||
raise TypeError("Class Only classes cannot define __init__") | ||
if classdict.get("__new__") not in (__new__, None): | ||
raise TypeError("Class Only classes cannot define __new__") | ||
|
||
# Disallow bases that have __new__ or __init__ defined | ||
for b in bases: | ||
if not isinstance(b, cls): | ||
if b.__init__ is not object.__init__: | ||
raise TypeError("Class Only classes cannot define __init__", b) | ||
if b.__new__ is not object.__new__: | ||
raise TypeError("Class Only classes cannot define __new__", b) | ||
|
||
# Insert our own __new__ | ||
classdict["__new__"] = __new__ | ||
return super().__new__(cls, name, bases, classdict) | ||
|
||
def __setattr__(cls, name, arg): | ||
if not getattr(cls, "_initializing_", False): | ||
raise TypeError("Class Only classes are immutable") | ||
return super().__setattr__(name, arg) | ||
|
||
|
||
class MetaNamespace(OnlyMeta): | ||
def __new__(cls, name, bases, classdict): | ||
# disallow reserved names | ||
bad_names = classdict.keys() & constants.RESERVED_NAMES | ||
|
||
for b in bases: | ||
if not isinstance(b, cls): | ||
bad_names |= vars(b).keys() & constants.RESERVED_NAMES | ||
if bad_names: | ||
raise ValueError( | ||
"Cannot create namespace class with reserved names", sorted(bad_names) | ||
) | ||
return super().__new__(cls, name, bases, classdict) | ||
|
||
def __iter__(cls): | ||
# Walk up the mro, looking for namespace classes. Keep track of attrs we've already seen | ||
# and don't re-yield their values | ||
seen_attrs = set() | ||
for c in cls.__mro__: | ||
if isinstance(c, MetaNamespace): | ||
for k, v in vars(c).items(): | ||
if not util._is_internal(k) and k not in seen_attrs: | ||
seen_attrs.add(k) | ||
yield v |
Oops, something went wrong.