Skip to content

Commit 6562a78

Browse files
committed
Merge pull request #1 from syrusakbary/feature/adam-django
Fixed tests and merged most recent code changes
2 parents 3d4f593 + c702632 commit 6562a78

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+751
-256
lines changed

.travis.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,20 @@ after_success:
7373
fi
7474
env:
7575
matrix:
76-
- TEST_TYPE=build DJANGO_VERSION=1.8
77-
- TEST_TYPE=build DJANGO_VERSION=1.9
76+
- TEST_TYPE=build
7877
global:
7978
secure: SQC0eCWCWw8bZxbLE8vQn+UjJOp3Z1m779s9SMK3lCLwJxro/VCLBZ7hj4xsrq1MtcFO2U2Kqf068symw4Hr/0amYI3HFTCFiwXAC3PAKXeURca03eNO2heku+FtnQcOjBanExTsIBQRLDXMOaUkf3MIztpLJ4LHqMfUupKmw9YSB0v40jDbSN8khBnndFykmOnVVHznFp8USoN5F0CiPpnfEvHnJkaX76lNf7Kc9XNShBTTtJsnsHMhuYQeInt0vg9HSjoIYC38Tv2hmMj1myNdzyrHF+LgRjI6ceGi50ApAnGepXC/DNRhXROfECKez+LON/ZSqBGdJhUILqC8A4WmWmIjNcwitVFp3JGBqO7LULS0BI96EtSLe8rD1rkkdTbjivajkbykM1Q0Tnmg1adzGwLxRUbTq9tJQlTTkHBCuXIkpKb1mAtb/TY7A6BqfnPi2xTc/++qEawUG7ePhscdTj0IBrUfZsUNUYZqD8E8XbSWKIuS3SHE+cZ+s/kdAsm4q+FFAlpZKOYGxIkwvgyfu4/Plfol4b7X6iAP9J3r1Kv0DgBVFst5CXEwzZs19/g0CgokQbCXf1N+xeNnUELl6/fImaR3RKP22EaABoil4z8vzl4EqxqVoH1nfhE+WlpryXsuSaF/1R+WklR7aQ1FwoCk8V8HxM2zrj4tI8k=
8079
matrix:
8180
fast_finish: true
8281
include:
82+
- python: '2.7'
83+
env: DJANGO_VERSION=1.6
84+
- python: '2.7'
85+
env: DJANGO_VERSION=1.7
86+
- python: '2.7'
87+
env: DJANGO_VERSION=1.8
88+
- python: '2.7'
89+
env: DJANGO_VERSION=1.9
8390
- python: '2.7'
8491
env: TEST_TYPE=build_website
8592
- python: '2.7'

graphene/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
Interface,
1212
Mutation,
1313
Scalar,
14-
BaseType,
14+
InstanceType,
1515
LazyType,
1616
Argument,
1717
Field,
@@ -51,7 +51,7 @@
5151
'NonNull',
5252
'signals',
5353
'Schema',
54-
'BaseType',
54+
'InstanceType',
5555
'LazyType',
5656
'ObjectType',
5757
'InputObjectType',

graphene/contrib/django/compat.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from django.db import models
2+
3+
try:
4+
UUIDField = models.UUIDField
5+
except AttributeError:
6+
# Improved compatibility for Django 1.6
7+
class UUIDField(object):
8+
pass
9+
10+
try:
11+
from django.db.models.related import RelatedObject
12+
except:
13+
# Improved compatibility for Django 1.6
14+
class RelatedObject(object):
15+
pass

graphene/contrib/django/converter.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
11
from django.db import models
22

3-
from .utils import import_single_dispatch
43
from ...core.types.scalars import ID, Boolean, Float, Int, String
4+
from .compat import RelatedObject, UUIDField
5+
from .utils import get_related_model, import_single_dispatch
56

67
singledispatch = import_single_dispatch()
78

8-
try:
9-
UUIDField = models.UUIDField
10-
except AttributeError:
11-
# Improved compatibility for Django 1.6
12-
class UUIDField(object):
13-
pass
14-
159

1610
@singledispatch
1711
def convert_django_field(field):
@@ -65,12 +59,20 @@ def convert_field_to_float(field):
6559
@convert_django_field.register(models.ManyToOneRel)
6660
def convert_field_to_list_or_connection(field):
6761
from .fields import DjangoModelField, ConnectionOrListField
68-
model_field = DjangoModelField(field.related_model)
62+
model_field = DjangoModelField(get_related_model(field))
63+
return ConnectionOrListField(model_field)
64+
65+
66+
# For Django 1.6
67+
@convert_django_field.register(RelatedObject)
68+
def convert_relatedfield_to_djangomodel(field):
69+
from .fields import DjangoModelField, ConnectionOrListField
70+
model_field = DjangoModelField(field.model)
6971
return ConnectionOrListField(model_field)
7072

