Skip to content

Allow subclassing of SQLAlchemyObjectType without major API changes #51

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,38 @@ query = '''
result = schema.execute(query, context_value={'session': db_session})
```

You may also subclass SQLAlchemyObjectType by providing `abstract = True` in
your subclasses Meta:
```python
from graphene_sqlalchemy import SQLAlchemyObjectType

class ActiveSQLAlchemyObjectType(SQLAlchemyObjectType):
class Meta:
abstract = True

@classmethod
def get_node(cls, id, context, info):
return cls.get_query(context).\
filter(and_(
cls._meta.model.deleted_at==None,
cls._meta.model.id==id,
)).\
first()

class User(ActiveSQLAlchemyObjectType):
class Meta:
model = UserModel

class Query(graphene.ObjectType):
users = graphene.List(User)

def resolve_users(self, args, context, info):
query = User.get_query(context) # SQLAlchemy query
return query.all()

schema = graphene.Schema(query=Query)
```

To learn more check out the following [examples](examples/):

* **Full example**: [Flask SQLAlchemy example](examples/flask_sqlalchemy)
Expand Down
9 changes: 5 additions & 4 deletions graphene_sqlalchemy/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ def __init__(self):
self._registry_composites = {}

def register(self, cls):
from .types import SQLAlchemyObjectType
assert issubclass(
cls, SQLAlchemyObjectType), 'Only SQLAlchemyObjectType can be registered, received "{}"'.format(
cls.__name__)
from .types import SQLAlchemyObjectTypeMeta
assert issubclass(type(cls), SQLAlchemyObjectTypeMeta), (
'Only classes of type SQLAlchemyObjectTypeMeta can be registered, ',
'received "{}"'
).format(cls.__name__)
assert cls._meta.registry == self, 'Registry for a Model have to match.'
# assert self.get_type_for_model(cls._meta.model) in [None, cls], (
# 'SQLAlchemy model "{}" already associated with '
Expand Down
31 changes: 30 additions & 1 deletion graphene_sqlalchemy/tests/test_types.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@

from graphene import Field, Int, Interface, ObjectType
from graphene.relay import Node, is_node
import six

from ..registry import Registry
from ..types import SQLAlchemyObjectType
from ..types import SQLAlchemyObjectType, SQLAlchemyObjectTypeMeta
from .models import Article, Reporter

registry = Registry()
Expand Down Expand Up @@ -74,3 +75,31 @@ def test_object_type():
assert issubclass(Human, ObjectType)
assert list(Human._meta.fields.keys()) == ['id', 'headline', 'reporter_id', 'reporter', 'pub_date']
assert is_node(Human)



# Test Custom SQLAlchemyObjectType Implementation
class CustomSQLAlchemyObjectType(SQLAlchemyObjectType):
class Meta:
abstract = True


class CustomCharacter(CustomSQLAlchemyObjectType):
'''Character description'''
class Meta:
model = Reporter
registry = registry


def test_custom_objecttype_registered():
assert issubclass(CustomCharacter, ObjectType)
assert CustomCharacter._meta.model == Reporter
assert list(
CustomCharacter._meta.fields.keys()) == [
'id',
'first_name',
'last_name',
'email',
'pets',
'articles',
'favorite_article']
12 changes: 9 additions & 3 deletions graphene_sqlalchemy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,15 @@ def __new__(cls, name, bases, attrs):
exclude_fields=(),
id='id',
interfaces=(),
registry=None
registry=None,
abstract=False,
)

cls = ObjectTypeMeta.__new__(cls, name, bases, dict(attrs, _meta=options))

if options.abstract:
return cls

if not options.registry:
options.registry = get_global_registry()
assert isinstance(options.registry, Registry), (
Expand All @@ -96,8 +102,6 @@ def __new__(cls, name, bases, attrs):
'{}.Meta, received "{}".'
).format(name, options.model)

cls = ObjectTypeMeta.__new__(cls, name, bases, dict(attrs, _meta=options))

options.registry.register(cls)

options.sqlalchemy_fields = yank_fields_from_attrs(
Expand All @@ -115,6 +119,8 @@ def __new__(cls, name, bases, attrs):


class SQLAlchemyObjectType(six.with_metaclass(SQLAlchemyObjectTypeMeta, ObjectType)):
class Meta:
abstract = True

@classmethod
def is_type_of(cls, root, context, info):
Expand Down
17 changes: 17 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,20 @@ omit = */tests/*

[isort]
known_first_party=graphene,graphene_sqlalchemy

[tool:pytest]
testpaths = graphene_sqlalchemy/
addopts =
-s
; --cov graphene-sqlalchemy
norecursedirs =
__pycache__
*.egg-info
.cache
.git
.tox
appdir
docs
filterwarnings =
error
ignore::DeprecationWarning