Skip to content

Commit

Permalink
converted to python3, no longer py2 compatible
Browse files Browse the repository at this point in the history
  • Loading branch information
kneufeld committed Oct 27, 2018
1 parent 00ef9e1 commit 3957ab3
Show file tree
Hide file tree
Showing 14 changed files with 149 additions and 84 deletions.
45 changes: 44 additions & 1 deletion alkali/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,52 @@
__copyright__ = 'Copyright 2017 Kurt Neufeld'

from .database import Database
from .storage import IStorage, Storage, FileStorage, JSONStorage
from .storage import Storage, FileStorage, JSONStorage
from .manager import Manager
from .model import Model
from .query import Query
from .utils import tznow, tzadd, fromts
from . import fields

class reify(object):
""" Use as a class method decorator. It operates almost exactly like the
Python ``@property`` decorator, but it puts the result of the method it
decorates into the instance dict after the first call, effectively
replacing the function it decorates with an instance variable. It is, in
Python parlance, a non-data descriptor. The following is an example and
its usage:
.. doctest::
>>> from pyramid.decorator import reify
>>> class Foo(object):
... @reify
... def jammy(self):
... print('jammy called')
... return 1
>>> f = Foo()
>>> v = f.jammy
jammy called
>>> print(v)
1
>>> f.jammy
1
>>> # jammy func not called the second time; it replaced itself with 1
>>> # Note: reassignment is possible
>>> f.jammy = 2
>>> f.jammy
2
"""
def __init__(self, wrapped):
self.wrapped = wrapped
from functools import update_wrapper
update_wrapper(self, wrapped)

def __get__(self, inst, objtype=None):
if inst is None:
return self
val = self.wrapped(inst)
setattr(inst, self.wrapped.__name__, val)
return val
6 changes: 3 additions & 3 deletions alkali/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def get_filename(self, model, storage=None):
:return: returns a filename path
:rtype: str
"""
if isinstance(model, types.StringTypes):
if isinstance(model, str):
model = self.get_model(model)

filename = model.Meta.filename
Expand All @@ -146,7 +146,7 @@ def set_storage(self, model, storage=None):
:param IStorage storage: override model storage class
:rtype: :class:`alkali.storage.Storage` instance or None
"""
if isinstance(model, types.StringTypes):
if isinstance(model, str):
model = self.get_model(model)

storage = storage or model.Meta.storage or self._storage_type
Expand All @@ -165,7 +165,7 @@ def get_storage(self, model):
:param model: the model name or model class
:rtype: :class:`alkali.storage.Storage` instance or None
"""
if isinstance(model, types.StringTypes):
if isinstance(model, str):
model = self.get_model(model)

try:
Expand Down
19 changes: 13 additions & 6 deletions alkali/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def __init__(self, field_type, **kw):
* primary_key: is this field a primary key of parent model
* indexed: is this field indexed (not implemented yet)
"""
self._order = Field._counter.next() # DO NOT TOUCH, deleted in MetaModel
self._order = next(Field._counter) # DO NOT TOUCH, deleted in MetaModel

assert field_type is not None
self._field_type = field_type
Expand Down Expand Up @@ -165,7 +165,7 @@ def cast(self, value):
if value is None:
return None

if isinstance(value, types.StringTypes):
if isinstance(value, str):
# if value is an empty string then consider that as not yet
# set vs False. This may be a mistake.
if not value:
Expand All @@ -189,7 +189,7 @@ class StringField(Field):
"""

def __init__(self, **kw):
super(StringField, self).__init__(unicode, **kw)
super(StringField, self).__init__(str, **kw)

def cast(self, value):
if value is None:
Expand Down Expand Up @@ -219,7 +219,7 @@ def cast(self, value):
if value is None:
return None

if isinstance(value, types.StringTypes):
if isinstance(value, str):
if value == 'now':
value = tznow()
else:
Expand All @@ -240,7 +240,7 @@ def loads(cls, value):
return None

# assume date is in isoformat, this preserves timezone info
if isinstance(value, types.StringTypes):
if isinstance(value, str):
value = dateutil.parser.parse(value)

if value.tzinfo is None:
Expand Down Expand Up @@ -273,7 +273,7 @@ def __init__(self, foreign_model, **kw):
from .model import Model

# TODO treat foreign_model as model name and lookup in database
# if isinstance(foreign_model, types.StringTypes):
# if isinstance(foreign_model, str):
# foreign_model = <db>.get_model(foreign_model)

self.foreign_model = foreign_model
Expand Down Expand Up @@ -342,6 +342,13 @@ def dumps(self, value):


class OneToOneField(ForeignKey):
"""
"""
# TODO maybe use reify
# https://docs.pylonsproject.org/projects/pyramid/en/latest/_modules/pyramid/decorator.html#reify
# I forsee a problem where you load the primary table and either
# create the OneToOneField table entries and replace the as the real file is loaded
# or maybe you have some wierd race condition in the other direction
pass

# TODO class ManyToManyField
13 changes: 4 additions & 9 deletions alkali/manager.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
from zope.interface import Interface, implements
import inspect
import copy

from .query import Query
from .storage import IStorage
from . import fields
from . import signals

import logging
logger = logging.getLogger(__name__)

class IManager( Interface ):
pass

class Manager(object):
"""
the ``Manager`` class is the parent/owner of all the
:class:`alkali.model.Model` instances. Each ``Model`` has it's own
manager. ``Manager`` could rightly be called ``Table``.
"""
implements(IManager)

def __init__( self, model_class ):
"""
Expand Down Expand Up @@ -70,7 +65,7 @@ def pks(self):
:rtype: ``list``
"""
return self._instances.keys()
return list(self._instances.keys())

@property
def instances(self):
Expand All @@ -79,7 +74,7 @@ def instances(self):
:rtype: ``list``
"""
return map( copy.copy, self._instances.itervalues() )
return [copy.copy(obj) for obj in self._instances.values()]

