Skip to content

Commit

Permalink
Merge pull request #1 from InvestmentSystems/dev
Browse files Browse the repository at this point in the history
0.2.0
  • Loading branch information
ForeverWintr authored Aug 15, 2018
2 parents 66e3652 + 655d4c8 commit 0403743
Show file tree
Hide file tree
Showing 14 changed files with 464 additions and 247 deletions.
14 changes: 0 additions & 14 deletions Pipfile

This file was deleted.

138 changes: 0 additions & 138 deletions Pipfile.lock

This file was deleted.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# class_only_design
[![Build Status](https://travis-ci.com/ForeverWintr/class_only_design.svg?branch=master)](https://travis-ci.com/ForeverWintr/class_only_design) [![codecov](https://codecov.io/gh/ForeverWintr/class_only/branch/master/graph/badge.svg)](https://codecov.io/gh/ForeverWintr/class_only)
[![Build Status](https://travis-ci.com/InvestmentSystems/class_only_design.svg?branch=master)](https://travis-ci.com/InvestmentSystems/class_only_design) [![codecov](https://codecov.io/gh/ForeverWintr/class_only/branch/master/graph/badge.svg)](https://codecov.io/gh/ForeverWintr/class_only)

## Tools for class only design.

Expand Down
14 changes: 12 additions & 2 deletions class_only_design.wpr
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
#!wing
#!version=6.0
#!version=7.0
##################################################################
# Wing IDE project file #
# Wing project file #
##################################################################
[project attributes]
console.toolbox = [{'autosave': False,
'id': 'cmd-NYHX8PCN0FQtdIJK',
'io_encoding': None,
'key_binding': None,
'line_mode': True,
'loc': u'/Users/tomrutherford/Documents/nonDBCode/class_only_design/class_only_design/meta.py',
'pseudo_tty': False,
'raise_panel': True,
'shared': False,
'title': None}]
proj.directory-list = [{'dirloc': loc('class_only_design'),
'excludes': (),
'filter': u'*',
Expand Down
7 changes: 4 additions & 3 deletions class_only_design/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
46 changes: 46 additions & 0 deletions class_only_design/api.py
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
3 changes: 3 additions & 0 deletions class_only_design/constants.py
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"}
58 changes: 0 additions & 58 deletions class_only_design/core.py

This file was deleted.

62 changes: 62 additions & 0 deletions class_only_design/meta.py
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
Loading

0 comments on commit 0403743

Please sign in to comment.