Update upstream source from tag 'upstream/2.0.0'
Update to upstream version '2.0.0'
with Debian dir 88a101edd32f1c703600b3c3e77c1d95c23f796f
Sophie Brun
6 years ago
3 | 3 | - 2.7 |
4 | 4 | - 3.4 |
5 | 5 | - 3.5 |
6 | - pypy | |
6 | - 3.6 | |
7 | 7 | before_install: |
8 | - | | |
9 | if [ "$TRAVIS_PYTHON_VERSION" = "pypy" ]; then | |
10 | export PYENV_ROOT="$HOME/.pyenv" | |
11 | if [ -f "$PYENV_ROOT/bin/pyenv" ]; then | |
12 | cd "$PYENV_ROOT" && git pull | |
13 | else | |
14 | rm -rf "$PYENV_ROOT" && git clone --depth 1 https://github.com/yyuu/pyenv.git "$PYENV_ROOT" | |
15 | fi | |
16 | export PYPY_VERSION="4.0.1" | |
17 | "$PYENV_ROOT/bin/pyenv" install "pypy-$PYPY_VERSION" | |
18 | virtualenv --python="$PYENV_ROOT/versions/pypy-$PYPY_VERSION/bin/python" "$HOME/virtualenvs/pypy-$PYPY_VERSION" | |
19 | source "$HOME/virtualenvs/pypy-$PYPY_VERSION/bin/activate" | |
20 | fi | |
21 | 8 | install: |
22 | 9 | - | |
23 | 10 | if [ "$TEST_TYPE" = build ]; then |
56 | 43 | tags: true |
57 | 44 | password: |
58 | 45 | secure: q0ey31cWljGB30l43aEd1KIPuAHRutzmsd2lBb/2zvD79ReBrzvCdFAkH2xcyo4Volk3aazQQTNUIurnTuvBxmtqja0e+gUaO5LdOcokVdOGyLABXh7qhd2kdvbTDWgSwA4EWneLGXn/SjXSe0f3pCcrwc6WDcLAHxtffMvO9gulpYQtUoOqXfMipMOkRD9iDWTJBsSo3trL70X1FHOVr6Yqi0mfkX2Y/imxn6wlTWRz28Ru94xrj27OmUnCv7qcG0taO8LNlUCquNFAr2sZ+l+U/GkQrrM1y+ehPz3pmI0cCCd7SX/7+EG9ViZ07BZ31nk4pgnqjmj3nFwqnCE/4IApGnduqtrMDF63C9TnB1TU8oJmbbUCu4ODwRpBPZMnwzaHsLnrpdrB89/98NtTfujdrh3U5bVB+t33yxrXVh+FjgLYj9PVeDixpFDn6V/Xcnv4BbRMNOhXIQT7a7/5b99RiXBjCk6KRu+Jdu5DZ+3G4Nbr4oim3kZFPUHa555qbzTlwAfkrQxKv3C3OdVJR7eGc9ADsbHyEJbdPNAh/T+xblXTXLS3hPYDvgM+WEGy3CytBDG3JVcXm25ZP96EDWjweJ7MyfylubhuKj/iR1Y1wiHeIsYq9CqRrFQUWL8gFJBfmgjs96xRXXXnvyLtKUKpKw3wFg5cR/6FnLeYZ8k= |
46 | distributions: "sdist bdist_wheel" |
0 | Please read [UPGRADE-v1.0.md](https://github.com/graphql-python/graphene/blob/master/UPGRADE-v1.0.md) | |
1 | to learn how to upgrade to Graphene `1.0`. | |
0 | Please read [UPGRADE-v2.0.md](https://github.com/graphql-python/graphene/blob/master/UPGRADE-v2.0.md) | |
1 | to learn how to upgrade to Graphene `2.0`. | |
2 | 2 | |
3 | 3 | --- |
4 | 4 | |
12 | 12 | For instaling graphene, just run this command in your shell |
13 | 13 | |
14 | 14 | ```bash |
15 | pip install "graphene-sqlalchemy>=1.0" | |
15 | pip install "graphene-sqlalchemy>=2.0" | |
16 | 16 | ``` |
17 | 17 | |
18 | 18 | ## Examples |
46 | 46 | class Query(graphene.ObjectType): |
47 | 47 | users = graphene.List(User) |
48 | 48 | |
49 | def resolve_users(self, args, context, info): | |
50 | query = User.get_query(context) # SQLAlchemy query | |
49 | def resolve_users(self, info): | |
50 | query = User.get_query(info) # SQLAlchemy query | |
51 | 51 | return query.all() |
52 | 52 | |
53 | 53 | schema = graphene.Schema(query=Query) |
65 | 65 | } |
66 | 66 | ''' |
67 | 67 | result = schema.execute(query, context_value={'session': db_session}) |
68 | ``` | |
69 | ||
70 | You may also subclass SQLAlchemyObjectType by providing `abstract = True` in | |
71 | your subclasses Meta: | |
72 | ```python | |
73 | from graphene_sqlalchemy import SQLAlchemyObjectType | |
74 | ||
75 | class ActiveSQLAlchemyObjectType(SQLAlchemyObjectType): | |
76 | class Meta: | |
77 | abstract = True | |
78 | ||
79 | @classmethod | |
80 | def get_node(cls, info, id): | |
81 | return cls.get_query(info).filter( | |
82 | and_(cls._meta.model.deleted_at==None, | |
83 | cls._meta.model.id==id) | |
84 | ).first() | |
85 | ||
86 | class User(ActiveSQLAlchemyObjectType): | |
87 | class Meta: | |
88 | model = UserModel | |
89 | ||
90 | class Query(graphene.ObjectType): | |
91 | users = graphene.List(User) | |
92 | ||
93 | def resolve_users(self, info): | |
94 | query = User.get_query(info) # SQLAlchemy query | |
95 | return query.all() | |
96 | ||
97 | schema = graphene.Schema(query=Query) | |
68 | 98 | ``` |
69 | 99 | |
70 | 100 | To learn more check out the following [examples](examples/): |
0 | 0 | Please read |
1 | `UPGRADE-v1.0.md <https://github.com/graphql-python/graphene/blob/master/UPGRADE-v1.0.md>`__ | |
2 | to learn how to upgrade to Graphene ``1.0``. | |
1 | `UPGRADE-v2.0.md <https://github.com/graphql-python/graphene/blob/master/UPGRADE-v2.0.md>`__ | |
2 | to learn how to upgrade to Graphene ``2.0``. | |
3 | 3 | |
4 | 4 | -------------- |
5 | 5 | |
16 | 16 | |
17 | 17 | .. code:: bash |
18 | 18 | |
19 | pip install "graphene-sqlalchemy>=1.0" | |
19 | pip install "graphene-sqlalchemy>=2.0" | |
20 | 20 | |
21 | 21 | Examples |
22 | 22 | -------- |
52 | 52 | class Query(graphene.ObjectType): |
53 | 53 | users = graphene.List(User) |
54 | 54 | |
55 | def resolve_users(self, args, context, info): | |
56 | query = User.get_query(context) # SQLAlchemy query | |
55 | def resolve_users(self, info): | |
56 | query = User.get_query(info) # SQLAlchemy query | |
57 | 57 | return query.all() |
58 | 58 | |
59 | 59 | schema = graphene.Schema(query=Query) |
94 | 94 | from graphene_sqlalchemy import SQLAlchemyObjectType, SQLAlchemyConnectionField |
95 | 95 | from models import db_session, Department as DepartmentModel, Employee as EmployeeModel |
96 | 96 | |
97 | schema = graphene.Schema() | |
98 | ||
99 | 97 | |
100 | 98 | class Department(SQLAlchemyObjectType): |
101 | 99 | class Meta: |
113 | 111 | node = relay.Node.Field() |
114 | 112 | all_employees = SQLAlchemyConnectionField(Employee) |
115 | 113 | |
116 | schema.query = Query | |
114 | schema = graphene.Schema(query=Query) | |
117 | 115 | |
118 | 116 | Creating GraphQL and GraphiQL views in Flask |
119 | 117 | -------------------------------------------- |
8 | 8 | get_session |
9 | 9 | ) |
10 | 10 | |
11 | __all__ = ['SQLAlchemyObjectType', | |
12 | 'SQLAlchemyConnectionField', | |
13 | 'get_query', | |
14 | 'get_session'] | |
11 | __version__ = '2.0.0' | |
12 | ||
13 | __all__ = [ | |
14 | '__version__', | |
15 | 'SQLAlchemyObjectType', | |
16 | 'SQLAlchemyConnectionField', | |
17 | 'get_query', | |
18 | 'get_session' | |
19 | ] |
4 | 4 | |
5 | 5 | from graphene import (ID, Boolean, Dynamic, Enum, Field, Float, Int, List, |
6 | 6 | String) |
7 | from graphene.relay import is_node | |
8 | 7 | from graphene.types.json import JSONString |
9 | 8 | |
10 | from .fields import SQLAlchemyConnectionField | |
9 | from .fields import createConnectionField | |
11 | 10 | |
12 | 11 | try: |
13 | from sqlalchemy_utils import ChoiceType, JSONType, ScalarListType | |
12 | from sqlalchemy_utils import ( | |
13 | ChoiceType, JSONType, ScalarListType, TSVectorType) | |
14 | 14 | except ImportError: |
15 | class ChoiceType(object): | |
16 | pass | |
17 | ||
18 | class ScalarListType(object): | |
19 | pass | |
20 | ||
21 | class JSONType(object): | |
22 | pass | |
15 | ChoiceType = JSONType = ScalarListType = TSVectorType = object | |
23 | 16 | |
24 | 17 | |
25 | 18 | def get_column_doc(column): |
27 | 20 | |
28 | 21 | |
29 | 22 | def is_column_nullable(column): |
30 | return bool(getattr(column, 'nullable', False)) | |
23 | return bool(getattr(column, 'nullable', True)) | |
31 | 24 | |
32 | 25 | |
33 | 26 | def convert_sqlalchemy_relationship(relationship, registry): |
38 | 31 | _type = registry.get_type_for_model(model) |
39 | 32 | if not _type: |
40 | 33 | return None |
41 | if (direction == interfaces.MANYTOONE or not relationship.uselist): | |
34 | if direction == interfaces.MANYTOONE or not relationship.uselist: | |
42 | 35 | return Field(_type) |
43 | elif (direction == interfaces.ONETOMANY or | |
44 | direction == interfaces.MANYTOMANY): | |
45 | if is_node(_type): | |
46 | return SQLAlchemyConnectionField(_type) | |
36 | elif direction in (interfaces.ONETOMANY, interfaces.MANYTOMANY): | |
37 | if _type._meta.connection: | |
38 | return createConnectionField(_type) | |
47 | 39 | return Field(List(_type)) |
48 | 40 | |
49 | 41 | return Dynamic(dynamic_type) |
42 | ||
43 | ||
44 | def convert_sqlalchemy_hybrid_method(hybrid_item): | |
45 | return String(description=getattr(hybrid_item, '__doc__', None), | |
46 | required=False) | |
50 | 47 | |
51 | 48 | |
52 | 49 | def convert_sqlalchemy_composite(composite, registry): |
96 | 93 | @convert_sqlalchemy_type.register(types.Enum) |
97 | 94 | @convert_sqlalchemy_type.register(postgresql.ENUM) |
98 | 95 | @convert_sqlalchemy_type.register(postgresql.UUID) |
96 | @convert_sqlalchemy_type.register(TSVectorType) | |
99 | 97 | def convert_column_to_string(type, column, registry=None): |
100 | 98 | return String(description=get_column_doc(column), |
101 | 99 | required=not(is_column_nullable(column))) |
15 | 15 | return self.type._meta.node._meta.model |
16 | 16 | |
17 | 17 | @classmethod |
18 | def get_query(cls, model, context, info, args): | |
19 | return get_query(model, context) | |
18 | def get_query(cls, model, info, **args): | |
19 | return get_query(model, info.context) | |
20 | ||
21 | @property | |
22 | def type(self): | |
23 | from .types import SQLAlchemyObjectType | |
24 | _type = super(ConnectionField, self).type | |
25 | assert issubclass(_type, SQLAlchemyObjectType), ( | |
26 | "SQLAlchemyConnectionField only accepts SQLAlchemyObjectType types" | |
27 | ) | |
28 | assert _type._meta.connection, "The type {} doesn't have a connection".format(_type.__name__) | |
29 | return _type._meta.connection | |
20 | 30 | |
21 | 31 | @classmethod |
22 | def connection_resolver(cls, resolver, connection, model, root, args, context, info): | |
23 | iterable = resolver(root, args, context, info) | |
32 | def connection_resolver(cls, resolver, connection, model, root, info, **args): | |
33 | iterable = resolver(root, info, **args) | |
24 | 34 | if iterable is None: |
25 | iterable = cls.get_query(model, context, info, args) | |
35 | iterable = cls.get_query(model, info, **args) | |
26 | 36 | if isinstance(iterable, Query): |
27 | 37 | _len = iterable.count() |
28 | 38 | else: |
29 | 39 | _len = len(iterable) |
30 | return connection_from_list_slice( | |
40 | connection = connection_from_list_slice( | |
31 | 41 | iterable, |
32 | 42 | args, |
33 | 43 | slice_start=0, |
37 | 47 | pageinfo_type=PageInfo, |
38 | 48 | edge_type=connection.Edge, |
39 | 49 | ) |
50 | connection.iterable = iterable | |
51 | connection.length = _len | |
52 | return connection | |
40 | 53 | |
41 | 54 | def get_resolver(self, parent_resolver): |
42 | 55 | return partial(self.connection_resolver, parent_resolver, self.type, self.model) |
56 | ||
57 | ||
58 | __connectionFactory = SQLAlchemyConnectionField | |
59 | ||
60 | ||
61 | def createConnectionField(_type): | |
62 | return __connectionFactory(_type) | |
63 | ||
64 | ||
65 | def registerConnectionFieldFactory(factoryMethod): | |
66 | global __connectionFactory | |
67 | __connectionFactory = factoryMethod | |
68 | ||
69 | ||
70 | def unregisterConnectionFieldFactory(): | |
71 | global __connectionFactory | |
72 | __connectionFactory = SQLAlchemyConnectionField |
6 | 6 | |
7 | 7 | def register(self, cls): |
8 | 8 | from .types import SQLAlchemyObjectType |
9 | assert issubclass( | |
10 | cls, SQLAlchemyObjectType), 'Only SQLAlchemyObjectType can be registered, received "{}"'.format( | |
11 | cls.__name__) | |
9 | assert issubclass(cls, SQLAlchemyObjectType), ( | |
10 | 'Only classes of type SQLAlchemyObjectType can be registered, ', | |
11 | 'received "{}"' | |
12 | ).format(cls.__name__) | |
12 | 13 | assert cls._meta.registry == self, 'Registry for a Model have to match.' |
13 | 14 | # assert self.get_type_for_model(cls._meta.model) in [None, cls], ( |
14 | 15 | # 'SQLAlchemy model "{}" already associated with ' |
1 | 1 | |
2 | 2 | from sqlalchemy import Column, Date, ForeignKey, Integer, String, Table |
3 | 3 | from sqlalchemy.ext.declarative import declarative_base |
4 | from sqlalchemy.orm import relationship | |
4 | from sqlalchemy.orm import mapper, relationship | |
5 | 5 | |
6 | 6 | Base = declarative_base() |
7 | 7 | |
33 | 33 | articles = relationship('Article', backref='reporter') |
34 | 34 | favorite_article = relationship("Article", uselist=False) |
35 | 35 | |
36 | # total = column_property( | |
37 | # select([ | |
38 | # func.cast(func.count(PersonInfo.id), Float) | |
39 | # ]) | |
40 | # ) | |
41 | ||
36 | 42 | |
37 | 43 | class Article(Base): |
38 | 44 | __tablename__ = 'articles' |
40 | 46 | headline = Column(String(100)) |
41 | 47 | pub_date = Column(Date()) |
42 | 48 | reporter_id = Column(Integer(), ForeignKey('reporters.id')) |
49 | ||
50 | ||
51 | class ReflectedEditor(type): | |
52 | """Same as Editor, but using reflected table.""" | |
53 | @classmethod | |
54 | def __subclasses__(cls): | |
55 | return [] | |
56 | ||
57 | editor_table = Table('editors', Base.metadata, autoload=True) | |
58 | ||
59 | mapper(ReflectedEditor, editor_table) |
0 | from graphene_sqlalchemy.fields import SQLAlchemyConnectionField, registerConnectionFieldFactory, unregisterConnectionFieldFactory | |
1 | import graphene | |
2 | ||
3 | def test_register(): | |
4 | class LXConnectionField(SQLAlchemyConnectionField): | |
5 | @classmethod | |
6 | def _applyQueryArgs(cls, model, q, args): | |
7 | return q | |
8 | ||
9 | @classmethod | |
10 | def connection_resolver(cls, resolver, connection, model, root, args, context, info): | |
11 | ||
12 | def LXResolver(root, args, context, info): | |
13 | iterable = resolver(root, args, context, info) | |
14 | if iterable is None: | |
15 | iterable = cls.get_query(model, context, info, args) | |
16 | ||
17 | # We accept always a query here. All LX-queries can be filtered and sorted | |
18 | iterable = cls._applyQueryArgs(model, iterable, args) | |
19 | return iterable | |
20 | ||
21 | return SQLAlchemyConnectionField.connection_resolver(LXResolver, connection, model, root, args, context, info) | |
22 | ||
23 | def createLXConnectionField(table): | |
24 | return LXConnectionField(table, filter=table.filter(), order_by=graphene.List(of_type=table.order_by)) | |
25 | ||
26 | registerConnectionFieldFactory(createLXConnectionField) | |
27 | unregisterConnectionFieldFactory() |
0 | 0 | from py.test import raises |
1 | from sqlalchemy import Column, Table, case, types | |
1 | from sqlalchemy import Column, Table, case, types, select, func | |
2 | 2 | from sqlalchemy.dialects import postgresql |
3 | 3 | from sqlalchemy.ext.declarative import declarative_base |
4 | from sqlalchemy.orm import composite | |
4 | from sqlalchemy.orm import composite, column_property | |
5 | 5 | from sqlalchemy.sql.elements import Label |
6 | 6 | from sqlalchemy_utils import ChoiceType, JSONType, ScalarListType |
7 | 7 | |
135 | 135 | assert graphene_type._meta.enum.__members__['en'].value == 'English' |
136 | 136 | |
137 | 137 | |
138 | def test_should_columproperty_convert(): | |
139 | ||
140 | Base = declarative_base() | |
141 | ||
142 | class Test(Base): | |
143 | __tablename__ = 'test' | |
144 | id = Column(types.Integer, primary_key=True) | |
145 | column = column_property( | |
146 | select([func.sum(func.cast(id, types.Integer))]).where( | |
147 | id==1 | |
148 | ) | |
149 | ) | |
150 | ||
151 | graphene_type = convert_sqlalchemy_column(Test.column) | |
152 | assert graphene_type.kwargs['required'] == False | |
153 | ||
154 | ||
138 | 155 | def test_should_scalar_list_convert_list(): |
139 | 156 | assert_column_conversion(ScalarListType(), graphene.List) |
140 | 157 |
269 | 269 | |
270 | 270 | class CreateArticle(graphene.Mutation): |
271 | 271 | |
272 | class Input: | |
272 | class Arguments: | |
273 | 273 | headline = graphene.String() |
274 | 274 | reporter_id = graphene.ID() |
275 | 275 | |
276 | 276 | ok = graphene.Boolean() |
277 | 277 | article = graphene.Field(ArticleNode) |
278 | 278 | |
279 | @classmethod | |
280 | def mutate(cls, instance, args, context, info): | |
279 | def mutate(self, info, headline, reporter_id): | |
281 | 280 | new_article = Article( |
282 | headline=args.get('headline'), | |
283 | reporter_id=args.get('reporter_id'), | |
281 | headline=headline, | |
282 | reporter_id=reporter_id, | |
284 | 283 | ) |
285 | 284 | |
286 | 285 | session.add(new_article) |
0 | ||
1 | from graphene import ObjectType | |
2 | ||
3 | from ..registry import Registry | |
4 | from ..types import SQLAlchemyObjectType | |
5 | from .models import ReflectedEditor | |
6 | ||
7 | registry = Registry() | |
8 | ||
9 | ||
10 | class Reflected(SQLAlchemyObjectType): | |
11 | ||
12 | class Meta: | |
13 | model = ReflectedEditor | |
14 | registry = registry | |
15 | ||
16 | ||
17 | def test_objecttype_registered(): | |
18 | assert issubclass(Reflected, ObjectType) | |
19 | assert Reflected._meta.model == ReflectedEditor | |
20 | assert list( | |
21 | Reflected._meta.fields.keys()) == ['editor_id', 'name'] | |
22 | ||
23 |
0 | 0 | |
1 | 1 | from graphene import Field, Int, Interface, ObjectType |
2 | 2 | from graphene.relay import Node, is_node |
3 | import six | |
3 | 4 | |
4 | 5 | from ..registry import Registry |
5 | 6 | from ..types import SQLAlchemyObjectType |
70 | 71 | |
71 | 72 | |
72 | 73 | def test_object_type(): |
74 | ||
75 | ||
76 | class Human(SQLAlchemyObjectType): | |
77 | '''Human description''' | |
78 | ||
79 | pub_date = Int() | |
80 | ||
81 | class Meta: | |
82 | model = Article | |
83 | # exclude_fields = ('id', ) | |
84 | registry = registry | |
85 | interfaces = (Node, ) | |
86 | ||
73 | 87 | assert issubclass(Human, ObjectType) |
74 | assert list(Human._meta.fields.keys()) == ['id', 'headline', 'reporter_id', 'reporter', 'pub_date'] | |
88 | assert list(Human._meta.fields.keys()) == ['id', 'headline', 'pub_date', 'reporter_id', 'reporter'] | |
75 | 89 | assert is_node(Human) |
90 | ||
91 | ||
92 | ||
93 | # Test Custom SQLAlchemyObjectType Implementation | |
94 | class CustomSQLAlchemyObjectType(SQLAlchemyObjectType): | |
95 | class Meta: | |
96 | abstract = True | |
97 | ||
98 | ||
99 | class CustomCharacter(CustomSQLAlchemyObjectType): | |
100 | '''Character description''' | |
101 | class Meta: | |
102 | model = Reporter | |
103 | registry = registry | |
104 | ||
105 | ||
106 | def test_custom_objecttype_registered(): | |
107 | assert issubclass(CustomCharacter, ObjectType) | |
108 | assert CustomCharacter._meta.model == Reporter | |
109 | assert list( | |
110 | CustomCharacter._meta.fields.keys()) == [ | |
111 | 'id', | |
112 | 'first_name', | |
113 | 'last_name', | |
114 | 'email', | |
115 | 'pets', | |
116 | 'articles', | |
117 | 'favorite_article'] |
8 | 8 | class Query(ObjectType): |
9 | 9 | x = String() |
10 | 10 | |
11 | def resolve_x(self, args, context, info): | |
12 | return get_session(context) | |
11 | def resolve_x(self, info): | |
12 | return get_session(info.context) | |
13 | 13 | |
14 | 14 | query = ''' |
15 | 15 | query ReporterQuery { |
0 | 0 | from collections import OrderedDict |
1 | 1 | |
2 | import six | |
3 | 2 | from sqlalchemy.inspection import inspect as sqlalchemyinspect |
3 | from sqlalchemy.ext.hybrid import hybrid_property | |
4 | 4 | from sqlalchemy.orm.exc import NoResultFound |
5 | 5 | |
6 | from graphene import Field, ObjectType | |
7 | from graphene.relay import is_node | |
8 | from graphene.types.objecttype import ObjectTypeMeta | |
9 | from graphene.types.options import Options | |
10 | from graphene.types.utils import merge, yank_fields_from_attrs | |
11 | from graphene.utils.is_base_type import is_base_type | |
6 | from graphene import Field # , annotate, ResolveInfo | |
7 | from graphene.relay import Connection, Node | |
8 | from graphene.types.objecttype import ObjectType, ObjectTypeOptions | |
9 | from graphene.types.utils import yank_fields_from_attrs | |
12 | 10 | |
13 | 11 | from .converter import (convert_sqlalchemy_column, |
14 | 12 | convert_sqlalchemy_composite, |
15 | convert_sqlalchemy_relationship) | |
13 | convert_sqlalchemy_relationship, | |
14 | convert_sqlalchemy_hybrid_method) | |
16 | 15 | from .registry import Registry, get_global_registry |
17 | from .utils import get_query, is_mapped | |
16 | from .utils import get_query, is_mapped_class, is_mapped_instance | |
18 | 17 | |
19 | 18 | |
20 | def construct_fields(options): | |
21 | only_fields = options.only_fields | |
22 | exclude_fields = options.exclude_fields | |
23 | inspected_model = sqlalchemyinspect(options.model) | |
19 | def construct_fields(model, registry, only_fields, exclude_fields): | |
20 | inspected_model = sqlalchemyinspect(model) | |
24 | 21 | |
25 | 22 | fields = OrderedDict() |
26 | 23 | |
27 | 24 | for name, column in inspected_model.columns.items(): |
28 | 25 | is_not_in_only = only_fields and name not in only_fields |
29 | is_already_created = name in options.fields | |
30 | is_excluded = name in exclude_fields or is_already_created | |
26 | # is_already_created = name in options.fields | |
27 | is_excluded = name in exclude_fields # or is_already_created | |
31 | 28 | if is_not_in_only or is_excluded: |
32 | 29 | # We skip this field if we specify only_fields and is not |
33 | # in there. Or when we excldue this field in exclude_fields | |
30 | # in there. Or when we exclude this field in exclude_fields | |
34 | 31 | continue |
35 | converted_column = convert_sqlalchemy_column(column, options.registry) | |
32 | converted_column = convert_sqlalchemy_column(column, registry) | |
36 | 33 | fields[name] = converted_column |
37 | 34 | |
38 | 35 | for name, composite in inspected_model.composites.items(): |
39 | 36 | is_not_in_only = only_fields and name not in only_fields |
40 | is_already_created = name in options.fields | |
41 | is_excluded = name in exclude_fields or is_already_created | |
37 | # is_already_created = name in options.fields | |
38 | is_excluded = name in exclude_fields # or is_already_created | |
42 | 39 | if is_not_in_only or is_excluded: |
43 | 40 | # We skip this field if we specify only_fields and is not |
44 | # in there. Or when we excldue this field in exclude_fields | |
41 | # in there. Or when we exclude this field in exclude_fields | |
45 | 42 | continue |
46 | converted_composite = convert_sqlalchemy_composite(composite, options.registry) | |
43 | converted_composite = convert_sqlalchemy_composite(composite, registry) | |
47 | 44 | fields[name] = converted_composite |
45 | ||
46 | for hybrid_item in inspected_model.all_orm_descriptors: | |
47 | ||
48 | if type(hybrid_item) == hybrid_property: | |
49 | name = hybrid_item.__name__ | |
50 | ||
51 | is_not_in_only = only_fields and name not in only_fields | |
52 | # is_already_created = name in options.fields | |
53 | is_excluded = name in exclude_fields # or is_already_created | |
54 | ||
55 | if is_not_in_only or is_excluded: | |
56 | # We skip this field if we specify only_fields and is not | |
57 | # in there. Or when we exclude this field in exclude_fields | |
58 | continue | |
59 | ||
60 | converted_hybrid_property = convert_sqlalchemy_hybrid_method( | |
61 | hybrid_item | |
62 | ) | |
63 | fields[name] = converted_hybrid_property | |
48 | 64 | |
49 | 65 | # Get all the columns for the relationships on the model |
50 | 66 | for relationship in inspected_model.relationships: |
51 | 67 | is_not_in_only = only_fields and relationship.key not in only_fields |
52 | is_already_created = relationship.key in options.fields | |
53 | is_excluded = relationship.key in exclude_fields or is_already_created | |
68 | # is_already_created = relationship.key in options.fields | |
69 | is_excluded = relationship.key in exclude_fields # or is_already_created | |
54 | 70 | if is_not_in_only or is_excluded: |
55 | 71 | # We skip this field if we specify only_fields and is not |
56 | # in there. Or when we excldue this field in exclude_fields | |
72 | # in there. Or when we exclude this field in exclude_fields | |
57 | 73 | continue |
58 | converted_relationship = convert_sqlalchemy_relationship(relationship, options.registry) | |
74 | converted_relationship = convert_sqlalchemy_relationship(relationship, registry) | |
59 | 75 | name = relationship.key |
60 | 76 | fields[name] = converted_relationship |
61 | 77 | |
62 | 78 | return fields |
63 | 79 | |
64 | 80 | |
65 | class SQLAlchemyObjectTypeMeta(ObjectTypeMeta): | |
81 | class SQLAlchemyObjectTypeOptions(ObjectTypeOptions): | |
82 | model = None # type: Model | |
83 | registry = None # type: Registry | |
84 | connection = None # type: Type[Connection] | |
85 | id = None # type: str | |
66 | 86 | |
67 | @staticmethod | |
68 | def __new__(cls, name, bases, attrs): | |
69 | # Also ensure initialization is only performed for subclasses of Model | |
70 | # (excluding Model class itself). | |
71 | if not is_base_type(bases, SQLAlchemyObjectTypeMeta): | |
72 | return type.__new__(cls, name, bases, attrs) | |
73 | 87 | |
74 | options = Options( | |
75 | attrs.pop('Meta', None), | |
76 | name=name, | |
77 | description=attrs.pop('__doc__', None), | |
78 | model=None, | |
79 | local_fields=None, | |
80 | only_fields=(), | |
81 | exclude_fields=(), | |
82 | id='id', | |
83 | interfaces=(), | |
84 | registry=None | |
88 | class SQLAlchemyObjectType(ObjectType): | |
89 | @classmethod | |
90 | def __init_subclass_with_meta__(cls, model=None, registry=None, skip_registry=False, | |
91 | only_fields=(), exclude_fields=(), connection=None, | |
92 | use_connection=None, interfaces=(), id=None, **options): | |
93 | assert is_mapped_class(model), ( | |
94 | 'You need to pass a valid SQLAlchemy Model in ' | |
95 | '{}.Meta, received "{}".' | |
96 | ).format(cls.__name__, model) | |
97 | ||
98 | if not registry: | |
99 | registry = get_global_registry() | |
100 | ||
101 | assert isinstance(registry, Registry), ( | |
102 | 'The attribute registry in {} needs to be an instance of ' | |
103 | 'Registry, received "{}".' | |
104 | ).format(cls.__name__, registry) | |
105 | ||
106 | sqla_fields = yank_fields_from_attrs( | |
107 | construct_fields(model, registry, only_fields, exclude_fields), | |
108 | _as=Field, | |
85 | 109 | ) |
86 | 110 | |
87 | if not options.registry: | |
88 | options.registry = get_global_registry() | |
89 | assert isinstance(options.registry, Registry), ( | |
90 | 'The attribute registry in {}.Meta needs to be an' | |
91 | ' instance of Registry, received "{}".' | |
92 | ).format(name, options.registry) | |
93 | assert is_mapped(options.model), ( | |
94 | 'You need to pass a valid SQLAlchemy Model in ' | |
95 | '{}.Meta, received "{}".' | |
96 | ).format(name, options.model) | |
111 | if use_connection is None and interfaces: | |
112 | use_connection = any((issubclass(interface, Node) for interface in interfaces)) | |
97 | 113 | |
98 | cls = ObjectTypeMeta.__new__(cls, name, bases, dict(attrs, _meta=options)) | |
114 | if use_connection and not connection: | |
115 | # We create the connection automatically | |
116 | connection = Connection.create_type('{}Connection'.format(cls.__name__), node=cls) | |
99 | 117 | |
100 | options.registry.register(cls) | |
118 | if connection is not None: | |
119 | assert issubclass(connection, Connection), ( | |
120 | "The connection must be a Connection. Received {}" | |
121 | ).format(connection.__name__) | |
101 | 122 | |
102 | options.sqlalchemy_fields = yank_fields_from_attrs( | |
103 | construct_fields(options), | |
104 | _as=Field, | |
105 | ) | |
106 | options.fields = merge( | |
107 | options.interface_fields, | |
108 | options.sqlalchemy_fields, | |
109 | options.base_fields, | |
110 | options.local_fields | |
111 | ) | |
123 | _meta = SQLAlchemyObjectTypeOptions(cls) | |
124 | _meta.model = model | |
125 | _meta.registry = registry | |
126 | _meta.fields = sqla_fields | |
127 | _meta.connection = connection | |
128 | _meta.id = id or 'id' | |
112 | 129 | |
113 | return cls | |
130 | super(SQLAlchemyObjectType, cls).__init_subclass_with_meta__(_meta=_meta, interfaces=interfaces, **options) | |
114 | 131 | |
115 | ||
116 | class SQLAlchemyObjectType(six.with_metaclass(SQLAlchemyObjectTypeMeta, ObjectType)): | |
132 | if not skip_registry: | |
133 | registry.register(cls) | |
117 | 134 | |
118 | 135 | @classmethod |
119 | def is_type_of(cls, root, context, info): | |
136 | def is_type_of(cls, root, info): | |
120 | 137 | if isinstance(root, cls): |
121 | 138 | return True |
122 | if not is_mapped(type(root)): | |
139 | if not is_mapped_instance(root): | |
123 | 140 | raise Exception(( |
124 | 141 | 'Received incompatible instance "{}".' |
125 | 142 | ).format(root)) |
126 | 143 | return isinstance(root, cls._meta.model) |
127 | 144 | |
128 | 145 | @classmethod |
129 | def get_query(cls, context): | |
146 | def get_query(cls, info): | |
130 | 147 | model = cls._meta.model |
131 | return get_query(model, context) | |
148 | return get_query(model, info.context) | |
132 | 149 | |
133 | 150 | @classmethod |
134 | def get_node(cls, id, context, info): | |
151 | def get_node(cls, info, id): | |
135 | 152 | try: |
136 | return cls.get_query(context).get(id) | |
153 | return cls.get_query(info).get(id) | |
137 | 154 | except NoResultFound: |
138 | 155 | return None |
139 | 156 | |
140 | def resolve_id(self, args, context, info): | |
141 | graphene_type = info.parent_type.graphene_type | |
142 | if is_node(graphene_type): | |
143 | return self.__mapper__.primary_key_from_instance(self)[0] | |
144 | return getattr(self, graphene_type._meta.id, None) | |
157 | def resolve_id(self, info): | |
158 | # graphene_type = info.parent_type.graphene_type | |
159 | keys = self.__mapper__.primary_key_from_instance(self) | |
160 | return tuple(keys) if len(keys) > 1 else keys[0] |
0 | from sqlalchemy.ext.declarative.api import DeclarativeMeta | |
0 | from sqlalchemy.exc import ArgumentError | |
1 | from sqlalchemy.orm import class_mapper, object_mapper | |
2 | from sqlalchemy.orm.exc import UnmappedClassError, UnmappedInstanceError | |
1 | 3 | |
2 | 4 | |
3 | 5 | def get_session(context): |
15 | 17 | return query |
16 | 18 | |
17 | 19 | |
18 | def is_mapped(obj): | |
19 | return isinstance(obj, DeclarativeMeta) | |
20 | def is_mapped_class(cls): | |
21 | try: | |
22 | class_mapper(cls) | |
23 | except (ArgumentError, UnmappedClassError): | |
24 | return False | |
25 | else: | |
26 | return True | |
27 | ||
28 | ||
29 | def is_mapped_instance(cls): | |
30 | try: | |
31 | object_mapper(cls) | |
32 | except (ArgumentError, UnmappedInstanceError): | |
33 | return False | |
34 | else: | |
35 | return True |
6 | 6 | |
7 | 7 | [isort] |
8 | 8 | known_first_party=graphene,graphene_sqlalchemy |
9 | ||
10 | [tool:pytest] | |
11 | testpaths = graphene_sqlalchemy/ | |
12 | addopts = | |
13 | -s | |
14 | ; --cov graphene-sqlalchemy | |
15 | norecursedirs = | |
16 | __pycache__ | |
17 | *.egg-info | |
18 | .cache | |
19 | .git | |
20 | .tox | |
21 | appdir | |
22 | docs | |
23 | filterwarnings = | |
24 | error | |
25 | ignore::DeprecationWarning | |
26 | ||
27 | [bdist_wheel] | |
28 | universal=1 |
0 | 0 | from setuptools import find_packages, setup |
1 | import sys | |
2 | import ast | |
3 | import re | |
4 | ||
5 | _version_re = re.compile(r'__version__\s+=\s+(.*)') | |
6 | ||
7 | with open('graphene_sqlalchemy/__init__.py', 'rb') as f: | |
8 | version = str(ast.literal_eval(_version_re.search( | |
9 | f.read().decode('utf-8')).group(1))) | |
10 | ||
1 | 11 | |
2 | 12 | setup( |
3 | 13 | name='graphene-sqlalchemy', |
4 | version='1.1.1', | |
14 | version=version, | |
5 | 15 | |
6 | 16 | description='Graphene SQLAlchemy integration', |
7 | 17 | long_description=open('README.rst').read(), |
32 | 42 | |
33 | 43 | install_requires=[ |
34 | 44 | 'six>=1.10.0', |
35 | 'graphene>=1.0', | |
45 | 'graphene>=2.0', | |
36 | 46 | 'SQLAlchemy', |
37 | 47 | 'singledispatch>=3.4.0.3', |
38 | 48 | 'iso8601', |