Skip to content

Commit

Permalink
Required fields (#1)
Browse files Browse the repository at this point in the history
Minor: fixes to initialization
  • Loading branch information
tilalis authored Feb 19, 2018
1 parent f1a8ad0 commit 80ccd43
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 63 deletions.
20 changes: 12 additions & 8 deletions _adapters/firebase.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,18 @@ def create(path, key, value):

@staticmethod
def update(path, key, value: dict):
FirebaseAdapter._execute(
lambda: db.reference(
path=FirebaseAdapter._path(path, key)
).update({
k: v
for k, v in value.items() if value is not None
})
)
no_none = {
k: v
for k, v in value.items()
if v is not None
}

if no_none:
FirebaseAdapter._execute(
lambda: db.reference(
path=FirebaseAdapter._path(path, key)
).update(no_none)
)

@staticmethod
def delete(path, key):
Expand Down
27 changes: 11 additions & 16 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,34 +26,20 @@ class IncidentPointer(PresentationDocument):
incident_id = StringField(id=True)
owner = StringField()

@classmethod
def presentation(cls, document):
return {
'incident_id': document.incident_id,
'owner': document.owner
}


class OngoingIncidentPointer(PresentationDocument):
_container = '/ongoingEvents'

incident_id = StringField(id=True)
owner = StringField()

@classmethod
def presentation(cls, document):
return {
'incident_id': document.incident_id,
'owner': document.owner
}


class Incident(Document):
_container = '/incidents'

incident_id = StringField(id=True, presentation=True)
confirmed = BooleanField(default=False)
action = StringField()
action = StringField(required=True)
firstResponder = StringField(presentation=True)
owner = StringField(presentation=True)
reliability = IntField(default=0, presentation=True)
Expand All @@ -62,6 +48,9 @@ class Incident(Document):

@staticmethod
def on_save(document):
if 'confirmed' not in document.__changed__:
return

ip = IncidentPointer(
**document,
ignore_non_existing=True
Expand Down Expand Up @@ -114,8 +103,9 @@ class Type(Enum):
UPDATE_PRESENTATION = 3
UPDATE_CACHE = 4
UPDATE_FULL = 5
UPDATE_CACHE_ONLY = 6

action = Type.UPDATE_PRESENTATION
action = Type.CREATE

for i in range(0, 20):
if action == Type.CREATE:
Expand All @@ -125,6 +115,7 @@ class Type(Enum):
reliability=i % 3,
confidence=i * 2,
confirmed=not bool(i % 2),
action='create',
created=datetime.datetime.utcnow()
).save()
elif action == Type.DELETE:
Expand All @@ -139,5 +130,9 @@ class Type(Enum):
with Incident.get(i) as incident:
incident.firstResponder = 'AAB'
incident.action = 'cleared'
elif action == Type.UPDATE_CACHE_ONLY:
with Incident.get(i) as incident:
incident.action = "hey"



23 changes: 19 additions & 4 deletions orm/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
from _adapters import RedisAdapter, FirebaseAdapter

_connections = {}
_connections = {
'redis': None,
'firebase': None
}

_connection_adapters = {
'redis': RedisAdapter,
'firebase': FirebaseAdapter
}


def adapters(redis, firebase):
_connection_adapters['redis'] = redis
_connection_adapters['firebase'] = firebase


def connect(redis: dict, firebase: dict):
_connections['redis'] = RedisAdapter(**redis)
_connections['firebase'] = FirebaseAdapter(**firebase)
redis_adapter, firebase_adapter = _connection_adapters['redis'], _connection_adapters['firebase']

_connections['redis'] = redis_adapter(**redis)
_connections['firebase'] = firebase_adapter(**firebase)


def get_connections():
def connections():
return _connections['redis'], _connections['firebase']
101 changes: 67 additions & 34 deletions orm/document.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import abc
import copy

from abc import abstractmethod

from .fields import BaseField
from _adapters import RedisAdapter, FirebaseAdapter

from orm import get_connections
from orm import connections, adapters


class DocumentMetaclass(abc.ABCMeta):
Expand All @@ -17,9 +14,11 @@ def __new__(mcs, name, bases, attrs):
firebase_props = conn.get('firebase', {})
attrs['_container'] = firebase_props.pop('container', '')

redis_adapter, firebase_adapter = adapters()

attrs.update({
'_redis': RedisAdapter(**redis_props) if redis_props else None,
'_firebase': FirebaseAdapter(**firebase_props) if firebase_props else None
'_redis': redis_adapter(**redis_props) if redis_props else None,
'_firebase': firebase_adapter(**firebase_props) if firebase_props else None
})

fields = {}
Expand All @@ -38,21 +37,23 @@ def __new__(mcs, name, bases, attrs):
presentation_fields.add(fieldname)

if field.is_id:
id_field = copy.copy(field)
id_field._presentation = False
id_field._id = False
fields['id'] = id_field
fields['id'] = field

del attrs[fieldname]

attrs['__fields__'] = fields
attrs['_required_fields'] = set(
name for name, field in fields.items()
if field.required
)

# If we have "presentation" in class name, then all of its fields are presentational
# If we class with "presentation" in bases, then all of its fields are presentational
# Should think of a better way
if "presentation" not in name.lower():
attrs['_presentation_fields'] = presentation_fields
else:

if any("presentation" in repr(base).lower() for base in bases):
attrs['_presentation_fields'] = set(fieldnames)
else:
attrs['_presentation_fields'] = presentation_fields

return super().__new__(mcs, name, bases, attrs)

Expand All @@ -61,49 +62,79 @@ class Document(metaclass=DocumentMetaclass):
def __new__(cls, *args, **kwargs):
document = super().__new__(cls)

fields = copy.deepcopy(cls.__fields__)

if not ({'_redis', '_firebase'} <= cls.__dict__.keys()):
redis, firebase = get_connections()
redis, firebase = connections()
if not redis and not firebase:
raise ConnectionError('Connections to Firebase and Redis not found')

# TODO: Make them class-level instead of instance-level (Maybe move to metaclass)
object.__setattr__(document, '_redis', redis)
object.__setattr__(document, '_firebase', firebase)

document_id = None
for name, field in fields.items():
if name in kwargs:
field.value = kwargs[name]
return document

if not document_id and field.is_id:
document_id = field.value
def __init__(self, ignore_non_existing=False, override=False, **kwargs):
cls = self.__class__
fields = copy.deepcopy(cls.__fields__)
required_fields = cls._required_fields

object.__setattr__(document, '_id', document_id)
object.__setattr__(document, '_fields', fields)
document_id, filled_fields = self._init_fields(fields, ignore_non_existing=ignore_non_existing, **kwargs)

return document
if document_id is None:
raise LookupError("Document without id!")

def __init__(self, ignore_non_existing=False, override=False, **kwargs):
self._ignore_non_existing = ignore_non_existing
self._path = "{}/".format(self._container)
self._fetched = override
self._changed = set()
if (filled_fields & required_fields) != required_fields:
raise AttributeError("The following fields are required: {}".format(
required_fields - filled_fields
))

self._init_options(ignore_non_existing=ignore_non_existing, override=override)

@classmethod
def get(cls, id):
if id is None:
raise AttributeError("Document must have id field!")

document = cls(id=id)
document = cls.__new__(cls)
document._init_fields(cls.__fields__)
document._init_options(
ignore_non_existing=False,
override=False
)

if document._redis.exists(id):
object.__setattr__(document, '_id', id)
return document._fetch()

raise LookupError('No such document with id: {}'.format(id))

def _init_fields(self, fields, ignore_non_existing=False, **kwargs):
filled_fields = set()
for name, value in kwargs.items():
field = fields.get(name)

if field is None:
if ignore_non_existing:
continue

raise AttributeError("No such field {}".format(name))

field.value = value
filled_fields.add(name)

document_id = fields['id'].value

object.__setattr__(self, '_id', document_id)
object.__setattr__(self, '_fields', fields)

return document_id, filled_fields

def _init_options(self, ignore_non_existing, override):
self._ignore_non_existing = ignore_non_existing
self._path = "{}/".format(self._container)
self._fetched = override
self._changed = set(self._fields.keys())

def _fetch(self):
entity = self._redis.read(self._id)
for key, value in entity.items():
Expand All @@ -113,6 +144,8 @@ def _fetch(self):
raise AttributeError("There is no such field as {}".format(key))

self._fetched = True
self._changed = set()

return self

def __setattr__(self, key, value):
Expand Down Expand Up @@ -146,7 +179,7 @@ def save(self, force=False):
return

if not self._fetched and exists:
raise Exception("Document already exists!")
raise Exception("Document with id {} already exists!".format(self._id))

document_view = DocumentView(self)

Expand Down Expand Up @@ -183,7 +216,7 @@ def delete(self):
def presentation(cls, document):
return {
key: value
for key, value in document
for key, value in document.items()
if key in cls._presentation_fields
}

Expand Down
8 changes: 7 additions & 1 deletion orm/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ def __new__(mcs, name, bases, namespace, **kwargs):
class BaseField(metaclass=FieldMeta):
__field_type__ = None

def __init__(self, id=False, default=None, presentation=False):
def __init__(self, id=False, default=None, presentation=False, required=False):
# TODO: add immutable option, make ids immutable by default
# TODO: add `name` option
self._id = id
self._presentation = presentation
self._required = required

if default is None or isinstance(default, self.__field_type__):
self._value = default
Expand All @@ -27,6 +29,10 @@ def __init__(self, id=False, default=None, presentation=False):
def is_id(self):
return self._id

@property
def required(self):
return self._required

@property
def presentation(self):
return self._presentation
Expand Down

0 comments on commit 80ccd43

Please sign in to comment.