7173

7274
@convert_django_field.register(models.OneToOneField)
7375
@convert_django_field.register(models.ForeignKey)
7476
def convert_field_to_djangomodel(field):
7577
from .fields import DjangoModelField
76-
return DjangoModelField(field.related_model, description=field.help_text)
78+
return DjangoModelField(get_related_model(field), description=field.help_text)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .plugin import DjangoDebugPlugin
2+
from .types import DjangoDebug
3+
4+
__all__ = ['DjangoDebugPlugin', 'DjangoDebug']
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from contextlib import contextmanager
2+
3+
from django.db import connections
4+
5+
from ....core.schema import GraphQLSchema
6+
from ....core.types import Field
7+
from ....plugins import Plugin
8+
from .sql.tracking import unwrap_cursor, wrap_cursor
9+
from .sql.types import DjangoDebugSQL
10+
from .types import DjangoDebug
11+
12+
13+
class WrappedRoot(object):
14+
15+
def __init__(self, root):
16+
self._recorded = []
17+
self._root = root
18+
19+
def record(self, **log):
20+
self._recorded.append(DjangoDebugSQL(**log))
21+
22+
def debug(self):
23+
return DjangoDebug(sql=self._recorded)
24+
25+
26+
class WrapRoot(object):
27+
28+
@property
29+
def _root(self):
30+
return self._wrapped_root.root
31+
32+
@_root.setter
33+
def _root(self, value):
34+
self._wrapped_root = value
35+
36+
def resolve_debug(self, args, info):
37+
return self._wrapped_root.debug()
38+
39+
40+
def debug_objecttype(objecttype):
41+
return type(
42+
'Debug{}'.format(objecttype._meta.type_name),
43+
(WrapRoot, objecttype),
44+
{'debug': Field(DjangoDebug, name='__debug')})
45+
46+
47+
class DjangoDebugPlugin(Plugin):
48+
49+
def enable_instrumentation(self, wrapped_root):
50+
# This is thread-safe because database connections are thread-local.
51+
for connection in connections.all():
52+
wrap_cursor(connection, wrapped_root)
53+
54+
def disable_instrumentation(self):
55+
for connection in connections.all():
56+
unwrap_cursor(connection)
57+
58+
def wrap_schema(self, schema_type):
59+
query = schema_type._query
60+
if query:
61+
class_type = self.schema.objecttype(schema_type.get_query_type())
62+
assert class_type, 'The query in schema is not constructed with graphene'
63+
_type = debug_objecttype(class_type)
64+
self.schema.register(_type, force=True)
65+
return GraphQLSchema(
66+
self.schema,
67+
self.schema.T(_type),
68+
schema_type.get_mutation_type(),
69+
schema_type.get_subscription_type()
70+
)
71+
return schema_type
72+
73+
@contextmanager
74+
def context_execution(self, executor):
75+
executor['root'] = WrappedRoot(root=executor['root'])
76+
executor['schema'] = self.wrap_schema(executor['schema'])
77+
self.enable_instrumentation(executor['root'])
78+
yield executor
79+
self.disable_instrumentation()
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Code obtained from django-debug-toolbar sql panel tracking
2+
from __future__ import absolute_import, unicode_literals
3+
4+
import json
5+
from threading import local
6+
from time import time
7+
8+
from django.utils import six
9+
from django.utils.encoding import force_text
10+
11+
12+
class SQLQueryTriggered(Exception):
13+
"""Thrown when template panel triggers a query"""
14+
15+
16+
class ThreadLocalState(local):
17+
18+
def __init__(self):
19+
self.enabled = True
20+
21+
@property
22+
def Wrapper(self):
23+
if self.enabled:
24+
return NormalCursorWrapper
25+
return ExceptionCursorWrapper
26+
27+
def recording(self, v):
28+
self.enabled = v
29+
30+
31+
state = ThreadLocalState()
32+
recording = state.recording # export function
33+
34+
35+
def wrap_cursor(connection, panel):
36+
if not hasattr(connection, '_djdt_cursor'):
37+
connection._djdt_cursor = connection.cursor
38+
39+
def cursor():
40+
return state.Wrapper(connection._djdt_cursor(), connection, panel)
41+
42+
connection.cursor = cursor
43+
return cursor
44+
45+
46+
def unwrap_cursor(connection):
47+
if hasattr(connection, '_djdt_cursor'):
48+
del connection._djdt_cursor
49+
del connection.cursor
50+
51+
52+
class ExceptionCursorWrapper(object):
53+
"""
54+
Wraps a cursor and raises an exception on any operation.
55+
Used in Templates panel.
56+
"""
57+
58+
def __init__(self, cursor, db, logger):
59+
pass
60+
61+
def __getattr__(self, attr):
62+
raise SQLQueryTriggered()
63+
64+
65+
class NormalCursorWrapper(object):
66+
"""
67+
Wraps a cursor and logs queries.
68+
"""
69+
70+
def __init__(self, cursor, db, logger):
71+
self.cursor = cursor
72+
# Instance of a BaseDatabaseWrapper subclass
73+
self.db = db
74+
# logger must implement a ``record`` method
75+
self.logger = logger
76+
77+
def _quote_expr(self, element):
78+
if isinstance(element, six.string_types):
79+
return "'%s'" % force_text(element).replace("'", "''")
80+
else:
81+
return repr(element)
82+
83+
def _quote_params(self, params):
84+
if not params:
85+
return params
86+
if isinstance(params, dict):
87+
return dict((key, self._quote_expr(value))
88+
for key, value in params.items())
89+
return list(map(self._quote_expr, params))
90+
91+
def _decode(self, param):
92+
try:
93+
return force_text(param, strings_only=True)
94+
except UnicodeDecodeError:
95+
return '(encoded string)'
96+
97+
def _record(self, method, sql, params):
98+
start_time = time()
99+
try:
100+
return method(sql, params)
101+
finally:
102+
stop_time = time()
103+
duration = (stop_time - start_time)
104+
_params = ''
105+
try:
106+
_params = json.dumps(list(map(self._decode, params)))
107+
except Exception:
108+
pass # object not JSON serializable
109+
110+
alias = getattr(self.db, 'alias', 'default')
111+
conn = self.db.connection
112+
vendor = getattr(conn, 'vendor', 'unknown')
113+
114+
params = {
115+
'vendor': vendor,
116+
'alias': alias,
117+
'sql': self.db.ops.last_executed_query(
118+
self.cursor, sql, self._quote_params(params)),
119+
'duration': duration,
120+
'raw_sql': sql,
121+
'params': _params,
122+
'start_time': start_time,
123+
'stop_time': stop_time,
124+
'is_slow': duration > 10,
125+
'is_select': sql.lower().strip().startswith('select'),
126+
}
127+
128+
if vendor == 'postgresql':
129+
# If an erroneous query was ran on the connection, it might
130+
# be in a state where checking isolation_level raises an
131+
# exception.
132+
try:
133+
iso_level = conn.isolation_level
134+
except conn.InternalError:
135+
iso_level = 'unknown'
136+
params.update({
137+
'trans_id': self.logger.get_transaction_id(alias),
138+
'trans_status': conn.get_transaction_status(),
139+
'iso_level': iso_level,
140+
'encoding': conn.encoding,
141+
})
142+
143+
# We keep `sql` to maintain backwards compatibility
144+
self.logger.record(**params)
145+
146+
def callproc(self, procname, params=()):
147+
return self._record(self.cursor.callproc, procname, params)
148+
149+
def execute(self, sql, params=()):
150+
return self._record(self.cursor.execute, sql, params)
151+
152+
def executemany(self, sql, param_list):
153+
return self._record(self.cursor.executemany, sql, param_list)
154+
155+
def __getattr__(self, attr):
156+
return getattr(self.cursor, attr)
157+
158+
def __iter__(self):
159+
return iter(self.cursor)
160+
161+
def __enter__(self):
162+
return self
163+
164+
def __exit__(self, type, value, traceback):
165+
self.close()
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from .....core import Boolean, Float, ObjectType, String
2+
3+
4+
class DjangoDebugSQL(ObjectType):
5+
vendor = String()
6+
alias = String()
7+
sql = String()
8+
duration = Float()
9+
raw_sql = String()
10+
params = String()
11+
start_time = Float()
12+
stop_time = Float()
13+
is_slow = Boolean()
14+
is_select = Boolean()
15+
16+
trans_id = String()
17+
trans_status = String()
18+
iso_level = String()
19+
encoding = String()

graphene/contrib/django/debug/tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)