@property
def dirty(self):
Expand All @@ -101,7 +96,7 @@ def sorter(elements, reverse=False ):
* reverse: return in reverse order
:rtype: ``generator``
"""
for key in sorted( elements.iterkeys(), reverse=reverse ):
for key in sorted(elements.keys(), reverse=reverse):
yield elements[key]

def save(self, instance, dirty=True, copy_instance=True):
Expand Down Expand Up @@ -244,7 +239,7 @@ def validate_fk_fields(fk_fields, elem):
return True

assert not inspect.isclass(storage)
storage = IStorage(storage)
storage = storage
logger.debug( "%s: loading models via storage class: %s", self._name, storage._name )

signals.pre_load.send(self.model_class)
Expand Down
2 changes: 1 addition & 1 deletion alkali/memoized_property.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.__doc__ = doc

if fget is not None:
self._attr_name = '___'+fget.func_name
self._attr_name = '___' + fget.__name__

def __get__(self, inst, type=None):
if inst is None:
Expand Down
19 changes: 15 additions & 4 deletions alkali/metamodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def _get_field_order(attrs):
fields.sort(key=lambda e: e[1]._order)
return [k for k, _ in fields]

class Object(object):
class Object():
pass

# Meta is an instance in Model class
Expand Down Expand Up @@ -175,6 +175,17 @@ class Object(object):
[(name, field) for name, field in meta.fields.items() if field.primary_key]
)

# monkey patch stupid fucking iterators
meta.pk_fields._keys = meta.pk_fields.keys
meta.pk_fields.keys = lambda: list(meta.pk_fields._keys())
meta.pk_fields._values = meta.pk_fields.values
meta.pk_fields.values = lambda: list(meta.pk_fields._values())

meta.fields._keys = meta.fields.keys
meta.fields.keys = lambda: list(meta.fields._keys())
meta.fields._values = meta.fields.values
meta.fields.values = lambda: list(meta.fields._values())

if len(meta.fields):
assert len(meta.pk_fields) > 0, "no primary_key defined in fields"

Expand All @@ -185,7 +196,7 @@ def _add_fields( new_class ):
meta = new_class.Meta

# add properties to field
for name, field in meta.fields.iteritems():
for name, field in meta.fields.items():
field._name = name
fget = lambda self: getattr(self, '_name')
setattr( field.__class__, 'name', property(fget=fget) )
Expand All @@ -198,7 +209,7 @@ def _add_fields( new_class ):
setattr( field.__class__, 'meta', property(fget=fget) )

# put fields in model
for name, field in meta.fields.iteritems():
for name, field in meta.fields.items():
# make magic property model.fieldname_field that returns Field object
fget = lambda self, name=name: self.Meta.fields[name]
setattr( new_class, name + '__field', property(fget=fget) )
Expand Down Expand Up @@ -226,7 +237,7 @@ def __call__(cls, *args, **kw):
kw[field_name] = value

# put field values (int,str,etc) into model instance
for name, field in cls.Meta.fields.iteritems():
for name, field in cls.Meta.fields.items():
# THINK: this somewhat duplicates Field.__set__ code
value = kw.pop(name, field.default_value)
value = field.cast(value)
Expand Down
16 changes: 8 additions & 8 deletions alkali/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ObjectDoesNotExist(Exception):
pass


class Model(object):
class Model(metaclass=MetaModel):
"""
main class for the database.
Expand All @@ -29,12 +29,11 @@ class Model(object):
see :mod:`alkali.database` for some example code
"""
__metaclass__ = MetaModel

def __init__(self, *args, **kw):
# MetaModel.__call__ has put fields in self,
# put any other keywords into self
for name, value in kw.iteritems():
for name, value in kw.items():
setattr(self, name, value)

# note, this is called twice, once during initial object creation
Expand Down Expand Up @@ -101,25 +100,26 @@ def schema(self):
def fmt(name, field):
return "{}:{}".format(name, field.field_type.__name__)

name_type = [ fmt(n, f) for n, f in self.Meta.fields.iteritems() ]
name_type = [ fmt(n, f) for n, f in self.Meta.fields.items() ]
fields = ", ".join( name_type )
return "<{}: {}>".format(self.__class__.__name__, fields)

@memoized_property
def pk(self):
"""
**property**: returns this models primary key value. If the model is
comprised of serveral primary keys then return a tuple of them.
comprised of several primary keys then return a tuple of them.
:rtype: ``Field.field_type`` or tuple-of-Field.field_type
"""
pks = self.Meta.pk_fields.values()
foreign_pks = filter(lambda f: isinstance(f, fields.ForeignKey), pks)
pks = list(self.Meta.pk_fields.values())
foreign_pks = list(filter(lambda f: isinstance(f, fields.ForeignKey), pks))

if foreign_pks:
pk_vals = tuple( getattr(self, f.name).pk for f in pks )
if len(pk_vals) == 1:
return pk_vals[0]
assert False, "not actually supported at this time"
return pk_vals # pragma: nocover, not actually supported at this time
else:
pk_vals = tuple( getattr(self, f.name) for f in pks )
Expand All @@ -144,7 +144,7 @@ def dict(self):
:rtype: ``OrderedDict``
"""
return OrderedDict( [(name, field.dumps(getattr(self, name)))
for name, field in self.Meta.fields.iteritems() ])
for name, field in self.Meta.fields.items() ])

@property
def json(self):
Expand Down
Loading

0 comments on commit 3957ab3

Please sign in to comment.