Skip to content

Commit

Permalink
[GH-4][WIP] Static type annotations + mypy checks on CI
Browse files Browse the repository at this point in the history
  • Loading branch information
onyb committed Oct 1, 2017
1 parent 0c34c41 commit 4234a81
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 41 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,4 @@ pip-selfcheck.json
# End of https://www.gitignore.io/api/python,pycharm,linux,virtualenv

README.rst
.mypy_cache
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ script:
- py.test tests --cov=./reobject
- PYTHONPATH=`pwd` py.test examples/*

after_success: codecov
after_success:
- codecov
- mypy reobject || true
10 changes: 5 additions & 5 deletions reobject/models/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class ManagerDescriptor(object):
def __init__(self):
self.manager = None

def __get__(self, instance, owner):
def __get__(self, instance, owner) -> 'Manager':
if instance is not None:
raise AttributeError(
"Manager isn't accessible via %s instances" % owner.__name__
Expand All @@ -30,7 +30,7 @@ class RelatedManagerDescriptor(object):
def __init__(self, model):
self.model = model

def __get__(self, instance, owner):
def __get__(self, instance, owner)-> 'Manager':
if instance is None:
raise AttributeError(
"RelatedManager isn't accessible via %s class" % owner.__name__
Expand Down Expand Up @@ -72,13 +72,13 @@ def __contains__(self, item):
def store(self):
return ModelStoreMapping.get(self.model.__name__)

def get_queryset(self):
def get_queryset(self) -> QuerySet:
return QuerySet(
self.store,
model=self.model
)

def add(self, instance: object) -> object:
def add(self, instance: 'Model') -> 'Model':
if not isinstance(instance, self.model):
raise TypeError(
'{model} instance expected, got {obj}'.format(
Expand Down Expand Up @@ -165,7 +165,7 @@ def none(self) -> EmptyQuerySet:
"""
return EmptyQuerySet(model=self.model)

def random(self) -> object:
def random(self) -> 'Model':
"""
Returns a random model instance.
"""
Expand Down
2 changes: 1 addition & 1 deletion reobject/models/store.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ModelStoreMapping = dict()
ModelStoreMapping: dict = dict()


class Store(list):
Expand Down
11 changes: 6 additions & 5 deletions reobject/query/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from reobject.utils import cmp

from ..types import LookupParams

class _Q(object):
verbs = (
Expand All @@ -25,7 +26,7 @@ class _Q(object):
'startswith'
)

def __init__(self, **kwargs):
def __init__(self, **kwargs: LookupParams) -> None:
if kwargs:
attr, self.value = list(kwargs.items())[0]

Expand All @@ -47,13 +48,13 @@ def _comparator_func(self, obj):
else:
return value == self.value

def __and__(self, other):
def __and__(self, other: '_Q') -> '_Q':
new = type(self)()
new.comparator = lambda obj: self.comparator(obj) and \
other.comparator(obj)
return new

def __or__(self, other):
def __or__(self, other: '_Q') -> '_Q':
new = type(self)()
new.comparator = lambda obj: self.comparator(obj) or \
other.comparator(obj)
Expand Down Expand Up @@ -127,15 +128,15 @@ def apply_verb(self, value):


class Q(object):
def __new__(cls, *args, **kwargs):
def __new__(cls, *args, **kwargs: LookupParams) -> _Q:
return reduce(
operator.and_,
map(lambda k: _Q(**{k: kwargs[k]}), kwargs),
_Q()
)

@classmethod
def from_Qs(cls, *args):
def from_Qs(cls, *args) -> _Q:
return reduce(
operator.and_,
args,
Expand Down
65 changes: 36 additions & 29 deletions reobject/query/queryset.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import random
from collections import OrderedDict
from itertools import chain
from typing import Tuple, Optional as Maybe, Dict, Any, Iterable

from reobject.exceptions import DoesNotExist, MultipleObjectsReturned
from reobject.query import Q
from reobject.query.parser import Q
from reobject.utils import cmp, flatmap

from ..types import LookupParams, Fields


class QuerySet(list):
def __init__(self, *args, model, **kwargs):
Expand All @@ -16,16 +19,16 @@ def __init__(self, *args, model, **kwargs):
def _attrs(self):
return self[0]._attrs if self.exists() else set()

def __or__(self, other):
def __or__(self, other: 'QuerySet') -> 'QuerySet':
return type(self)(
chain(self, other),
model=self.model
).distinct('id')

def count(self):
def count(self) -> int: # type: ignore
return len(self)

def delete(self):
def delete(self) -> Tuple[int, dict]:
_len = self.count()
_type = self.model.__name__

Expand All @@ -34,12 +37,12 @@ def delete(self):

return _len, {_type: _len}

def distinct(self, *attrs):
if not attrs:
attrs = self._attrs - {'created', 'updated'}
def distinct(self, *fields: Fields) -> 'QuerySet':
if not fields:
fields = self._attrs - {'created', 'updated'}

meta = [
(cmp(*attrs)(obj), obj)
(cmp(*fields)(obj), obj)
for obj in self.reverse()
]

Expand All @@ -48,44 +51,46 @@ def distinct(self, *attrs):
model=self.model
).reverse()

def earliest(self, field_name='created'):
def earliest(self, field_name: str = 'created') -> Maybe['Model']:
try:
obj = self.filter(
**{field_name + '__isnone': False}
).order_by(field_name)[0]
).order_by(
field_name
)[0]
except IndexError:
return None
else:
return obj

def exclude(self, *args, **kwargs):
def exclude(self, *args: Tuple[Q, ...], **kwargs: LookupParams) -> 'QuerySet':
q = ~(Q.from_Qs(*args) & Q(**kwargs))

return type(self)(
filter(q.comparator, self),
model=self.model
)

def exists(self):
def exists(self) -> bool:
return bool(self)

def filter(self, *args, **kwargs):
def filter(self, *args: Tuple[Q, ...], **kwargs: LookupParams) -> 'QuerySet':
q = Q.from_Qs(*args) & Q(**kwargs)

return type(self)(
filter(q.comparator, self),
model=self.model
)

def first(self):
def first(self) -> Maybe['Model']: # type: ignore
try:
obj = self[0]
except IndexError:
return None
else:
return obj

def get(self, *args, **kwargs):
def get(self, *args: Tuple[Q, ...], **kwargs: LookupParams):
result_set = self.filter(*args, **kwargs)

if len(result_set) == 0:
Expand All @@ -105,7 +110,7 @@ def get(self, *args, **kwargs):
)
)

def get_or_create(self, defaults=None, **kwargs):
def get_or_create(self, defaults=None, **kwargs: LookupParams):
try:
obj = self.get(**kwargs)
except DoesNotExist:
Expand All @@ -119,59 +124,61 @@ def get_or_create(self, defaults=None, **kwargs):
else:
return obj, False

def last(self):
def last(self) -> Maybe['Model']: # type: ignore
try:
obj = self[-1]
except IndexError:
return None
else:
return obj

def latest(self, field_name='created'):
def latest(self, field_name: str = 'created') -> Maybe['Model']: # type: ignore
try:
obj = self.filter(
**{field_name + '__isnone': False}
).order_by(field_name)[-1]
).order_by(
field_name
)[-1]
except IndexError:
return None
else:
return obj

def map(self, func):
def map(self, func) -> Iterable:
if not callable(func):
raise TypeError(
'Expected a callable, got {}'.format(type(func))
)

return map(func, self)

def none(self):
def none(self) -> 'EmptyQuerySet':
return EmptyQuerySet(model=self.model)

def order_by(self, *attrs):
if not attrs:
def order_by(self, *fields: Fields):
if not fields:
raise AttributeError

return type(self)(
sorted(self, key=cmp(*attrs)),
sorted(self, key=cmp(*fields)),
model=self.model
)

def random(self):
def random(self) -> Maybe['Model']: # type: ignore
try:
obj = random.choice(self)
except IndexError:
return None
else:
return obj

def reverse(self):
def reverse(self) -> 'QuerySet':
return type(self)(
reversed(self),
model=self.model
)

def values(self, *fields):
def values(self, *fields: Fields) -> 'QuerySet':
if not fields:
fields = self._attrs

Expand All @@ -183,7 +190,7 @@ def values(self, *fields):
model=self.model
)

def values_list(self, *fields, flat=False):
def values_list(self, *fields: Fields, flat: bool=False) -> 'QuerySet':
# TODO: Allow order_by on values_list

if not fields:
Expand All @@ -202,6 +209,6 @@ def values_list(self, *fields, flat=False):


class EmptyQuerySet(QuerySet):
def __init__(self, model, *args, **kwargs):
def __init__(self, model: 'Model', *args, **kwargs) -> None: # type: ignore
super(QuerySet, self).__init__(*args, **kwargs)
self.model = model
5 changes: 5 additions & 0 deletions reobject/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from typing import Dict, Any, Tuple, Union

LookupParams = Dict[str, Any]
Fields = Union[Tuple[str, ...], str]

1 change: 1 addition & 0 deletions test-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pytest
pytest-cov
codecov
mypy

0 comments on commit 4234a81

Please sign in to comment.