mirror of
https://github.com/graphql-python/graphene.git
synced 2025-07-10 16:12:20 +03:00
Compare commits
1179 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
8290326308 | ||
|
4a274b8424 | ||
|
b3db1c0cb2 | ||
|
3ed7bf6362 | ||
|
ccae7364e5 | ||
|
cf97cbb1de | ||
|
dca31dc61d | ||
|
73df50e3dc | ||
|
821451fddc | ||
|
f2e68141fd | ||
|
431826814d | ||
|
5b3ed2c2ba | ||
|
f95e9221bb | ||
|
48678afba4 | ||
|
dc3b2e49c1 | ||
|
d53a102b08 | ||
|
fd9ecef36e | ||
|
1263e9b41e | ||
|
74b33ae148 | ||
|
6834385786 | ||
|
c335c5f529 | ||
|
d90d65cafe | ||
|
5924cc4150 | ||
|
6a668514de | ||
|
88c3ec539b | ||
|
17d09c8ded | ||
|
614449e651 | ||
|
44dcdad182 | ||
|
221afaf4c4 | ||
|
5db1af039f | ||
|
82d0a68a81 | ||
|
3cd0c30de8 | ||
|
5fb7b54377 | ||
|
baaef0d21a | ||
|
93cb33d359 | ||
|
f5aba2c027 | ||
|
ea7ccc350e | ||
|
6b8cd2dc78 | ||
|
74db349da4 | ||
|
99f0103e37 | ||
|
03cf2e131e | ||
|
d77d0b0571 | ||
|
c636d984c6 | ||
|
2da8e9db5c | ||
|
8ede21e063 | ||
|
57cbef6666 | ||
|
d33e38a391 | ||
|
b76e89c0c2 | ||
|
81e7eee5da | ||
|
969a630541 | ||
|
8b89afeff1 | ||
|
52143473ef | ||
|
8eb2807ce5 | ||
|
340d5ed12f | ||
|
19ea63b9c5 | ||
|
d5dadb7b1b | ||
|
8596349405 | ||
|
a141e848c3 | ||
|
f09b2e5a81 | ||
|
7f6fa16194 | ||
|
0b1bfbf65b | ||
|
f891a3683d | ||
|
a2b63d8d84 | ||
|
b349632a82 | ||
|
ccdd35b354 | ||
|
6969023491 | ||
|
ee1ff975d7 | ||
|
b20bbdcdf7 | ||
|
694c1db21e | ||
|
20219fdc1b | ||
|
45986b18e7 | ||
|
c5ccc9502d | ||
|
35c281a3cd | ||
|
355601bd5c | ||
|
cbf59a88ad | ||
|
24059a8b40 | ||
|
d96ec55abb | ||
|
f1e8f4862a | ||
|
e6429c3c5b | ||
|
ed1290aaba | ||
|
84faf8f57c | ||
|
0ac4d9397e | ||
|
023452a09f | ||
|
6339f489e9 | ||
|
23ca978918 | ||
|
97abb9db42 | ||
|
57f3aa3ba9 | ||
|
8e1c3d3102 | ||
|
13c661332e | ||
|
80e3498750 | ||
|
c77d87d205 | ||
|
8bdcec5cd7 | ||
|
dfece7f65d | ||
|
8589aaeb98 | ||
|
72c2fd5ec3 | ||
|
69be326290 | ||
|
2ee23b0b2c | ||
|
8f6a8f9c4a | ||
|
3bdc67c6ae | ||
|
efe4b89015 | ||
|
785fcb38b6 | ||
|
5475a7ad1f | ||
|
5d4e71f463 | ||
|
9c3e4bb7da | ||
|
9e7e08d48a | ||
|
e37ef00ca4 | ||
|
4e8a1e6057 | ||
|
181d9f76da | ||
|
03277a5512 | ||
|
19ebf08339 | ||
|
bf40e6c419 | ||
|
61f0d8a8e0 | ||
|
763910e7b5 | ||
|
beb957382d | ||
|
06eb1a3e82 | ||
|
e441fa72aa | ||
|
7ecb4e68ba | ||
|
9311d3525d | ||
|
7108bc8577 | ||
|
a61f0a214d | ||
|
27f19e5a90 | ||
|
9e17044ddc | ||
|
b274a607f4 | ||
|
0a54094f59 | ||
|
9c1db0f662 | ||
|
03aad2799a | ||
|
b6c8931b22 | ||
|
10aee710fc | ||
|
1d6f9e984b | ||
|
c1bd25555c | ||
|
78973964b8 | ||
|
f039af2810 | ||
|
47696559c7 | ||
|
2e5944eb20 | ||
|
2c66e496f7 | ||
|
908d5aeaeb | ||
|
d5d7a0e5e0 | ||
|
85f06fb2a6 | ||
|
a3a2f999aa | ||
|
7087710d02 | ||
|
1c3054b7c8 | ||
|
5896ade2dd | ||
|
76701e0809 | ||
|
3b77b5f92a | ||
|
16d0b32a8f | ||
|
d54b819552 | ||
|
1886ec9dcb | ||
|
0ebff3313d | ||
|
7827219ba2 | ||
|
ce59f1ff15 | ||
|
3145543386 | ||
|
7960b02124 | ||
|
9807d6102c | ||
|
e66d6148ab | ||
|
1654d2fa29 | ||
|
772986ac83 | ||
|
0aef168687 | ||
|
dc6b820635 | ||
|
16551369b2 | ||
|
e1822c9ae9 | ||
|
3c50fa817a | ||
|
efc03533ae | ||
|
74a6565ea3 | ||
|
98980b53f6 | ||
|
57a4394bf3 | ||
|
ea4e6d65e9 | ||
|
18cd3451f9 | ||
|
946c2a3807 | ||
|
7d890bf915 | ||
|
0e4c14b076 | ||
|
8ae4369155 | ||
|
c0ddbbfaf4 | ||
|
467b1f8e8d | ||
|
b4be4a686b | ||
|
4e32dac251 | ||
|
ec982ac50b | ||
|
c68071952d | ||
|
ac5dd90f5f | ||
|
7be4bd6bc6 | ||
|
d7b474751d | ||
|
a784ef15e5 | ||
|
5977b1648c | ||
|
4259502dc3 | ||
|
fc2967e276 | ||
|
aa11681048 | ||
|
fce45ef552 | ||
|
5290c9364c | ||
|
69b6286861 | ||
|
485b1ed325 | ||
|
c08379ed85 | ||
|
55cbc4d100 | ||
|
fbac4d5092 | ||
|
002b769db4 | ||
|
12302b78f9 | ||
|
db9d9a08f2 | ||
|
3ed8273239 | ||
|
a5fbb2e9e5 | ||
|
5acd04aa93 | ||
|
17f6a45a47 | ||
|
f5321d619c | ||
|
f622f1f53c | ||
|
6f9cdb4888 | ||
|
7004515f06 | ||
|
a17f63cf03 | ||
|
86b7e6ac86 | ||
|
ae93499a37 | ||
|
2e87ebe5fc | ||
|
e5eeb9d831 | ||
|
e0d4bec2d8 | ||
|
7d09e5b138 | ||
|
84582eb374 | ||
|
e24ac547d6 | ||
|
a53b782bf8 | ||
|
8c327fc4ed | ||
|
b685e109f5 | ||
|
6918db1033 | ||
|
188ce9a6cb | ||
|
86b904d327 | ||
|
29dd3f8391 | ||
|
d085c8852b | ||
|
2130005406 | ||
|
64af43748c | ||
|
81fff0f1b5 | ||
|
d042d5e95a | ||
|
c61f0f736a | ||
|
5b2eb1109a | ||
|
ecd11ccc1e | ||
|
324df19d3d | ||
|
bf034ca85f | ||
|
05d96a9833 | ||
|
a9625dac0e | ||
|
a1fc3688aa | ||
|
4b70186031 | ||
|
47c63f3dd7 | ||
|
966aba06cd | ||
|
b0c8a17ec7 | ||
|
9b756bf12c | ||
|
d6acfc6eae | ||
|
0723dd1d6c | ||
|
df67e69129 | ||
|
396b278aff | ||
|
380166989d | ||
|
3e4305259b | ||
|
a0b522fa39 | ||
|
12ec8dc007 | ||
|
133a831ab9 | ||
|
7a1e9d7798 | ||
|
49fcf9f2e6 | ||
|
37d6eaea46 | ||
|
871c60cf46 | ||
|
0051f82b5f | ||
|
a2fe8dd704 | ||
|
9fdab033a7 | ||
|
cb3bfe011f | ||
|
6f2863ef6e | ||
|
00e36b52d5 | ||
|
f9efe15973 | ||
|
60a9609b9a | ||
|
14183012a8 | ||
|
1cf303a27b | ||
|
88f79b2850 | ||
|
5d97c848e0 | ||
|
5e6f68957e | ||
|
ffb7701466 | ||
|
796880fc5c | ||
|
98e10f0db8 | ||
|
ac98be7836 | ||
|
ba5b7dd3d7 | ||
|
be97a369f7 | ||
|
03bd6984dd | ||
|
23bb52a770 | ||
|
ad0b3a529c | ||
|
9a19447213 | ||
|
55a03ba716 | ||
|
f82b811377 | ||
|
bd6d8d086d | ||
|
81d61f82c5 | ||
|
482c7fcc65 | ||
|
c0fbcba97a | ||
|
e31b93d1fd | ||
|
abc2c2a784 | ||
|
3f6f426946 | ||
|
7c7876d37c | ||
|
a3b215d891 | ||
|
e90aa1b712 | ||
|
8e7d76bbce | ||
|
3d0e460be1 | ||
|
0808e8acb3 | ||
|
c96bd680d7 | ||
|
6e4058960d | ||
|
167c8c203c | ||
|
6fc1a8f79d | ||
|
57157ab7f4 | ||
|
4170e73251 | ||
|
8dee85cc13 | ||
|
abe547fb4d | ||
|
0e8a3c5063 | ||
|
ac6714e8fa | ||
|
89ca4f58a2 | ||
|
431e93cd68 | ||
|
5cb7d91aaa | ||
|
da1359ecca | ||
|
40229b8a73 | ||
|
89a352e93a | ||
|
17fbcb6746 | ||
|
f0cc9268d2 | ||
|
4e3f46b2c9 | ||
|
40534bcce9 | ||
|
ec9f76cfae | ||
|
eb7966eca7 | ||
|
1fcdeaac65 | ||
|
f73055f72b | ||
|
a5162e9ae3 | ||
|
6a4091b3e4 | ||
|
abff3d75a3 | ||
|
daf0d17647 | ||
|
d0cfee5641 | ||
|
bcbb66c025 | ||
|
582ac59bf7 | ||
|
28f6b18f55 | ||
|
7b6dae7fa3 | ||
|
c5b2281e22 | ||
|
21cccf4c96 | ||
|
0805436d45 | ||
|
ca9188a615 | ||
|
bbe11c9b5e | ||
|
96d497c2b8 | ||
|
a4681ce6b2 | ||
|
d95163c61d | ||
|
9ae2359b87 | ||
|
fd0bd0ccd7 | ||
|
359753dc0f | ||
|
806b99f59d | ||
|
ae7a5d71c7 | ||
|
88d68fa672 | ||
|
ff4fb4f86a | ||
|
e32cbc3346 | ||
|
d1b1ad733f | ||
|
e1a2eb5a35 | ||
|
349add5700 | ||
|
2bc7699a98 | ||
|
2c43a2ae0a | ||
|
3aafe58d4d | ||
|
08c86f3def | ||
|
37a6c01839 | ||
|
8e53672a1d | ||
|
4d5a091d16 | ||
|
80066e55f3 | ||
|
587ce5ae93 | ||
|
52f54f51b2 | ||
|
95cfff8c37 | ||
|
e07e89d2e2 | ||
|
a0be081fc0 | ||
|
49a41060a6 | ||
|
7cfc3ffe67 | ||
|
83a5587d71 | ||
|
89ba0da6d7 | ||
|
984e7cebe1 | ||
|
21dbaa93c4 | ||
|
ca02095806 | ||
|
4752ec08ab | ||
|
7e17b92a18 | ||
|
85e6c3d3fb | ||
|
705cad76b2 | ||
|
8eb23ed802 | ||
|
a4c812f886 | ||
|
3df3754ae6 | ||
|
131cbebc88 | ||
|
2a3d92682a | ||
|
b8ecc3929d | ||
|
8d5843dc21 | ||
|
8d4b9cdc77 | ||
|
00d370b228 | ||
|
e043527d5e | ||
|
325ab0d002 | ||
|
e748c5f048 | ||
|
563ef221d4 | ||
|
9512528a77 | ||
|
3d41a500c9 | ||
|
3e5319cf70 | ||
|
5777d85f99 | ||
|
bf3a4a88a4 | ||
|
c40ce98bb8 | ||
|
baec6249e5 | ||
|
07ec419578 | ||
|
727e09105f | ||
|
4f2b278e12 | ||
|
7dd8305bdf | ||
|
d728b84e48 | ||
|
00cc97875d | ||
|
d28dc68abc | ||
|
bfd6fd7c49 | ||
|
1eae96fd43 | ||
|
8ca7b855ac | ||
|
c076412ba5 | ||
|
fc3dbf0963 | ||
|
0fdc2ca3eb | ||
|
4346832f71 | ||
|
319605bfaf | ||
|
43aec720a8 | ||
|
142f4a58d8 | ||
|
086f9dda99 | ||
|
71bcbb8566 | ||
|
bf0d23b584 | ||
|
04782a2818 | ||
|
ae7395f9da | ||
|
181e75c952 | ||
|
9f366e93c6 | ||
|
2e41db8d95 | ||
|
fa5f5b0acb | ||
|
1e40eceab3 | ||
|
9ce78e32a5 | ||
|
1f541e4467 | ||
|
56000394c4 | ||
|
cbcaac66d0 | ||
|
1b746e6460 | ||
|
708278e6dc | ||
|
9efdf4c46e | ||
|
9da46e8c99 | ||
|
cc54c76a3e | ||
|
e7ebb86e5a | ||
|
a2db7c5dae | ||
|
3f6c3a7a99 | ||
|
43e87768d2 | ||
|
5c4736e102 | ||
|
c102458808 | ||
|
81419de5bf | ||
|
33f2b303de | ||
|
b72dfa87a4 | ||
|
400a98de92 | ||
|
12ee52a13a | ||
|
c8fba61a05 | ||
|
b5542d4426 | ||
|
0f3d786402 | ||
|
87cf3d4b80 | ||
|
1d49df033c | ||
|
dbb72ba06b | ||
|
6116901ab6 | ||
|
1b3e7f3b96 | ||
|
8bf937d1ff | ||
|
8802ab3c28 | ||
|
2fbd2c1cb6 | ||
|
00ccc2056b | ||
|
332214ba9c | ||
|
d6a81ee7ff | ||
|
aa0c401cb5 | ||
|
7bd77a0817 | ||
|
4e59cf3ea6 | ||
|
f13e54b4a4 | ||
|
05a362c9b5 | ||
|
a7168ffc6e | ||
|
a554b986af | ||
|
034b5385a5 | ||
|
4787cdc200 | ||
|
9f678fdfe8 | ||
|
ec9526d0cd | ||
|
034765aebe | ||
|
013c9e5077 | ||
|
28f6353212 | ||
|
4720f7743e | ||
|
ead24a94c2 | ||
|
5d084cf2ea | ||
|
9777184721 | ||
|
de050fa6db | ||
|
d40ef4be2b | ||
|
49258193ed | ||
|
12d4dab774 | ||
|
7d998f0844 | ||
|
9408ba70d1 | ||
|
05d7e61b84 | ||
|
d8aacc3b9e | ||
|
50cd43c6e5 | ||
|
85e354c139 | ||
|
b4255e55fd | ||
|
d46d8e8c33 | ||
|
562cafc14f | ||
|
415b71f730 | ||
|
f4d1553d48 | ||
|
a96ed550bd | ||
|
dbc2ee4da9 | ||
|
2594cdb614 | ||
|
d6df14923d | ||
|
9973fd314f | ||
|
2a67ffeb35 | ||
|
1a1efbd77d | ||
|
84fbf5dc23 | ||
|
5df134e096 | ||
|
c5ce04f31e | ||
|
79f7cc08e3 | ||
|
d3b708533d | ||
|
8c7ca74c6f | ||
|
c25bcb3345 | ||
|
38baf7ab52 | ||
|
42c96a453f | ||
|
0971a05b33 | ||
|
a7a4ba62af | ||
|
da0b2c6805 | ||
|
4e5a49789b | ||
|
113cf8da38 | ||
|
265719d11f | ||
|
f4b21c7a75 | ||
|
368a2a02fe | ||
|
c044b2431b | ||
|
e26c0a3717 | ||
|
4fa0df401a | ||
|
e94716d94a | ||
|
035ff7ef88 | ||
|
a0fc843513 | ||
|
8123c4ad8f | ||
|
be1f7d72a4 | ||
|
9dde259f54 | ||
|
a2178dc220 | ||
|
38db32e4f2 | ||
|
25dab925ee | ||
|
94d5e345a1 | ||
|
d6968c2e92 | ||
|
a16c5ba00b | ||
|
7afeaa052b | ||
|
5036d164b7 | ||
|
0a6921d2d0 | ||
|
76f8a35916 | ||
|
92f6a496c5 | ||
|
502597c5a4 | ||
|
27745078bc | ||
|
6c92e25ae8 | ||
|
98366c6add | ||
|
375d1f494b | ||
|
e19e229710 | ||
|
834d52f9d3 | ||
|
ec32c252e7 | ||
|
e71a52140a | ||
|
eb5108f81e | ||
|
6dd9e5ff1f | ||
|
f6697188ee | ||
|
0fc7280154 | ||
|
3412dba31f | ||
|
b2151d62da | ||
|
9c27db7ed5 | ||
|
b5abccb1dc | ||
|
71d5b1d943 | ||
|
3fe12ca611 | ||
|
3ee94131ae | ||
|
045d5fffbe | ||
|
43cda1d46a | ||
|
1555c988e0 | ||
|
7f59a8fa7e | ||
|
f79eb57c06 | ||
|
90272e5297 | ||
|
e1f8480b32 | ||
|
16c80c173e | ||
|
a8a6555537 | ||
|
98c2af3fcc | ||
|
36a902fbfa | ||
|
05518a7615 | ||
|
ef507c7932 | ||
|
71177fe977 | ||
|
34d03a7bd2 | ||
|
d65e431619 | ||
|
e405cea361 | ||
|
afcad8a8f0 | ||
|
fcef703eb5 | ||
|
c38ffa5ffd | ||
|
88111610fb | ||
|
3ce6031192 | ||
|
7b08dbd2a1 | ||
|
9c10649045 | ||
|
a1bc7377bd | ||
|
37fbbf55fa | ||
|
2cc701f444 | ||
|
bb6dc43d4b | ||
|
b34b3091d4 | ||
|
ab5a11b399 | ||
|
f68682e153 | ||
|
7455063728 | ||
|
1d52148b37 | ||
|
0b92d3dba6 | ||
|
7eb3ab5747 | ||
|
4585469425 | ||
|
7cfec55410 | ||
|
19bf9b3713 | ||
|
1e20e2bff3 | ||
|
48754046b2 | ||
|
066e7bab05 | ||
|
8826d72ada | ||
|
0a97deaa4d | ||
|
7e901f83ae | ||
|
eff882e5a5 | ||
|
7f33fbe638 | ||
|
10a3e86cc5 | ||
|
d8fac58701 | ||
|
81018268aa | ||
|
a4bcb94958 | ||
|
3a83671669 | ||
|
7f1c2a654b | ||
|
602d8866bb | ||
|
6a85507325 | ||
|
0002d42e38 | ||
|
394a1beb07 | ||
|
d85a4e4874 | ||
|
586ea56693 | ||
|
6ae9e51320 | ||
|
66390554d9 | ||
|
e6b0cbb3bc | ||
|
a56d5d9f77 | ||
|
fa512cfa50 | ||
|
5c58d9e686 | ||
|
f3bdd7de69 | ||
|
719acc6771 | ||
|
711a096ec6 | ||
|
6dde81ee65 | ||
|
c7c611266b | ||
|
e71b59a8c3 | ||
|
5696c5af4f | ||
|
eabb9b202c | ||
|
ed4bcce0cf | ||
|
7ca5c2225f | ||
|
33a30db5f1 | ||
|
dba6256578 | ||
|
df58b9a48b | ||
|
0e355ee296 | ||
|
9769612a44 | ||
|
800fbdf820 | ||
|
40a15bdd21 | ||
|
66468e3ea5 | ||
|
907a3d9915 | ||
|
6c4f4624e3 | ||
|
f8561fa5c4 | ||
|
8c3a933bd9 | ||
|
287a7a814d | ||
|
85d3145862 | ||
|
8bac3dc9e5 | ||
|
6ae9717415 | ||
|
fb4b4df500 | ||
|
b185f4cae7 | ||
|
b892eee0ae | ||
|
d2f1024d81 | ||
|
f7fdc9aa3d | ||
|
26686da30e | ||
|
8ff3380291 | ||
|
ec5697b185 | ||
|
f1624af08a | ||
|
3fbc3281a2 | ||
|
6321c52bd2 | ||
|
557ec44a13 | ||
|
c155b7a56e | ||
|
a023aeba62 | ||
|
7a6d741531 | ||
|
4820a7fede | ||
|
563ca23d00 | ||
|
e38007868e | ||
|
58b04dfcf5 | ||
|
28c987bdd1 | ||
|
e86f73d30c | ||
|
9ce1288e12 | ||
|
3604c8f172 | ||
|
858343a8f6 | ||
|
02c203f748 | ||
|
b78b8c4134 | ||
|
f0edcb224a | ||
|
2d557d6ed7 | ||
|
d8b42dd1ca | ||
|
5ee6e2b8e7 | ||
|
e487206818 | ||
|
c98d91ba1c | ||
|
3e62fcf0cc | ||
|
dc5de1f503 | ||
|
93fc03d9e0 | ||
|
f22504c2fc | ||
|
078230ad49 | ||
|
985252920c | ||
|
c41b183fad | ||
|
6c2b940a03 | ||
|
645bfddfe9 | ||
|
fccc22b651 | ||
|
8622989da9 | ||
|
ba04abfea8 | ||
|
d7dff53f46 | ||
|
9d30136095 | ||
|
cb0adcb0fe | ||
|
0a9dbb608a | ||
|
83857bfcfe | ||
|
388253ede4 | ||
|
7642644d82 | ||
|
59f4ddcd94 | ||
|
606575a2b0 | ||
|
b5d15b635e | ||
|
23e028fe72 | ||
|
342b3a703a | ||
|
bad6f5a188 | ||
|
5052536787 | ||
|
8cae7bd16f | ||
|
082186c169 | ||
|
b71a2cb69e | ||
|
bd754c1989 | ||
|
a7511d3a2c | ||
|
e3663d41ce | ||
|
06757f10c6 | ||
|
b8323ed8e7 | ||
|
e92b03bed5 | ||
|
5f71ac7d41 | ||
|
6c040e68a2 | ||
|
038f81e8e1 | ||
|
917dc16ea6 | ||
|
b369ccfa08 | ||
|
60e29028a8 | ||
|
dfcd7f2563 | ||
|
afed25a180 | ||
|
aaf9e92d24 | ||
|
4b71465922 | ||
|
bd0d418986 | ||
|
d594a0eca8 | ||
|
3822d6568e | ||
|
087f1c55cd | ||
|
d4d8a76a09 | ||
|
a837fb6836 | ||
|
d86bbd8a86 | ||
|
360d5cfac5 | ||
|
48efec307c | ||
|
d5960a7630 | ||
|
5e7002ce76 | ||
|
c3ad42564a | ||
|
313a004141 | ||
|
3a198d052a | ||
|
e405a20e3d | ||
|
ad251e9a8d | ||
|
98825fa4bc | ||
|
62e58bd953 | ||
|
215d131d2f | ||
|
47bb5292fa | ||
|
c592e94f73 | ||
|
0dc8e57c50 | ||
|
d1d87221d5 | ||
|
e2cdd80a5c | ||
|
81a2ed0bfe | ||
|
11bed46a26 | ||
|
8b0cd488d4 | ||
|
1a52cf9a3d | ||
|
b5cc0a81da | ||
|
74400642ed | ||
|
0e2d9be6a8 | ||
|
ba29de5670 | ||
|
51b72d8dc8 | ||
|
2044d29edb | ||
|
2f87698a0b | ||
|
ecb1edd5c2 | ||
|
52e68cf23e | ||
|
81c1cf3d61 | ||
|
8dff91d4c6 | ||
|
e8fc58afd6 | ||
|
256c84a9a5 | ||
|
8f373ccec3 | ||
|
eb1ded2a65 | ||
|
0ed4050bb6 | ||
|
b9d3abee31 | ||
|
1232ff3ee1 | ||
|
a3c3be26f4 | ||
|
f728542ce7 | ||
|
02eb6856ed | ||
|
888348bd94 | ||
|
2ef0e23dcf | ||
|
a749d02663 | ||
|
573101fc90 | ||
|
dda3237e0a | ||
|
dda294d911 | ||
|
edd090efde | ||
|
27cd00b9f9 | ||
|
93dacda923 | ||
|
7e5285d859 | ||
|
17ea139ce4 | ||
|
c200a088c0 | ||
|
a74c402f13 | ||
|
774e9bf463 | ||
|
da3683028e | ||
|
4a31c5a927 | ||
|
80a809216b | ||
|
3df62d26a7 | ||
|
51c37fef98 | ||
|
20ca84966e | ||
|
4653a0e5df | ||
|
692d7e76ad | ||
|
ad83a7e076 | ||
|
48b422e289 | ||
|
a8bb6ae348 | ||
|
5e0923b560 | ||
|
df2900e215 | ||
|
cf8792ef9e | ||
|
7c57d71e84 | ||
|
f089c78b99 | ||
|
dddb20a0f4 | ||
|
5cfa895881 | ||
|
11996e9121 | ||
|
67c0872c9f | ||
|
6817761a08 | ||
|
3c99302ed6 | ||
|
90e8e30a3a | ||
|
cc776e8def | ||
|
bb7976a75f | ||
|
0efee6be5d | ||
|
2e58f53f18 | ||
|
78a1b18e44 | ||
|
473f97c7b8 | ||
|
e26bbdd937 | ||
|
d8ab8fec34 | ||
|
815f7528c5 | ||
|
85cad0efc3 | ||
|
b179d012d7 | ||
|
b1bffc4f8d | ||
|
09969355fa | ||
|
c01a9b1843 | ||
|
8fa6d61271 | ||
|
6c7cd4e4fa | ||
|
a22c9276e5 | ||
|
0a79df3d13 | ||
|
5f7af3e43f | ||
|
a427a0fb18 | ||
|
50eadde8b2 | ||
|
937e257d60 | ||
|
4a4123e660 | ||
|
d1a9bdb5c3 | ||
|
5bb028e3d7 | ||
|
c1f567c4e4 | ||
|
3f0c01ed3c | ||
|
b2d7dfe546 | ||
|
f5c5d33639 | ||
|
adfbffb267 | ||
|
9a3d84319d | ||
|
344d85c19e | ||
|
495361ef53 | ||
|
c7a48c3c2c | ||
|
0b9aa7cbeb | ||
|
760ccc8358 | ||
|
16e9f221b5 | ||
|
ea69be6117 | ||
|
ecdfed257c | ||
|
d7fded7d98 | ||
|
e1e24327b0 | ||
|
24b85d318c | ||
|
47d7adf7b0 | ||
|
0a80119f5e | ||
|
f8e636db80 | ||
|
5cce7deb34 | ||
|
1f5c0fe7df | ||
|
77e4ead0d7 | ||
|
0408591141 | ||
|
822b030938 | ||
|
ef18eb5ce3 | ||
|
71b4de9369 | ||
|
88ccaec8fa | ||
|
5dd92b7d6b | ||
|
95280e4f7c | ||
|
fa231fb472 | ||
|
999bca84c9 | ||
|
02a6c1c603 | ||
|
9231e0d28d | ||
|
c961f0b3c6 | ||
|
bd207b5f06 | ||
|
c792923429 | ||
|
ad953f01a7 | ||
|
71556031bf | ||
|
8030fea443 | ||
|
cdd4afb79a | ||
|
c9aa461c65 | ||
|
c65d5a532a | ||
|
69ad249619 | ||
|
c920537380 | ||
|
46cd025835 | ||
|
c9a30f7139 | ||
|
6bd03d59d7 | ||
|
8128292b02 | ||
|
b72684192f | ||
|
aa86c27ea2 | ||
|
d9b8f5941d | ||
|
316569b019 | ||
|
d8eeb65b5c | ||
|
fc43421e68 | ||
|
0148401c06 | ||
|
227d9422ff | ||
|
d112cc5988 | ||
|
a77b27987e | ||
|
87634240c9 | ||
|
f955280d1a | ||
|
8fb4214809 | ||
|
2ed800bae3 | ||
|
51eb2e593f | ||
|
c9777fd16e | ||
|
4610f85af8 | ||
|
5287758f0e | ||
|
92b04225b2 | ||
|
aa84d6d8db | ||
|
061c33f8d6 | ||
|
9824ae48cf | ||
|
84c8711f79 | ||
|
78ff67eb13 | ||
|
5c6971a608 | ||
|
a2b6d3dd05 | ||
|
b01137f53a | ||
|
8e320da051 | ||
|
2fbf17a109 | ||
|
2975e1fc42 | ||
|
aa94fe157e | ||
|
bb636b81c2 | ||
|
e504c7227e | ||
|
73945fb569 | ||
|
f232e433e6 | ||
|
3888307f89 | ||
|
c15ce93542 | ||
|
94d46f7960 | ||
|
a0d07343e1 | ||
|
a2140110c2 | ||
|
2ae6cc3b7c | ||
|
cc4031f866 | ||
|
26e564c5b8 | ||
|
c78ff81f3a | ||
|
9f30aa2d45 | ||
|
210a2aed49 | ||
|
0bb1dd607d | ||
|
bcfb5182fd | ||
|
b9cfa32029 | ||
|
893d6f681e | ||
|
a8feba6ab4 | ||
|
05d71aac75 | ||
|
aaf7abe831 | ||
|
9875526891 | ||
|
751c4245c5 | ||
|
036210e097 | ||
|
0e2092530b | ||
|
630556004a | ||
|
9992952908 | ||
|
c54b495b16 | ||
|
907b34676d | ||
|
92dbba91b7 | ||
|
b4657a5796 | ||
|
d3716ff5db | ||
|
5da0fef083 | ||
|
9fc431b581 | ||
|
3edb4baf9b | ||
|
e24fb01e3c | ||
|
7952f94f53 | ||
|
77d68689ba | ||
|
b9c4e62e97 | ||
|
ceffc4de69 | ||
|
80fb69a9ae | ||
|
241b59dda9 | ||
|
2d09ab8537 | ||
|
bcd523082b | ||
|
4d0b9a0f15 | ||
|
012d0098e0 | ||
|
6543f9272a | ||
|
208651a4c0 | ||
|
9109b9de90 | ||
|
d17bd0aa3e | ||
|
2635a0c70d | ||
|
dac4f2dc19 | ||
|
4a2f10b116 | ||
|
1e3b30124f | ||
|
0922f91c33 | ||
|
0f76e8f817 | ||
|
bf5e0e2881 | ||
|
a48b41eaf9 | ||
|
e2904a4d6e | ||
|
135a171fe4 | ||
|
8e5555a044 | ||
|
657810aef7 | ||
|
100e03ec0c | ||
|
3787269812 | ||
|
af9e51e58d | ||
|
f53c26c7ab | ||
|
81013fbafb | ||
|
a78c487173 | ||
|
ef8d7e2bd2 | ||
|
7913052d07 | ||
|
0ef065e5cc | ||
|
018811036b | ||
|
a056acba62 | ||
|
a55741b03c | ||
|
1820a4eb60 | ||
|
8c321875cf | ||
|
f3dff38541 | ||
|
13f9b658c1 | ||
|
e5831057f0 | ||
|
bd41abcb5a | ||
|
5cb5d9d65a | ||
|
fd0b9223cf | ||
|
030fde2cff | ||
|
ed12231ee8 | ||
|
6f99372287 | ||
|
7940f02ba9 | ||
|
6fa32a7287 | ||
|
9f1c5fa682 | ||
|
35169480d0 | ||
|
5ca6f6cb7b | ||
|
8bf6b22765 | ||
|
f296a2a73f | ||
|
c414b3f688 | ||
|
b75da9fb8a | ||
|
e5a8eff4ac | ||
|
f74c23f02e | ||
|
3feae35f69 | ||
|
12c4485a43 | ||
|
29e5502e0e | ||
|
26f364de0b | ||
|
dbc981c6d2 | ||
|
a0228ae6b2 | ||
|
f4062c3fc3 | ||
|
99eec49a06 | ||
|
3f3bb113ce | ||
|
7804f10732 | ||
|
e2036da75f | ||
|
b19bca7f3b | ||
|
0ffdd8d9ab | ||
|
fd16de8748 | ||
|
4f52498a06 | ||
|
2696ae9b73 | ||
|
c339afc1ce | ||
|
ec4a49498d | ||
|
6e0789bfcd | ||
|
b68b1753bb | ||
|
84c1da60dd | ||
|
0802aaced0 | ||
|
0f66dba7e8 | ||
|
a7b3d193eb | ||
|
70a9de63f9 | ||
|
8cf5b1d9ab | ||
|
9c4706f7c5 | ||
|
e408541c7b | ||
|
1c24e1b954 | ||
|
7923f45595 | ||
|
3620e2ad4e | ||
|
3e77f258b4 | ||
|
c4fba3b7ca | ||
|
feb8fb9b13 | ||
|
ac0e699069 | ||
|
453d6d6ab7 | ||
|
55a1450dd3 | ||
|
cd0be62993 | ||
|
550ad386f0 | ||
|
04492600e5 | ||
|
ff3968faea | ||
|
2c446658e5 | ||
|
27a0d4147f | ||
|
0f279abecf | ||
|
73d37a1bb2 | ||
|
1e4393f74d | ||
|
387b997b1d | ||
|
05144487ce | ||
|
05a48c634d | ||
|
e6e08f60bc | ||
|
8f8b38d757 | ||
|
ba4d1d6abf | ||
|
9deec688ed | ||
|
8ea2fde16e | ||
|
af4c63512c | ||
|
79d7636ab6 | ||
|
4cadf33b4f | ||
|
12ffb027fa | ||
|
12f264b290 | ||
|
4168439abe | ||
|
b6522f393c | ||
|
cbf0d8fedd | ||
|
3529fcb29b | ||
|
1737089c6d | ||
|
24cb8306c0 | ||
|
91be6ac51e | ||
|
86fe19d84c | ||
|
7d59489183 | ||
|
f9303dab72 | ||
|
37ed617fce | ||
|
043e548f49 | ||
|
f2ed1e58d9 | ||
|
1827099381 | ||
|
b5458493ce | ||
|
c87d87d1ea | ||
|
568718d573 | ||
|
9a9d7f8873 | ||
|
6611c861d8 | ||
|
7a29502790 | ||
|
b772499b9b | ||
|
907a093117 | ||
|
d3f98d75f6 | ||
|
4de77c95e1 | ||
|
ccd8349ef6 | ||
|
3c2f2c3c06 | ||
|
c88e933e9e | ||
|
e7e2ed518a | ||
|
feaa09616d | ||
|
80f98c5fd3 | ||
|
995150bfa5 | ||
|
35f42f4e0c | ||
|
dd377c7da3 | ||
|
6e38e48faf | ||
|
ccfed3df3f | ||
|
145183d0ae | ||
|
8bd9669d3d | ||
|
ee46d8c7be | ||
|
2172edc033 | ||
|
b91e10ef8a | ||
|
7caf96c746 | ||
|
5ccd815fbd | ||
|
c74a75133e | ||
|
76ecd895e1 | ||
|
46918d3ba0 | ||
|
88a81e306b | ||
|
afd521de29 | ||
|
ac416b6ab0 | ||
|
8d4cf2d059 | ||
|
6c7cf55b18 | ||
|
cf80f87dca | ||
|
d8201c44fa | ||
|
7219babbd7 | ||
|
8078937359 | ||
|
a6346664a3 | ||
|
e6788993ba | ||
|
eec4c52c33 | ||
|
c0460060a0 | ||
|
397df4ea1a | ||
|
888d5037da | ||
|
01ddc83c4b | ||
|
a9e09dcee8 | ||
|
66c375bfe3 | ||
|
51e97510c0 | ||
|
522f769cad | ||
|
9025b1c8f0 | ||
|
d8d884c9be | ||
|
d67b7bc6a1 | ||
|
b24e9a1051 | ||
|
25e967200b | ||
|
9f655d9416 | ||
|
01190fb6ff | ||
|
4c8f5367ba | ||
|
ec67303594 | ||
|
3a566241f3 | ||
|
0db5b1cfbc | ||
|
da3769b902 | ||
|
01130b8af5 | ||
|
251d7106e2 | ||
|
77ec170dd5 | ||
|
b17f081906 | ||
|
19dff03784 | ||
|
ad607eb122 | ||
|
31ea09ff30 | ||
|
2b676743ae | ||
|
ff7bf20f6d | ||
|
0f10ae884f | ||
|
50c1ab62ba | ||
|
449b8c67d1 | ||
|
ab72393e66 | ||
|
9cf4da5fcb | ||
|
89790e7508 | ||
|
58291a3b25 | ||
|
a1454fdd74 | ||
|
6c42877758 | ||
|
3acf5fd588 | ||
|
40ce604d2c | ||
|
d58d1f8d89 | ||
|
58dbfefc15 | ||
|
33d4f44f04 | ||
|
1711e6a529 | ||
|
7df1f8a88d | ||
|
b9695c877a | ||
|
de424f7d21 | ||
|
9e715cd902 | ||
|
4636f9290a | ||
|
6eb00083d9 | ||
|
edfbbf52ef | ||
|
4936e40258 | ||
|
7f6598518e | ||
|
be449ab1c0 | ||
|
0f94f2b2ef | ||
|
e5357ccc84 | ||
|
da09bc3210 | ||
|
7d5d7eac3c | ||
|
8744f6a90e | ||
|
aa2f4c5bac | ||
|
8bc14953d7 | ||
|
da0927aa84 | ||
|
ed070b6a9a | ||
|
427a08106b | ||
|
161f198451 | ||
|
feb0825a63 |
|
@ -1,2 +1,2 @@
|
|||
[run]
|
||||
omit = graphene/utils/enum.py,graphene/contrib/django/debug/sql/tracking.py,*/tests/*
|
||||
omit = graphene/pyutils/*,*/tests/*
|
||||
|
|
|
@ -11,4 +11,3 @@ trim_trailing_whitespace = true
|
|||
[*.{py,rst,ini}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
|
|
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: "\U0001F41B bug"
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Note: for support questions, please use stackoverflow**. This repository's issues are reserved for feature requests and bug reports.
|
||||
|
||||
* **What is the current behavior?**
|
||||
|
||||
|
||||
|
||||
* **If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem** via
|
||||
a github repo, https://repl.it or similar.
|
||||
|
||||
|
||||
|
||||
* **What is the expected behavior?**
|
||||
|
||||
|
||||
|
||||
* **What is the motivation / use case for changing the behavior?**
|
||||
|
||||
|
||||
|
||||
* **Please tell us about your environment:**
|
||||
|
||||
- Version:
|
||||
- Platform:
|
||||
|
||||
* **Other information** (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. stackoverflow)
|
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
blank_issues_enabled: false
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: "✨ enhancement"
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
24
.github/stale.yml
vendored
Normal file
24
.github/stale.yml
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: false
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: false
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
- 🐛 bug
|
||||
- 📖 documentation
|
||||
- 🙋 help wanted
|
||||
- ✨ enhancement
|
||||
- good first issue
|
||||
- work in progress
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: wontfix
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: false
|
||||
# markComment: >
|
||||
# This issue has been automatically marked as stale because it has not had
|
||||
# recent activity. It will be closed if no further activity occurs. Thank you
|
||||
# for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
21
.github/workflows/build.yaml
vendored
Normal file
21
.github/workflows/build.yaml
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
name: 📦 Build
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install build twine
|
||||
- name: Building package
|
||||
run: python3 -m build
|
||||
- name: Check package with Twine
|
||||
run: twine check dist/*
|
26
.github/workflows/deploy.yml
vendored
Normal file
26
.github/workflows/deploy.yml
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
name: 🚀 Deploy to PyPI
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Build wheel and source tarball
|
||||
run: |
|
||||
pip install wheel
|
||||
python setup.py sdist bdist_wheel
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@v1.1.0
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.pypi_password }}
|
26
.github/workflows/lint.yml
vendored
Normal file
26
.github/workflows/lint.yml
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
name: 💅 Lint
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install tox
|
||||
- name: Run lint
|
||||
run: tox
|
||||
env:
|
||||
TOXENV: pre-commit
|
||||
- name: Run mypy
|
||||
run: tox
|
||||
env:
|
||||
TOXENV: mypy
|
64
.github/workflows/tests.yml
vendored
Normal file
64
.github/workflows/tests.yml
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
name: 📄 Tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- '*.x'
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '*.md'
|
||||
- '*.rst'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- '*.x'
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '*.md'
|
||||
- '*.rst'
|
||||
jobs:
|
||||
tests:
|
||||
# runs the test suite
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- {name: '3.13', python: '3.13', os: ubuntu-latest, tox: py313}
|
||||
- {name: '3.12', python: '3.12', os: ubuntu-latest, tox: py312}
|
||||
- {name: '3.11', python: '3.11', os: ubuntu-latest, tox: py311}
|
||||
- {name: '3.10', python: '3.10', os: ubuntu-latest, tox: py310}
|
||||
- {name: '3.9', python: '3.9', os: ubuntu-latest, tox: py39}
|
||||
- {name: '3.8', python: '3.8', os: ubuntu-latest, tox: py38}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
|
||||
- name: update pip
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade setuptools wheel
|
||||
|
||||
- name: get pip cache dir
|
||||
id: pip-cache
|
||||
run: echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
|
||||
- name: cache pip dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.pip-cache.outputs.dir }}
|
||||
key: pip|${{ runner.os }}|${{ matrix.python }}|${{ hashFiles('setup.py') }}
|
||||
- run: pip install tox
|
||||
- run: tox -e ${{ matrix.tox }}
|
||||
- name: Upload coverage.xml
|
||||
if: ${{ matrix.python == '3.10' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: graphene-coverage
|
||||
path: coverage.xml
|
||||
if-no-files-found: error
|
||||
- name: Upload coverage.xml to codecov
|
||||
if: ${{ matrix.python == '3.10' }}
|
||||
uses: codecov/codecov-action@v4
|
17
.gitignore
vendored
17
.gitignore
vendored
|
@ -10,7 +10,6 @@ __pycache__/
|
|||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
|
@ -42,9 +41,11 @@ htmlcov/
|
|||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
.pytest_cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
*.cover
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
@ -59,6 +60,14 @@ docs/_build/
|
|||
# PyBuilder
|
||||
target/
|
||||
|
||||
# VirtualEnv
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
|
||||
# Typing
|
||||
.mypy_cache/
|
||||
|
||||
/tests/django.sqlite
|
||||
|
||||
|
@ -75,6 +84,10 @@ target/
|
|||
|
||||
# PyCharm
|
||||
.idea
|
||||
*.iml
|
||||
|
||||
# Databases
|
||||
*.sqlite3
|
||||
.vscode
|
||||
.mypy_cache
|
||||
.ruff_cache
|
||||
|
|
29
.pre-commit-config.yaml
Normal file
29
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,29 @@
|
|||
default_language_version:
|
||||
python: python3.10
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.3.0
|
||||
hooks:
|
||||
- id: check-merge-conflict
|
||||
- id: check-json
|
||||
- id: check-yaml
|
||||
- id: debug-statements
|
||||
- id: end-of-file-fixer
|
||||
exclude: ^docs/.*$
|
||||
- id: pretty-format-json
|
||||
args:
|
||||
- --autofix
|
||||
- id: trailing-whitespace
|
||||
exclude: README.md
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.37.3
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.5.0
|
||||
hooks:
|
||||
- id: ruff
|
||||
- id: ruff-format
|
||||
args: [ --check ]
|
96
.travis.yml
96
.travis.yml
|
@ -1,96 +0,0 @@
|
|||
language: python
|
||||
sudo: false
|
||||
python:
|
||||
- 2.7
|
||||
- 3.4
|
||||
- 3.5
|
||||
- pypy
|
||||
cache:
|
||||
directories:
|
||||
- .cache/pip/
|
||||
- $HOME/.cache/pip
|
||||
- docs/node_modules/
|
||||
- $HOME/docs/node_modules
|
||||
before_install:
|
||||
- |
|
||||
git_diff=$(git diff --name-only $TRAVIS_COMMIT_RANGE)
|
||||
if [ "$?" == 0 ] && [ "$TEST_TYPE" != build_website ] && \
|
||||
! echo "$git_diff" | grep -qvE '(\.md$)|(^(docs))/'
|
||||
then
|
||||
echo "Only docs were updated, stopping build process."
|
||||
exit
|
||||
fi
|
||||
install:
|
||||
- |
|
||||
if [ "$TEST_TYPE" = build ]; then
|
||||
pip install --download-cache $HOME/.cache/pip/ pytest pytest-cov coveralls six pytest-django django-filter sqlalchemy_utils
|
||||
pip install --download-cache $HOME/.cache/pip psycopg2 > /dev/null 2>&1
|
||||
pip install --download-cache $HOME/.cache/pip/ -e .
|
||||
pip install --download-cache $HOME/.cache/pip/ -e .[django]
|
||||
pip install --download-cache $HOME/.cache/pip/ -e .[sqlalchemy]
|
||||
pip install django==$DJANGO_VERSION
|
||||
python setup.py develop
|
||||
elif [ "$TEST_TYPE" = build_website ]; then
|
||||
pip install --download-cache $HOME/.cache/pip/ -e .
|
||||
python setup.py develop
|
||||
elif [ "$TEST_TYPE" = lint ]; then
|
||||
pip install --download-cache $HOME/.cache/pip/ flake8
|
||||
fi
|
||||
script:
|
||||
- |
|
||||
if [ "$TEST_TYPE" = build_website ]; then
|
||||
if [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = false ]; then
|
||||
echo "Building the web."
|
||||
nvm install 4.0
|
||||
|
||||
GH_PAGES_DIR="$TRAVIS_BUILD_DIR"/docs/public
|
||||
git config --global user.name "Travis CI"
|
||||
git config --global user.email "travis@graphene-python.org"
|
||||
git clone --branch gh-pages --depth=50 \
|
||||
https://graphql-python-bot@github.com/graphql-python/graphene.git \
|
||||
$GH_PAGES_DIR
|
||||
cd docs
|
||||
./playground/graphene-js/build.sh
|
||||
npm run deploy
|
||||
cd $GH_PAGES_DIR
|
||||
git status
|
||||
git add --intent-to-add .
|
||||
if ! git diff-index --quiet HEAD --; then
|
||||
git add -A .
|
||||
git commit -m "Rebuild website"
|
||||
git push "https://${GITHUB_TOKEN}@github.com/graphql-python/graphene.git" gh-pages
|
||||
fi
|
||||
exit
|
||||
fi
|
||||
elif [ "$TEST_TYPE" = lint ]; then
|
||||
echo "Checking Python code lint."
|
||||
flake8
|
||||
exit
|
||||
elif [ "$TEST_TYPE" = build ]; then
|
||||
py.test --cov=graphene
|
||||
fi
|
||||
after_success:
|
||||
- |
|
||||
if [ "$TEST_TYPE" = build ]; then
|
||||
coveralls
|
||||
fi
|
||||
env:
|
||||
matrix:
|
||||
- TEST_TYPE=build
|
||||
global:
|
||||
secure: SQC0eCWCWw8bZxbLE8vQn+UjJOp3Z1m779s9SMK3lCLwJxro/VCLBZ7hj4xsrq1MtcFO2U2Kqf068symw4Hr/0amYI3HFTCFiwXAC3PAKXeURca03eNO2heku+FtnQcOjBanExTsIBQRLDXMOaUkf3MIztpLJ4LHqMfUupKmw9YSB0v40jDbSN8khBnndFykmOnVVHznFp8USoN5F0CiPpnfEvHnJkaX76lNf7Kc9XNShBTTtJsnsHMhuYQeInt0vg9HSjoIYC38Tv2hmMj1myNdzyrHF+LgRjI6ceGi50ApAnGepXC/DNRhXROfECKez+LON/ZSqBGdJhUILqC8A4WmWmIjNcwitVFp3JGBqO7LULS0BI96EtSLe8rD1rkkdTbjivajkbykM1Q0Tnmg1adzGwLxRUbTq9tJQlTTkHBCuXIkpKb1mAtb/TY7A6BqfnPi2xTc/++qEawUG7ePhscdTj0IBrUfZsUNUYZqD8E8XbSWKIuS3SHE+cZ+s/kdAsm4q+FFAlpZKOYGxIkwvgyfu4/Plfol4b7X6iAP9J3r1Kv0DgBVFst5CXEwzZs19/g0CgokQbCXf1N+xeNnUELl6/fImaR3RKP22EaABoil4z8vzl4EqxqVoH1nfhE+WlpryXsuSaF/1R+WklR7aQ1FwoCk8V8HxM2zrj4tI8k=
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- python: '2.7'
|
||||
env: TEST_TYPE=build DJANGO_VERSION=1.6
|
||||
- python: '2.7'
|
||||
env: TEST_TYPE=build DJANGO_VERSION=1.7
|
||||
- python: '2.7'
|
||||
env: TEST_TYPE=build DJANGO_VERSION=1.8
|
||||
- python: '2.7'
|
||||
env: TEST_TYPE=build DJANGO_VERSION=1.9
|
||||
- python: '2.7'
|
||||
env: TEST_TYPE=build_website
|
||||
- python: '2.7'
|
||||
env: TEST_TYPE=lint
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Syrus Akbary
|
||||
Copyright (c) 2015-Present Syrus Akbary
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
@ -2,3 +2,4 @@ global-exclude tests/*
|
|||
recursive-exclude tests *
|
||||
recursive-exclude tests_py35 *
|
||||
recursive-exclude examples *
|
||||
include LICENSE
|
||||
|
|
28
Makefile
Normal file
28
Makefile
Normal file
|
@ -0,0 +1,28 @@
|
|||
.PHONY: help
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@grep -E '^\.PHONY: [a-zA-Z_-]+ .*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = "(: |##)"}; {printf "\033[36m%-30s\033[0m %s\n", $$2, $$3}'
|
||||
|
||||
.PHONY: install-dev ## Install development dependencies
|
||||
install-dev:
|
||||
pip install -e ".[dev]"
|
||||
|
||||
.PHONY: test ## Run tests
|
||||
test:
|
||||
py.test graphene examples
|
||||
|
||||
.PHONY: docs ## Generate docs
|
||||
docs: install-dev
|
||||
cd docs && make install && make html
|
||||
|
||||
.PHONY: docs-live ## Generate docs with live reloading
|
||||
docs-live: install-dev
|
||||
cd docs && make install && make livehtml
|
||||
|
||||
.PHONY: format
|
||||
format:
|
||||
black graphene examples setup.py
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
flake8 graphene examples setup.py
|
110
README.md
110
README.md
|
@ -1,47 +1,53 @@
|
|||
#  [Graphene](http://graphene-python.org) [](https://travis-ci.org/graphql-python/graphene) [](https://badge.fury.io/py/graphene) [](https://coveralls.io/github/graphql-python/graphene?branch=master)
|
||||
#  [Graphene](http://graphene-python.org) [](https://badge.fury.io/py/graphene) [](https://coveralls.io/github/graphql-python/graphene?branch=master) [](https://discord.gg/T6Gp6NFYHe)
|
||||
|
||||
[💬 Join the community on Discord](https://discord.gg/T6Gp6NFYHe)
|
||||
|
||||
[Graphene](http://graphene-python.org) is a Python library for building GraphQL schemas/types fast and easily.
|
||||
**We are looking for contributors**! Please check the current issues to see how you can help ❤️
|
||||
|
||||
## Introduction
|
||||
|
||||
[Graphene](http://graphene-python.org) is an opinionated Python library for building GraphQL schemas/types fast and easily.
|
||||
|
||||
- **Easy to use:** Graphene helps you use GraphQL in Python without effort.
|
||||
- **Relay:** Graphene has builtin support for Relay
|
||||
- **Django:** Automatic *Django model* mapping to Graphene Types. Check a fully working [Django](http://github.com/graphql-python/swapi-graphene) implementation
|
||||
- **Relay:** Graphene has builtin support for Relay.
|
||||
- **Data agnostic:** Graphene supports any kind of data source: SQL (Django, SQLAlchemy), Mongo, custom Python objects, etc.
|
||||
We believe that by providing a complete API you could plug Graphene anywhere your data lives and make your data available
|
||||
through GraphQL.
|
||||
|
||||
Graphene also supports *SQLAlchemy*!
|
||||
## Integrations
|
||||
|
||||
*What is supported in this Python version?* **Everything**: Interfaces, ObjectTypes, Scalars, Unions and Relay (Nodes, Connections), in addition to queries, mutations and subscriptions.
|
||||
Graphene has multiple integrations with different frameworks:
|
||||
|
||||
**NEW**!: [Try graphene online](http://graphene-python.org/playground/)
|
||||
| integration | Package |
|
||||
| ----------------- | --------------------------------------------------------------------------------------- |
|
||||
| SQLAlchemy | [graphene-sqlalchemy](https://github.com/graphql-python/graphene-sqlalchemy/) |
|
||||
| Mongo | [graphene-mongo](https://github.com/graphql-python/graphene-mongo/) |
|
||||
| Apollo Federation | [graphene-federation](https://github.com/graphql-python/graphene-federation/) |
|
||||
| Django | [graphene-django](https://github.com/graphql-python/graphene-django/) |
|
||||
|
||||
Also, Graphene is fully compatible with the GraphQL spec, working seamlessly with all GraphQL clients, such as [Relay](https://github.com/facebook/relay), [Apollo](https://github.com/apollographql/apollo-client) and [gql](https://github.com/graphql-python/gql).
|
||||
|
||||
## Installation
|
||||
|
||||
For instaling graphene, just run this command in your shell
|
||||
To install `graphene`, just run this command in your shell
|
||||
|
||||
```bash
|
||||
pip install graphene
|
||||
# In case of need Django model support
|
||||
pip install graphene[django]
|
||||
# Or in case of need SQLAlchemy support
|
||||
pip install graphene[sqlalchemy]
|
||||
pip install "graphene>=3.1"
|
||||
```
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
Here is one example for get you started:
|
||||
Here is one example for you to get started:
|
||||
|
||||
```python
|
||||
import graphene
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
hello = graphene.String(description='A typical hello world')
|
||||
ping = graphene.String(description='Ping someone',
|
||||
to=graphene.String())
|
||||
|
||||
def resolve_hello(self, args, info):
|
||||
def resolve_hello(self, info):
|
||||
return 'World'
|
||||
|
||||
def resolve_ping(self, args, info):
|
||||
return 'Pinging {}'.format(args.get('to'))
|
||||
|
||||
schema = graphene.Schema(query=Query)
|
||||
```
|
||||
|
||||
|
@ -51,7 +57,6 @@ Then Querying `graphene.Schema` is as simple as:
|
|||
query = '''
|
||||
query SayHello {
|
||||
hello
|
||||
ping(to:"peter")
|
||||
}
|
||||
'''
|
||||
result = schema.execute(query)
|
||||
|
@ -59,22 +64,67 @@ result = schema.execute(query)
|
|||
|
||||
If you want to learn even more, you can also check the following [examples](examples/):
|
||||
|
||||
* **Basic Schema**: [Starwars example](examples/starwars)
|
||||
* **Relay Schema**: [Starwars Relay example](examples/starwars_relay)
|
||||
* **Django model mapping**: [Starwars Django example](examples/starwars_django)
|
||||
* **SQLAlchemy model mapping**: [Flask SQLAlchemy example](examples/flask_sqlalchemy)
|
||||
- **Basic Schema**: [Starwars example](examples/starwars)
|
||||
- **Relay Schema**: [Starwars Relay example](examples/starwars_relay)
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation and links to additional resources are available at
|
||||
https://docs.graphene-python.org/en/latest/
|
||||
|
||||
## Contributing
|
||||
|
||||
After cloning this repo, ensure dependencies are installed by running:
|
||||
After cloning this repo, create a [virtualenv](https://virtualenv.pypa.io/en/stable/) and ensure dependencies are installed by running:
|
||||
|
||||
```sh
|
||||
python setup.py install
|
||||
virtualenv venv
|
||||
source venv/bin/activate
|
||||
pip install -e ".[test]"
|
||||
```
|
||||
|
||||
After developing, the full test suite can be evaluated by running:
|
||||
Well-written tests and maintaining good test coverage is important to this project. While developing, run new and existing tests with:
|
||||
|
||||
```sh
|
||||
python setup.py test # Use --pytest-args="-v -s" for verbose mode
|
||||
pytest graphene/relay/tests/test_node.py # Single file
|
||||
pytest graphene/relay # All tests in directory
|
||||
```
|
||||
|
||||
Add the `-s` flag if you have introduced breakpoints into the code for debugging.
|
||||
Add the `-v` ("verbose") flag to get more detailed test output. For even more detailed output, use `-vv`.
|
||||
Check out the [pytest documentation](https://docs.pytest.org/en/latest/) for more options and test running controls.
|
||||
|
||||
Regularly ensure your `pre-commit` hooks are up to date and enabled:
|
||||
|
||||
```sh
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
You can also run the benchmarks with:
|
||||
|
||||
```sh
|
||||
pytest graphene --benchmark-only
|
||||
```
|
||||
|
||||
Graphene supports several versions of Python. To make sure that changes do not break compatibility with any of those versions, we use `tox` to create virtualenvs for each Python version and run tests with that version. To run against all Python versions defined in the `tox.ini` config file, just run:
|
||||
|
||||
```sh
|
||||
tox
|
||||
```
|
||||
|
||||
If you wish to run against a specific version defined in the `tox.ini` file:
|
||||
|
||||
```sh
|
||||
tox -e py39
|
||||
```
|
||||
|
||||
Tox can only use whatever versions of Python are installed on your system. When you create a pull request, GitHub Actions pipelines will also be running the same tests and report the results, so there is no need for potential contributors to try to install every single version of Python on their own system ahead of time. We appreciate opening issues and pull requests to make graphene even more stable & useful!
|
||||
|
||||
### Building Documentation
|
||||
|
||||
The documentation is generated using the excellent [Sphinx](http://www.sphinx-doc.org/) and a custom theme.
|
||||
|
||||
An HTML version of the documentation is produced by running:
|
||||
|
||||
```sh
|
||||
make docs
|
||||
```
|
||||
|
|
101
README.rst
101
README.rst
|
@ -1,101 +0,0 @@
|
|||
|Graphene Logo| `Graphene <http://graphene-python.org>`__ |Build Status| |PyPI version| |Coverage Status|
|
||||
=========================================================================================================
|
||||
|
||||
`Graphene <http://graphene-python.org>`__ is a Python library for
|
||||
building GraphQL schemas/types fast and easily.
|
||||
|
||||
- **Easy to use:** Graphene helps you use GraphQL in Python without
|
||||
effort.
|
||||
- **Relay:** Graphene has builtin support for Relay
|
||||
- **Django:** Automatic *Django model* mapping to Graphene Types. Check
|
||||
a fully working
|
||||
`Django <http://github.com/graphql-python/swapi-graphene>`__
|
||||
implementation
|
||||
|
||||
Graphene also supports *SQLAlchemy*!
|
||||
|
||||
*What is supported in this Python version?* **Everything**: Interfaces,
|
||||
ObjectTypes, Scalars, Unions and Relay (Nodes, Connections), in addition
|
||||
to queries, mutations and subscriptions.
|
||||
|
||||
**NEW**!: `Try graphene
|
||||
online <http://graphene-python.org/playground/>`__
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
For instaling graphene, just run this command in your shell
|
||||
|
||||
.. code:: bash
|
||||
|
||||
pip install graphene
|
||||
# In case of need Django model support
|
||||
pip install graphene[django]
|
||||
# Or in case of need SQLAlchemy support
|
||||
pip install graphene[sqlalchemy]
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Here is one example for get you started:
|
||||
|
||||
.. code:: python
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
hello = graphene.String(description='A typical hello world')
|
||||
ping = graphene.String(description='Ping someone',
|
||||
to=graphene.String())
|
||||
|
||||
def resolve_hello(self, args, info):
|
||||
return 'World'
|
||||
|
||||
def resolve_ping(self, args, info):
|
||||
return 'Pinging {}'.format(args.get('to'))
|
||||
|
||||
schema = graphene.Schema(query=Query)
|
||||
|
||||
Then Querying ``graphene.Schema`` is as simple as:
|
||||
|
||||
.. code:: python
|
||||
|
||||
query = '''
|
||||
query SayHello {
|
||||
hello
|
||||
ping(to:"peter")
|
||||
}
|
||||
'''
|
||||
result = schema.execute(query)
|
||||
|
||||
If you want to learn even more, you can also check the following
|
||||
`examples <examples/>`__:
|
||||
|
||||
- **Basic Schema**: `Starwars example <examples/starwars>`__
|
||||
- **Relay Schema**: `Starwars Relay
|
||||
example <examples/starwars_relay>`__
|
||||
- **Django model mapping**: `Starwars Django
|
||||
example <examples/starwars_django>`__
|
||||
- **SQLAlchemy model mapping**: `Flask SQLAlchemy
|
||||
example <examples/flask_sqlalchemy>`__
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
After cloning this repo, ensure dependencies are installed by running:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
python setup.py install
|
||||
|
||||
After developing, the full test suite can be evaluated by running:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
python setup.py test # Use --pytest-args="-v -s" for verbose mode
|
||||
|
||||
.. |Graphene Logo| image:: http://graphene-python.org/favicon.png
|
||||
.. |Build Status| image:: https://travis-ci.org/graphql-python/graphene.svg?branch=master
|
||||
:target: https://travis-ci.org/graphql-python/graphene
|
||||
.. |PyPI version| image:: https://badge.fury.io/py/graphene.svg
|
||||
:target: https://badge.fury.io/py/graphene
|
||||
.. |Coverage Status| image:: https://coveralls.io/repos/graphql-python/graphene/badge.svg?branch=master&service=github
|
||||
:target: https://coveralls.io/github/graphql-python/graphene?branch=master
|
15
SECURITY.md
Normal file
15
SECURITY.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Support for security issues is currently provided for Graphene 3.0 and above. Support on earlier versions cannot be guaranteed by the maintainers of this library, but community PRs may be accepted in critical cases.
|
||||
The preferred mitigation strategy is via an upgrade to Graphene 3.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 3.x | :white_check_mark: |
|
||||
| <3.x | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please use responsible disclosure by contacting a core maintainer via Discord or E-Mail.
|
190
UPGRADE-v1.0.md
Normal file
190
UPGRADE-v1.0.md
Normal file
|
@ -0,0 +1,190 @@
|
|||
# v1.0 Upgrade Guide
|
||||
|
||||
Big changes from v0.10.x to 1.0. While on the surface a lot of this just looks like shuffling around API, the entire codebase has been rewritten to handle some really great use cases and improved performance.
|
||||
|
||||
## Backwards Compatibility and Deprecation Warnings
|
||||
|
||||
This has been a community project from the start, we need your help making the upgrade as smooth as possible for everybody!
|
||||
We have done our best to provide backwards compatibility with deprecated APIs.
|
||||
|
||||
## Deprecations
|
||||
|
||||
- `with_context` is no longer needed. Resolvers now always take the context argument.
|
||||
Before:
|
||||
|
||||
```python
|
||||
def resolve_xxx(root, args, info):
|
||||
# ...
|
||||
```
|
||||
|
||||
With 1.0:
|
||||
|
||||
```python
|
||||
def resolve_xxx(root, args, context, info):
|
||||
# ...
|
||||
```
|
||||
|
||||
- `ObjectType` and `Interface` no longer accept the `abstract` option in the `Meta`.
|
||||
Inheriting fields should be now achieved using `AbstractType` inheritance.
|
||||
|
||||
Before:
|
||||
|
||||
```python
|
||||
class MyBaseQuery(graphene.ObjectType):
|
||||
my_field = String()
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
class Query(MyBaseQuery):
|
||||
pass
|
||||
|
||||
```
|
||||
|
||||
With 1.0:
|
||||
|
||||
```python
|
||||
class MyBaseQuery(graphene.AbstractType):
|
||||
my_field = String()
|
||||
|
||||
class Query(MyBaseQuery, graphene.ObjectType):
|
||||
pass
|
||||
```
|
||||
|
||||
- The `type_name` option in the Meta in types is now `name`
|
||||
|
||||
- Type references no longer work with strings, but with functions.
|
||||
|
||||
Before:
|
||||
|
||||
```python
|
||||
class Query(graphene.ObjectType):
|
||||
user = graphene.Field('User')
|
||||
users = graphene.List('User')
|
||||
```
|
||||
|
||||
With 1.0:
|
||||
|
||||
```python
|
||||
class Query(graphene.ObjectType):
|
||||
user = graphene.Field(lambda: User)
|
||||
users = graphene.List(lambda: User)
|
||||
```
|
||||
|
||||
## Schema
|
||||
|
||||
Schemas in graphene `1.0` are `Immutable`, that means that once you create a `graphene.Schema` any
|
||||
change in their attributes will not have any effect.
|
||||
The `name` argument is removed from the Schema.
|
||||
|
||||
The arguments `executor` and `middlewares` are also removed from the `Schema` definition.
|
||||
You can still use them, but by calling explicitly in the `execute` method in `graphql`.
|
||||
|
||||
```python
|
||||
# Old way
|
||||
schema = graphene.Schema(name='My Schema')
|
||||
schema.query = Query
|
||||
schema.mutation = Mutation
|
||||
|
||||
# New way
|
||||
schema = graphene.Schema(
|
||||
query=Query,
|
||||
mutation=Mutation
|
||||
)
|
||||
```
|
||||
|
||||
## Interfaces
|
||||
|
||||
For implementing an Interface in an ObjectType, you have to add it onto `Meta.interfaces`.
|
||||
|
||||
Like:
|
||||
|
||||
```python
|
||||
from graphene import Interface, ObjectType, String
|
||||
|
||||
class Character(Interface):
|
||||
name = String()
|
||||
|
||||
class Human(Character): # Old way, Human will still be an Interface
|
||||
pass
|
||||
|
||||
class Droid(ObjectType): # New way, you have to specify the ObjectType
|
||||
class Meta:
|
||||
interfaces = (Character, )
|
||||
```
|
||||
|
||||
## Mutations
|
||||
|
||||
Mutation fields have changed the way of usage, before if you have the mutation `MyMutation` you
|
||||
only have to reference with `graphene.Field(MyMutation)` now it's simply `MyMutation.Field()`
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
from graphene import ObjectType, Mutation, String
|
||||
|
||||
class ReverseString(Mutation):
|
||||
class Input:
|
||||
input = String(required=True)
|
||||
|
||||
reversed = String()
|
||||
|
||||
def mutate(root, args, context, info):
|
||||
reversed = args.get('input')[::-1]
|
||||
return ReverseString(reversed=reversed)
|
||||
|
||||
class Query(ObjectType):
|
||||
reverse_string = graphene.Field(ReverseString) # Old way, will not include the mutation arguments by default
|
||||
reverse_string = ReverseString.Field()
|
||||
```
|
||||
|
||||
## Nodes
|
||||
|
||||
Apart from implementing as shown in the previous section, to use the node field you have to
|
||||
specify the node Type.
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
from graphene import ObjectType, relay
|
||||
|
||||
class Query(ObjectType):
|
||||
node = relay.NodeField() # Old way, NodeField no longer exists. Use Node.Field
|
||||
node = relay.Node.Field() # New way
|
||||
```
|
||||
|
||||
Also, if you wanted to create an `ObjectType` that implements `Node`, you have to do it
|
||||
explicitly.
|
||||
|
||||
## Django
|
||||
|
||||
The Django integration with Graphene now has an independent package: `graphene-django`.
|
||||
For installing, you have to replace the old `graphene[django]` with `graphene-django`.
|
||||
|
||||
- As the package is now independent, you now have to import from `graphene_django`.
|
||||
- **DjangoNode no longer exists**, please use `relay.Node` instead:
|
||||
|
||||
```python
|
||||
from graphene.relay import Node
|
||||
from graphene_django import DjangoObjectType
|
||||
|
||||
class Droid(DjangoObjectType):
|
||||
class Meta:
|
||||
interfaces = (Node, )
|
||||
```
|
||||
|
||||
## SQLAlchemy
|
||||
|
||||
The SQLAlchemy integration with Graphene now has an independent package: `graphene-sqlalchemy`.
|
||||
For installing, you have to replace the old `graphene[sqlalchemy]` with `graphene-sqlalchemy`.
|
||||
|
||||
- As the package is now independent, you have to import now from `graphene_sqlalchemy`.
|
||||
- **SQLAlchemyNode no longer exists**, please use `relay.Node` instead:
|
||||
|
||||
```python
|
||||
from graphene.relay import Node
|
||||
from graphene_sqlalchemy import SQLAlchemyObjectType
|
||||
|
||||
class Droid(SQLAlchemyObjectType):
|
||||
class Meta:
|
||||
interfaces = (Node, )
|
||||
```
|
385
UPGRADE-v2.0.md
Normal file
385
UPGRADE-v2.0.md
Normal file
|
@ -0,0 +1,385 @@
|
|||
# v2.0 Upgrade Guide
|
||||
|
||||
`ObjectType`, `Interface`, `InputObjectType`, `Scalar` and `Enum` implementations
|
||||
have been quite simplified, without the need to define a explicit Metaclass for each subtype.
|
||||
|
||||
It also improves the field resolvers, [simplifying the code](#simpler-resolvers) the
|
||||
developer has to write to use them.
|
||||
|
||||
**Deprecations:**
|
||||
|
||||
- [`AbstractType`](#abstracttype-deprecated)
|
||||
- [`resolve_only_args`](#resolve_only_args)
|
||||
- [`Mutation.Input`](#mutationinput)
|
||||
|
||||
**Breaking changes:**
|
||||
|
||||
- [`Simpler Resolvers`](#simpler-resolvers)
|
||||
- [`Node Connections`](#node-connections)
|
||||
|
||||
**New Features!**
|
||||
|
||||
- [`InputObjectType`](#inputobjecttype)
|
||||
- [`Meta as Class arguments`](#meta-as-class-arguments) (_only available for Python 3_)
|
||||
|
||||
> The type metaclasses are now deleted as they are no longer necessary. If your code was depending
|
||||
> on this strategy for creating custom attrs, see an [example on how to do it in 2.0](https://github.com/graphql-python/graphene/blob/v2.0.0/graphene/tests/issues/test_425.py).
|
||||
|
||||
## Deprecations
|
||||
|
||||
### AbstractType deprecated
|
||||
|
||||
AbstractType is deprecated in graphene 2.0, you can now use normal inheritance instead.
|
||||
|
||||
Before:
|
||||
|
||||
```python
|
||||
class CommonFields(AbstractType):
|
||||
name = String()
|
||||
|
||||
class Pet(CommonFields, Interface):
|
||||
pass
|
||||
```
|
||||
|
||||
With 2.0:
|
||||
|
||||
```python
|
||||
class CommonFields(object):
|
||||
name = String()
|
||||
|
||||
class Pet(CommonFields, Interface):
|
||||
pass
|
||||
```
|
||||
|
||||
### resolve_only_args
|
||||
|
||||
`resolve_only_args` is now deprecated as the resolver API has been simplified.
|
||||
|
||||
Before:
|
||||
|
||||
```python
|
||||
class User(ObjectType):
|
||||
name = String()
|
||||
|
||||
@resolve_only_args
|
||||
def resolve_name(root):
|
||||
return root.name
|
||||
```
|
||||
|
||||
With 2.0:
|
||||
|
||||
```python
|
||||
class User(ObjectType):
|
||||
name = String()
|
||||
|
||||
def resolve_name(root, info):
|
||||
return root.name
|
||||
```
|
||||
|
||||
### Mutation.Input
|
||||
|
||||
`Mutation.Input` is now deprecated in favor of using `Mutation.Arguments` (`ClientIDMutation` still uses `Input`).
|
||||
|
||||
Before:
|
||||
|
||||
```python
|
||||
class User(Mutation):
|
||||
class Input:
|
||||
name = String()
|
||||
```
|
||||
|
||||
With 2.0:
|
||||
|
||||
```python
|
||||
class User(Mutation):
|
||||
class Arguments:
|
||||
name = String()
|
||||
```
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
### Simpler resolvers
|
||||
|
||||
All the resolvers in graphene have been simplified.
|
||||
Prior to Graphene `2.0`, all resolvers required four arguments: `(root, args, context, info)`.
|
||||
Now, resolver `args` are passed as keyword arguments to the function, and `context` argument dissapeared in favor of `info.context`.
|
||||
|
||||
Before:
|
||||
|
||||
```python
|
||||
my_field = graphene.String(my_arg=graphene.String())
|
||||
|
||||
def resolve_my_field(root, args, context, info):
|
||||
my_arg = args.get('my_arg')
|
||||
return ...
|
||||
```
|
||||
|
||||
With 2.0:
|
||||
|
||||
```python
|
||||
my_field = graphene.String(my_arg=graphene.String())
|
||||
|
||||
def resolve_my_field(root, info, my_arg):
|
||||
return ...
|
||||
```
|
||||
|
||||
**PS.: Take care with receiving args like `my_arg` as above. This doesn't work for optional (non-required) arguments as standard `Connection`'s arguments (first, last, after, before).**
|
||||
You may need something like this:
|
||||
|
||||
```python
|
||||
def resolve_my_field(root, info, known_field1, known_field2, **args): ## get other args with: args.get('arg_key')
|
||||
```
|
||||
|
||||
And, if you need the context in the resolver, you can use `info.context`:
|
||||
|
||||
```python
|
||||
my_field = graphene.String(my_arg=graphene.String())
|
||||
|
||||
def resolve_my_field(root, info, my_arg):
|
||||
context = info.context
|
||||
return ...
|
||||
```
|
||||
|
||||
### Node Connections
|
||||
|
||||
Node types no longer have a `Connection` by default.
|
||||
In 2.0 and onwards `Connection`s should be defined explicitly.
|
||||
|
||||
Before:
|
||||
|
||||
```python
|
||||
class User(ObjectType):
|
||||
class Meta:
|
||||
interfaces = [relay.Node]
|
||||
name = String()
|
||||
|
||||
class Query(ObjectType):
|
||||
user_connection = relay.ConnectionField(User)
|
||||
```
|
||||
|
||||
With 2.0:
|
||||
|
||||
```python
|
||||
class User(ObjectType):
|
||||
class Meta:
|
||||
interfaces = [relay.Node]
|
||||
name = String()
|
||||
|
||||
class UserConnection(relay.Connection):
|
||||
class Meta:
|
||||
node = User
|
||||
|
||||
class Query(ObjectType):
|
||||
user_connection = relay.ConnectionField(UserConnection)
|
||||
```
|
||||
|
||||
## Node.get_node
|
||||
|
||||
The method `get_node` in `ObjectTypes` that have `Node` as interface, changes its API.
|
||||
From `def get_node(cls, id, context, info)` to `def get_node(cls, info, id)`.
|
||||
|
||||
```python
|
||||
class MyObject(ObjectType):
|
||||
class Meta:
|
||||
interfaces = (Node, )
|
||||
|
||||
@classmethod
|
||||
def get_node(cls, id, context, info):
|
||||
return ...
|
||||
```
|
||||
|
||||
To:
|
||||
|
||||
```python
|
||||
class MyObject(ObjectType):
|
||||
class Meta:
|
||||
interfaces = (Node, )
|
||||
|
||||
@classmethod
|
||||
def get_node(cls, info, id):
|
||||
return ...
|
||||
```
|
||||
|
||||
## Node.get_node_from_global_id
|
||||
|
||||
The parameters' order of `get_node_from_global_id` method has changed. You may need to adjust your [Node Root Field](http://docs.graphene-python.org/en/latest/relay/nodes/#node-root-field) and maybe other places that uses this method to obtain an object.
|
||||
|
||||
Before:
|
||||
|
||||
```python
|
||||
class RootQuery(object):
|
||||
...
|
||||
node = Field(relay.Node, id=ID(required=True))
|
||||
|
||||
def resolve_node(root, args, context, info):
|
||||
node = relay.Node.get_node_from_global_id(args['id'], context, info)
|
||||
return node
|
||||
```
|
||||
|
||||
Now:
|
||||
|
||||
```python
|
||||
class RootQuery(object):
|
||||
...
|
||||
node = Field(relay.Node, id=ID(required=True))
|
||||
|
||||
def resolve_node(root, info, id):
|
||||
node = relay.Node.get_node_from_global_id(info, id)
|
||||
return node
|
||||
```
|
||||
|
||||
## Mutation.mutate
|
||||
|
||||
Now only receives (`root`, `info`, `**kwargs`) and is not a @classmethod
|
||||
|
||||
Before:
|
||||
|
||||
```python
|
||||
class SomeMutation(Mutation):
|
||||
...
|
||||
|
||||
@classmethod
|
||||
def mutate(cls, instance, args, context, info):
|
||||
...
|
||||
```
|
||||
|
||||
With 2.0:
|
||||
|
||||
```python
|
||||
class SomeMutation(Mutation):
|
||||
...
|
||||
|
||||
def mutate(root, info, **args):
|
||||
...
|
||||
```
|
||||
|
||||
With 2.0 you can also get your declared (as above) `args` this way:
|
||||
|
||||
```python
|
||||
class SomeMutation(Mutation):
|
||||
class Arguments:
|
||||
first_name = String(required=True)
|
||||
last_name = String(required=True)
|
||||
...
|
||||
|
||||
def mutate(root, info, first_name, last_name):
|
||||
...
|
||||
```
|
||||
|
||||
## ClientIDMutation.mutate_and_get_payload
|
||||
|
||||
Now only receives (`root`, `info`, `**input`)
|
||||
|
||||
### Middlewares
|
||||
|
||||
If you are using Middelwares, you need to some adjustments:
|
||||
|
||||
Before:
|
||||
|
||||
```python
|
||||
class MyGrapheneMiddleware(object):
|
||||
def resolve(self, next_mw, root, args, context, info):
|
||||
|
||||
## Middleware code
|
||||
|
||||
return next_mw(root, args, context, info)
|
||||
```
|
||||
|
||||
With 2.0:
|
||||
|
||||
```python
|
||||
class MyGrapheneMiddleware(object):
|
||||
def resolve(self, next_mw, root, info, **args):
|
||||
context = info.context
|
||||
|
||||
## Middleware code
|
||||
|
||||
info.context = context
|
||||
return next_mw(root, info, **args)
|
||||
```
|
||||
|
||||
## New Features
|
||||
|
||||
### InputObjectType
|
||||
|
||||
If you are using `InputObjectType`, you now can access
|
||||
its fields via `getattr` (`my_input.myattr`) when resolving, instead of
|
||||
the classic way `my_input['myattr']`.
|
||||
|
||||
And also use custom defined properties on your input class.
|
||||
|
||||
Example. Before:
|
||||
|
||||
```python
|
||||
class UserInput(InputObjectType):
|
||||
id = ID(required=True)
|
||||
|
||||
def is_valid_input(input):
|
||||
return input.get('id').startswith('userid_')
|
||||
|
||||
class Query(ObjectType):
|
||||
user = graphene.Field(User, input=UserInput())
|
||||
|
||||
@resolve_only_args
|
||||
def resolve_user(root, input):
|
||||
user_id = input.get('id')
|
||||
if is_valid_input(user_id):
|
||||
return get_user(user_id)
|
||||
```
|
||||
|
||||
With 2.0:
|
||||
|
||||
```python
|
||||
class UserInput(InputObjectType):
|
||||
id = ID(required=True)
|
||||
|
||||
@property
|
||||
def is_valid(root):
|
||||
return root.id.startswith('userid_')
|
||||
|
||||
class Query(ObjectType):
|
||||
user = graphene.Field(User, input=UserInput())
|
||||
|
||||
def resolve_user(root, info, input):
|
||||
if input.is_valid:
|
||||
return get_user(input.id)
|
||||
```
|
||||
|
||||
### Meta as Class arguments
|
||||
|
||||
Now you can use the meta options as class arguments (**ONLY PYTHON 3**).
|
||||
|
||||
Before:
|
||||
|
||||
```python
|
||||
class Dog(ObjectType):
|
||||
class Meta:
|
||||
interfaces = [Pet]
|
||||
name = String()
|
||||
```
|
||||
|
||||
With 2.0:
|
||||
|
||||
```python
|
||||
class Dog(ObjectType, interfaces=[Pet]):
|
||||
name = String()
|
||||
```
|
||||
|
||||
### Abstract types
|
||||
|
||||
Now you can create abstact types super easily, without the need of subclassing the meta.
|
||||
|
||||
```python
|
||||
class Base(ObjectType):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
id = ID()
|
||||
|
||||
def resolve_id(root, info):
|
||||
return f"{root.__class__.__name__}_{root.id}"
|
||||
```
|
||||
|
||||
### UUID Scalar
|
||||
|
||||
In Graphene 2.0 there is a new dedicated scalar for UUIDs, `UUID`.
|
|
@ -1,7 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Install the required scripts with
|
||||
# pip install autoflake autopep8 isort
|
||||
autoflake ./examples/ ./graphene/ -r --remove-unused-variables --remove-all-unused-imports --in-place
|
||||
autopep8 ./examples/ ./graphene/ -r --in-place --experimental --aggressive --max-line-length 120
|
||||
isort -rc ./examples/ ./graphene/
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"stage": 0
|
||||
}
|
29
docs/.gitignore
vendored
29
docs/.gitignore
vendored
|
@ -1,29 +0,0 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
node_modules
|
||||
|
||||
public/
|
208
docs/Makefile
Normal file
208
docs/Makefile
Normal file
|
@ -0,0 +1,208 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@grep -E '^\.PHONY: [a-zA-Z_-]+ .*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = "(: |##)"}; {printf "\033[36m%-30s\033[0m %s\n", $$2, $$3}'
|
||||
|
||||
.PHONY: install ## to install all documentation related requirements
|
||||
install:
|
||||
pip install -r requirements.txt
|
||||
|
||||
.PHONY: clean ## to remove all built documentation
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
|
||||
.PHONY: html ## to make standalone HTML files
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
.PHONY: dirhtml ## to make HTML files named index.html in directories
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
.PHONY: singlehtml ## to make a single large HTML file
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
.PHONY: pickle ## to make pickle files
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
.PHONY: json ## to make JSON files
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
.PHONY: htmlhelp ## to make HTML files and a HTML help project
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
.PHONY: qthelp ## to make HTML files and a qthelp project
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Graphene.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Graphene.qhc"
|
||||
|
||||
.PHONY: applehelp ## to make an Apple Help Book
|
||||
applehelp:
|
||||
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
|
||||
@echo
|
||||
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
|
||||
@echo "N.B. You won't be able to view it unless you put it in" \
|
||||
"~/Library/Documentation/Help or install it in your application" \
|
||||
"bundle."
|
||||
|
||||
.PHONY: devhelp ## to make HTML files and a Devhelp project
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/Graphene"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Graphene"
|
||||
@echo "# devhelp"
|
||||
|
||||
.PHONY: epub ## to make an epub
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
.PHONY: epub3 ## to make an epub3
|
||||
epub3:
|
||||
$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
|
||||
@echo
|
||||
@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
|
||||
|
||||
.PHONY: latex ## to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
.PHONY: latexpdf ## to make LaTeX files and run them through pdflatex
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
.PHONY: latexpdfja ## to make LaTeX files and run them through platex/dvipdfmx
|
||||
latexpdfja:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
.PHONY: text ## to make text files
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
.PHONY: man ## to make manual pages
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
.PHONY: texinfo ## to make Texinfo files
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
.PHONY: info ## to make Texinfo files and run them through makeinfo
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
.PHONY: gettext ## to make PO message catalogs
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
.PHONY: changes ## to make an overview of all changed/added/deprecated items
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
.PHONY: linkcheck ## to check all external links for integrity
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
.PHONY: doctest ## to run all doctests embedded in the documentation (if enabled)
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
.PHONY: coverage ## to run coverage check of the documentation (if enabled)
|
||||
coverage:
|
||||
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
|
||||
@echo "Testing of coverage in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/coverage/python.txt."
|
||||
|
||||
.PHONY: xml ## to make Docutils-native XML files
|
||||
xml:
|
||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||
@echo
|
||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||
|
||||
.PHONY: pseudoxml ## to make pseudoxml-XML files for display purposes
|
||||
pseudoxml:
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
||||
|
||||
.PHONY: dummy ## to check syntax errors of document sources
|
||||
dummy:
|
||||
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
|
||||
@echo
|
||||
@echo "Build finished. Dummy builder generates no files."
|
||||
|
||||
.PHONY: livehtml ## to build and serve live-reloading documentation
|
||||
livehtml:
|
||||
sphinx-autobuild -b html --watch ../graphene $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
|
@ -1,43 +0,0 @@
|
|||
# Graphene Docs
|
||||
|
||||
Graphene docs are powered by [gatsby](https://github.com/gatsbyjs/gatsby).
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
For running locally this docs. You have to execute
|
||||
```bash
|
||||
npm install -g gatsby && npm install
|
||||
```
|
||||
|
||||
And then
|
||||
|
||||
```bash
|
||||
gatsby develop
|
||||
```
|
||||
|
||||
## Playground
|
||||
|
||||
If you want to have the playground running too, just execute
|
||||
|
||||
```
|
||||
./playground/graphene-js/build.sh
|
||||
```
|
||||
|
||||
This command will clone the [pypyjs-release-nojit](https://github.com/pypyjs/pypyjs-release-nojit) repo, update it with the latest graphene, graphql-core and graphql-relay code, and make it available for the `/playground` view in the docs.
|
||||
|
||||
|
||||
## Build
|
||||
|
||||
For building the docs into the `public` dir, just run:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
|
||||
## Automation
|
||||
|
||||
Thanks to [Travis](https://github.com/graphql-python/graphene/blob/master/.travis.yml#L39-L58), we automated the way documentation is updated in the `gh-pages` branch.
|
||||
|
||||
Each time we modify the docs in the `master` branch the travis job runs and updates the `gh-pages` branch with the latest code, so [Graphene's website](http://graphene-python.org) have always the latest docs.
|
110
docs/api/index.rst
Normal file
110
docs/api/index.rst
Normal file
|
@ -0,0 +1,110 @@
|
|||
API Reference
|
||||
=============
|
||||
|
||||
Schema
|
||||
------
|
||||
|
||||
.. autoclass:: graphene.types.schema.Schema
|
||||
:members:
|
||||
|
||||
.. Uncomment sections / types as API documentation is fleshed out
|
||||
.. in each class
|
||||
|
||||
Object types
|
||||
------------
|
||||
|
||||
.. autoclass:: graphene.ObjectType
|
||||
|
||||
.. autoclass:: graphene.InputObjectType
|
||||
|
||||
.. autoclass:: graphene.Mutation
|
||||
:members:
|
||||
|
||||
.. _fields-mounted-types:
|
||||
|
||||
Fields (Mounted Types)
|
||||
----------------------
|
||||
|
||||
.. autoclass:: graphene.Field
|
||||
|
||||
.. autoclass:: graphene.Argument
|
||||
|
||||
.. autoclass:: graphene.InputField
|
||||
|
||||
Fields (Unmounted Types)
|
||||
------------------------
|
||||
|
||||
.. autoclass:: graphene.types.unmountedtype.UnmountedType
|
||||
|
||||
GraphQL Scalars
|
||||
---------------
|
||||
|
||||
.. autoclass:: graphene.Int()
|
||||
|
||||
.. autoclass:: graphene.Float()
|
||||
|
||||
.. autoclass:: graphene.String()
|
||||
|
||||
.. autoclass:: graphene.Boolean()
|
||||
|
||||
.. autoclass:: graphene.ID()
|
||||
|
||||
Graphene Scalars
|
||||
----------------
|
||||
|
||||
.. autoclass:: graphene.Date()
|
||||
|
||||
.. autoclass:: graphene.DateTime()
|
||||
|
||||
.. autoclass:: graphene.Time()
|
||||
|
||||
.. autoclass:: graphene.Decimal()
|
||||
|
||||
.. autoclass:: graphene.UUID()
|
||||
|
||||
.. autoclass:: graphene.JSONString()
|
||||
|
||||
.. autoclass:: graphene.Base64()
|
||||
|
||||
Enum
|
||||
----
|
||||
|
||||
.. autoclass:: graphene.Enum()
|
||||
|
||||
Structures
|
||||
----------
|
||||
|
||||
.. autoclass:: graphene.List
|
||||
|
||||
.. autoclass:: graphene.NonNull
|
||||
|
||||
Type Extension
|
||||
--------------
|
||||
|
||||
.. autoclass:: graphene.Interface()
|
||||
|
||||
.. autoclass:: graphene.Union()
|
||||
|
||||
Execution Metadata
|
||||
------------------
|
||||
|
||||
.. autoclass:: graphene.ResolveInfo
|
||||
|
||||
.. autoclass:: graphene.Context
|
||||
|
||||
.. autoclass:: graphql.ExecutionResult
|
||||
|
||||
.. Relay
|
||||
.. -----
|
||||
|
||||
.. .. autoclass:: graphene.Node
|
||||
|
||||
.. .. autoclass:: graphene.GlobalID
|
||||
|
||||
.. .. autoclass:: graphene.ClientIDMutation
|
||||
|
||||
.. .. autoclass:: graphene.Connection
|
||||
|
||||
.. .. autoclass:: graphene.ConnectionField
|
||||
|
||||
.. .. autoclass:: graphene.PageInfo
|
19
docs/app.js
19
docs/app.js
|
@ -1,19 +0,0 @@
|
|||
exports.loadContext = function(callback) {
|
||||
var context;
|
||||
context = require.context('./pages', true);
|
||||
if (module.hot) {
|
||||
module.hot.accept(context.id, function() {
|
||||
context = require.context('./pages', true);
|
||||
return callback(context);
|
||||
});
|
||||
}
|
||||
return callback(context);
|
||||
};
|
||||
|
||||
exports.onRouteChange = function(state) {
|
||||
if (typeof window !== "undefined" && window.ga) {
|
||||
window.ga('send', 'pageview', {
|
||||
page: state.pathname
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
export default class Icon extends React.Component {
|
||||
render() {
|
||||
return <span {...this.props} src={null} dangerouslySetInnerHTML={{__html:this.props.src}} />
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
<svg width="100%" height="100%" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" class="logo"><g fill="none" fill-rule="evenodd"><path class="logo-path" d="M49.17 10.915L15.415 30.29l.058 39.252 34.633 20.082 33.345-18.95-.023-39.79-34.185 19.238" stroke="white" stroke-width="4.987"/>
|
||||
<g transform="translate(41.5 3)"><ellipse cx="7.934" cy="7.97" rx="7.934" ry="7.97" fill="white"/></g>
|
||||
<g transform="translate(8 22.482)"><ellipse cx="7.934" cy="7.97" rx="7.934" ry="7.97" fill="white"/></g>
|
||||
<g transform="translate(8 62.33)"><ellipse cx="7.934" cy="7.97" rx="7.934" ry="7.97" fill="white"/></g>
|
||||
<g transform="translate(42.382 81.813)"><ellipse cx="7.934" cy="7.97" rx="7.934" ry="7.97" fill="white"/></g>
|
||||
<g transform="translate(75.882 62.33)"><ellipse cx="7.934" cy="7.97" rx="7.934" ry="7.97" fill="white"/></g>
|
||||
<g transform="translate(75.882 22.482)"><ellipse cx="7.934" cy="7.97" rx="7.934" ry="7.97" fill="white"/></g>
|
||||
<g transform="translate(42.382 41.964)"><ellipse cx="7.934" cy="7.97" rx="7.934" ry="7.97" fill="white"/></g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
458
docs/conf.py
Normal file
458
docs/conf.py
Normal file
|
@ -0,0 +1,458 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
import sphinx_graphene_theme
|
||||
|
||||
on_rtd = os.environ.get("READTHEDOCS", None) == "True"
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Graphene documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sun Sep 11 18:30:51 2016.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
|
||||
sys.path.insert(0, os.path.abspath(".."))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.intersphinx",
|
||||
"sphinx.ext.todo",
|
||||
"sphinx.ext.coverage",
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx.ext.napoleon",
|
||||
]
|
||||
if not on_rtd:
|
||||
extensions += ["sphinx.ext.githubpages"]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = ".rst"
|
||||
|
||||
# The encoding of source files.
|
||||
#
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = "index"
|
||||
|
||||
# General information about the project.
|
||||
project = "Graphene"
|
||||
copyright = "Graphene 2016"
|
||||
author = "Syrus Akbary"
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = "1.0"
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = "1.0"
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
# language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#
|
||||
# today = ''
|
||||
#
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#
|
||||
# today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This patterns also effect to html_static_path and html_extra_path
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#
|
||||
# default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#
|
||||
# add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#
|
||||
# add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#
|
||||
# show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = "sphinx"
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
# modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
# keep_warnings = False
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = True
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
# html_theme = 'alabaster'
|
||||
# if on_rtd:
|
||||
# html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
html_theme = "sphinx_graphene_theme"
|
||||
|
||||
html_theme_path = [sphinx_graphene_theme.get_html_theme_path()]
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
# html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents.
|
||||
# "<project> v<release> documentation" by default.
|
||||
#
|
||||
# html_title = u'Graphene v1.0'
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#
|
||||
# html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#
|
||||
# html_logo = None
|
||||
|
||||
# The name of an image file (relative to this directory) to use as a favicon of
|
||||
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#
|
||||
# html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ["_static"]
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#
|
||||
# html_extra_path = []
|
||||
|
||||
# If not None, a 'Last updated on:' timestamp is inserted at every page
|
||||
# bottom, using the given strftime format.
|
||||
# The empty string is equivalent to '%b %d, %Y'.
|
||||
#
|
||||
# html_last_updated_fmt = None
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#
|
||||
# html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#
|
||||
# html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#
|
||||
# html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#
|
||||
# html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#
|
||||
# html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#
|
||||
# html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#
|
||||
# html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#
|
||||
# html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#
|
||||
# html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#
|
||||
# html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
# html_file_suffix = None
|
||||
|
||||
# Language to be used for generating the HTML full-text search index.
|
||||
# Sphinx supports the following languages:
|
||||
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
||||
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'
|
||||
#
|
||||
# html_search_language = 'en'
|
||||
|
||||
# A dictionary with options for the search language support, empty by default.
|
||||
# 'ja' uses this config value.
|
||||
# 'zh' user can custom change `jieba` dictionary path.
|
||||
#
|
||||
# html_search_options = {'type': 'default'}
|
||||
|
||||
# The name of a javascript file (relative to the configuration directory) that
|
||||
# implements a search results scorer. If empty, the default will be used.
|
||||
#
|
||||
# html_search_scorer = 'scorer.js'
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = "Graphenedoc"
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, "Graphene.tex", "Graphene Documentation", "Syrus Akbary", "manual")
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#
|
||||
# latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#
|
||||
# latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#
|
||||
# latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#
|
||||
# latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#
|
||||
# latex_appendices = []
|
||||
|
||||
# It false, will not define \strong, \code, itleref, \crossref ... but only
|
||||
# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added
|
||||
# packages.
|
||||
#
|
||||
# latex_keep_old_macro_names = True
|
||||
|
||||
# If false, no module index is generated.
|
||||
#
|
||||
# latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [(master_doc, "graphene", "Graphene Documentation", [author], 1)]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#
|
||||
# man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(
|
||||
master_doc,
|
||||
"Graphene",
|
||||
"Graphene Documentation",
|
||||
author,
|
||||
"Graphene",
|
||||
"One line description of project.",
|
||||
"Miscellaneous",
|
||||
)
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#
|
||||
# texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#
|
||||
# texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#
|
||||
# texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#
|
||||
# texinfo_no_detailmenu = False
|
||||
|
||||
|
||||
# -- Options for Epub output ----------------------------------------------
|
||||
|
||||
# Bibliographic Dublin Core info.
|
||||
epub_title = project
|
||||
epub_author = author
|
||||
epub_publisher = author
|
||||
epub_copyright = copyright
|
||||
|
||||
# The basename for the epub file. It defaults to the project name.
|
||||
# epub_basename = project
|
||||
|
||||
# The HTML theme for the epub output. Since the default themes are not
|
||||
# optimized for small screen space, using the same theme for HTML and epub
|
||||
# output is usually not wise. This defaults to 'epub', a theme designed to save
|
||||
# visual space.
|
||||
#
|
||||
# epub_theme = 'epub'
|
||||
|
||||
# The language of the text. It defaults to the language option
|
||||
# or 'en' if the language is not set.
|
||||
#
|
||||
# epub_language = ''
|
||||
|
||||
# The scheme of the identifier. Typical schemes are ISBN or URL.
|
||||
# epub_scheme = ''
|
||||
|
||||
# The unique identifier of the text. This can be a ISBN number
|
||||
# or the project homepage.
|
||||
#
|
||||
# epub_identifier = ''
|
||||
|
||||
# A unique identification for the text.
|
||||
#
|
||||
# epub_uid = ''
|
||||
|
||||
# A tuple containing the cover image and cover page html template filenames.
|
||||
#
|
||||
# epub_cover = ()
|
||||
|
||||
# A sequence of (type, uri, title) tuples for the guide element of content.opf.
|
||||
#
|
||||
# epub_guide = ()
|
||||
|
||||
# HTML files that should be inserted before the pages created by sphinx.
|
||||
# The format is a list of tuples containing the path and title.
|
||||
#
|
||||
# epub_pre_files = []
|
||||
|
||||
# HTML files that should be inserted after the pages created by sphinx.
|
||||
# The format is a list of tuples containing the path and title.
|
||||
#
|
||||
# epub_post_files = []
|
||||
|
||||
# A list of files that should not be packed into the epub file.
|
||||
epub_exclude_files = ["search.html"]
|
||||
|
||||
# The depth of the table of contents in toc.ncx.
|
||||
#
|
||||
# epub_tocdepth = 3
|
||||
|
||||
# Allow duplicate toc entries.
|
||||
#
|
||||
# epub_tocdup = True
|
||||
|
||||
# Choose between 'default' and 'includehidden'.
|
||||
#
|
||||
# epub_tocscope = 'default'
|
||||
|
||||
# Fix unsupported image types using the Pillow.
|
||||
#
|
||||
# epub_fix_images = False
|
||||
|
||||
# Scale large images.
|
||||
#
|
||||
# epub_max_image_width = 0
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#
|
||||
# epub_show_urls = 'inline'
|
||||
|
||||
# If false, no index is generated.
|
||||
#
|
||||
# epub_use_index = True
|
||||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
"https://docs.python.org/": None,
|
||||
"python": ("https://docs.python.org/", None),
|
||||
"graphene_django": (
|
||||
"http://docs.graphene-python.org/projects/django/en/latest/",
|
||||
None,
|
||||
),
|
||||
"graphene_sqlalchemy": (
|
||||
"http://docs.graphene-python.org/projects/sqlalchemy/en/latest/",
|
||||
None,
|
||||
),
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
siteTitle = "Graphene"
|
||||
ga = "UA-12613282-7"
|
||||
|
||||
[docs.quickstart]
|
||||
name = "Quickstart"
|
||||
pages = [
|
||||
"/docs/quickstart/",
|
||||
]
|
||||
|
||||
[docs.walkthrough]
|
||||
name = "Walkthrough"
|
||||
pages = [
|
||||
"/docs/interfaces/",
|
||||
"/docs/objecttypes/",
|
||||
"/docs/resolvers/",
|
||||
"/docs/mutations/",
|
||||
"/docs/basic-types/",
|
||||
"/docs/enums/",
|
||||
"/docs/relay/",
|
||||
"/docs/middleware/",
|
||||
]
|
||||
|
||||
[docs.django]
|
||||
name = "Django"
|
||||
pages = [
|
||||
"/docs/django/tutorial/",
|
||||
"/docs/django/filtering/",
|
||||
"/docs/django/authorization/",
|
||||
"/docs/django/introspection-schema/",
|
||||
"/docs/django/debug/",
|
||||
]
|
||||
|
||||
[docs.sqlalchemy]
|
||||
name = "SQLAlchemy"
|
||||
pages = [
|
||||
"/docs/sqlalchemy/tutorial/",
|
||||
"/docs/sqlalchemy/tips/",
|
||||
]
|
|
@ -1,41 +0,0 @@
|
|||
/* Position and sizing of burger button */
|
||||
.bm-burger-button {
|
||||
position: absolute;
|
||||
width: 24px;
|
||||
height: 20px;
|
||||
right: 36px;
|
||||
top: 42px;
|
||||
}
|
||||
|
||||
/* Color/shape of burger icon bars */
|
||||
.bm-burger-bars {
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* Color of close button cross */
|
||||
.bm-cross {
|
||||
background: #bdc3c7;
|
||||
margin-top: -1px;
|
||||
width: 1px!important;
|
||||
height: 18px!important;
|
||||
}
|
||||
|
||||
/* Background color of sidebar */
|
||||
.bm-menu {
|
||||
background: #3c3c3c;
|
||||
box-shadow: -1px 0 5px rgba(0,0,0,.15);
|
||||
}
|
||||
|
||||
/* Morph shape necessary with bubble or elastic */
|
||||
.bm-morph-shape {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
.bm-menu-wrap {
|
||||
z-index: 10000!important;
|
||||
}
|
||||
/* General menu styles */
|
||||
.bm-menu {
|
||||
padding: 2.5em 1.5em 0;
|
||||
font-size: 1.15em;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,70 +0,0 @@
|
|||
/**
|
||||
* GitHub Gist Theme
|
||||
* Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro
|
||||
*/
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
background: white;
|
||||
padding: 0.5em;
|
||||
color: #333333;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-meta {
|
||||
color: #969896;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-strong,
|
||||
.hljs-emphasis,
|
||||
.hljs-quote {
|
||||
color: #df5000;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-type {
|
||||
color: #a71d5d;
|
||||
}
|
||||
|
||||
.hljs-literal,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-attribute {
|
||||
color: #0086b3;
|
||||
}
|
||||
|
||||
.hljs-section,
|
||||
.hljs-name {
|
||||
color: #63a35c;
|
||||
}
|
||||
|
||||
.hljs-tag {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-attr,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-attr {
|
||||
color: #795da3;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: #55a532;
|
||||
background-color: #eaffea;
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
color: #bd2c00;
|
||||
background-color: #ffecec;
|
||||
}
|
||||
|
||||
.hljs-link {
|
||||
text-decoration: underline;
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 372 B |
Binary file not shown.
Before Width: | Height: | Size: 688 B |
Binary file not shown.
Before Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.8 KiB |
|
@ -1,426 +0,0 @@
|
|||
@import 'nib'
|
||||
@import 'jeet'
|
||||
|
||||
@import 'https://fonts.googleapis.com/css?family=Raleway:400,500,600,200,100&.css'
|
||||
|
||||
normalize-css()
|
||||
|
||||
@import 'hljs.css'
|
||||
@import 'bm.css'
|
||||
|
||||
$wrapper
|
||||
center(960px, pad:20px)
|
||||
|
||||
.wrapper
|
||||
@extend $wrapper
|
||||
position relative
|
||||
|
||||
a, a:hover
|
||||
text-decoration none
|
||||
|
||||
a
|
||||
color rgb(42,93,173)
|
||||
|
||||
p
|
||||
margin-bottom 1em
|
||||
|
||||
html, body
|
||||
font-family "Helvetica Neue", Helvetica, Arial, sans-serif
|
||||
font-weight 300
|
||||
font-size 16px
|
||||
color #606060
|
||||
line-height 1.5
|
||||
height 100%
|
||||
margin 0
|
||||
width 100%
|
||||
|
||||
.header
|
||||
clearfix()
|
||||
position relative
|
||||
text-align center
|
||||
background: #DB594C;
|
||||
background-image: radial-gradient(95% 101%, #E46643 5%, rgba(226,91,72,0.00) 100%)
|
||||
.logo
|
||||
width 42px
|
||||
height @width
|
||||
vertical-align middle
|
||||
|
||||
h1
|
||||
max-width 380px
|
||||
font-family 'Raleway', sans-serif
|
||||
font-weight 200
|
||||
font-size 42px
|
||||
color #FFFFFF
|
||||
line-height 49px
|
||||
margin 80px auto 40px
|
||||
z-index 110
|
||||
|
||||
.get-started
|
||||
font-family 'Raleway'
|
||||
display inline-block
|
||||
margin 0 auto
|
||||
font-size 13px
|
||||
color #FFFFFF
|
||||
padding 0 18px
|
||||
text-transform uppercase
|
||||
font-weight 600
|
||||
line-height 15px
|
||||
border 1px solid #FFFFFF
|
||||
border-radius 2px
|
||||
padding 12px 18px
|
||||
z-index 111
|
||||
position relative
|
||||
&:hover
|
||||
background white
|
||||
color #E05B49
|
||||
text-decoration none
|
||||
|
||||
.header-wrapper
|
||||
@extend $wrapper
|
||||
text-align left
|
||||
padding-top 32px
|
||||
padding-bottom 32px
|
||||
position relative
|
||||
z-index 100
|
||||
|
||||
.header-extended
|
||||
padding-bottom 100px
|
||||
|
||||
.header-nav
|
||||
margin-top 8px
|
||||
a
|
||||
font-family 'Raleway'
|
||||
font-size 13px
|
||||
color #FFFFFF
|
||||
margin 0 16px
|
||||
padding 0 2px
|
||||
text-transform uppercase
|
||||
font-weight 600
|
||||
line-height 15px
|
||||
position relative
|
||||
&.active:before
|
||||
content: ''
|
||||
width 5px
|
||||
height 5px
|
||||
border-radius 3px
|
||||
display block
|
||||
position absolute
|
||||
background white
|
||||
left 50%
|
||||
margin-left -3px
|
||||
bottom -24px
|
||||
|
||||
+below(600px)
|
||||
display none
|
||||
|
||||
.bm-burger-button, .bm-menu-wrap, .bm-overlay
|
||||
display none
|
||||
+below(600px)
|
||||
display block
|
||||
|
||||
.bm-burger-button
|
||||
z-index 300!important
|
||||
|
||||
.bm-overlay
|
||||
z-index 1000!important
|
||||
|
||||
.bm-item-list
|
||||
a
|
||||
font-family 'Raleway'
|
||||
display block
|
||||
font-size 15px
|
||||
color #CCC
|
||||
margin 6px 0
|
||||
padding 10px 6px
|
||||
text-transform uppercase
|
||||
font-weight 500
|
||||
line-height 20px
|
||||
position relative
|
||||
&:hover
|
||||
color white
|
||||
|
||||
.header-logo
|
||||
font-family 'Raleway'
|
||||
font-size 22px
|
||||
color #FFFFFF
|
||||
float left
|
||||
font-weight 500
|
||||
text-transform uppercase
|
||||
text-decoration none
|
||||
|
||||
.header-nav
|
||||
float right
|
||||
|
||||
.logo
|
||||
path
|
||||
stroke-dasharray 250
|
||||
stroke-dashoffset 250
|
||||
animation logo-dash .9s ease-in-out forwards
|
||||
animation-delay .12s
|
||||
g
|
||||
ellipse
|
||||
animation logo-dot .3s ease forwards
|
||||
animation-fill-mode both
|
||||
transform-origin 50% 50%
|
||||
&:nth-child(2)
|
||||
ellipse
|
||||
animation-delay .1s
|
||||
&:nth-child(3)
|
||||
ellipse
|
||||
animation-delay .2s
|
||||
&:nth-child(4)
|
||||
ellipse
|
||||
animation-delay .3s
|
||||
&:nth-child(5)
|
||||
ellipse
|
||||
animation-delay .4s
|
||||
&:nth-child(6)
|
||||
ellipse
|
||||
animation-delay .5s
|
||||
&:nth-child(7)
|
||||
ellipse
|
||||
animation-delay .6s
|
||||
&:nth-child(8)
|
||||
ellipse
|
||||
animation-delay .7s
|
||||
|
||||
@keyframes logo-dash
|
||||
to
|
||||
stroke-dashoffset 0
|
||||
|
||||
@keyframes logo-dot
|
||||
from
|
||||
opacity 0.5
|
||||
transform scale(0)
|
||||
to
|
||||
opacity 1
|
||||
transform scale(1)
|
||||
|
||||
#header-background
|
||||
z-index 0
|
||||
display block
|
||||
position absolute
|
||||
width 100%
|
||||
top 0
|
||||
bottom 0
|
||||
right 0
|
||||
left 0
|
||||
|
||||
.particles-js-canvas-el
|
||||
display block
|
||||
opacity 0
|
||||
position absolute
|
||||
|
||||
.starwars-example-wrapper
|
||||
+below(600px)
|
||||
margin-bottom 30px
|
||||
|
||||
.starwars-example
|
||||
background: #3C3C3C
|
||||
display inline-block
|
||||
position absolute
|
||||
right 20px
|
||||
top -100px
|
||||
box-shadow: 0px 2px 5px 0px rgba(0,0,0,0.25);
|
||||
border-radius: 100px
|
||||
font-size 13px
|
||||
padding 17px 17px 17px 71px
|
||||
width 236px
|
||||
box-sizing border-box
|
||||
color white
|
||||
font-family 'Raleway'
|
||||
font-weight 500
|
||||
transition all .2s ease-in-out
|
||||
&:before
|
||||
content: ''
|
||||
display block
|
||||
position absolute
|
||||
left 20px
|
||||
top 20px
|
||||
height 32px
|
||||
width 32px
|
||||
image './images/starwars-icon.png' 32px 32px
|
||||
|
||||
&:hover
|
||||
transform translateY(-3px)
|
||||
box-shadow 0px 4px 8px 0px rgba(0,0,0,0.32)
|
||||
|
||||
+below(600px)
|
||||
top -70px
|
||||
|
||||
.improve-document-link
|
||||
position fixed
|
||||
right 0
|
||||
bottom 70px
|
||||
transform-origin 100% 100%
|
||||
background: #999;
|
||||
border: 1px solid #919191;
|
||||
border-radius: 3px 3px 0 0;
|
||||
border-bottom 0
|
||||
padding 9px 12px 12px 34px
|
||||
transform: rotate(270deg) translateX(100%) translateY(3px);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
text-transform uppercase
|
||||
color: #FFFFFF;
|
||||
letter-spacing: 0.3px;
|
||||
line-height: 11px;
|
||||
transition all .2s ease-in-out
|
||||
&:before
|
||||
content: ''
|
||||
display block
|
||||
position absolute
|
||||
left 10px
|
||||
top 8px
|
||||
height 16px
|
||||
width 16px
|
||||
image './images/edit.png' 16px 16px
|
||||
&:hover
|
||||
transform: rotate(270deg) translateX(100%)
|
||||
background #666
|
||||
border-color #555
|
||||
+below(600px)
|
||||
display none
|
||||
|
||||
$title
|
||||
display block
|
||||
font-family: 'Raleway';
|
||||
font-weight 500
|
||||
line-height 1.2em
|
||||
padding-top .3em
|
||||
margin-bottom .5em
|
||||
padding-bottom .5em
|
||||
color #4A4A4A
|
||||
|
||||
.markdown
|
||||
.wrapper
|
||||
margin-top 60px
|
||||
+below(600px)
|
||||
margin-top 30px
|
||||
|
||||
h1, h2, h3, h4, h5, h6
|
||||
@extend $title
|
||||
|
||||
h1
|
||||
font-size 32px
|
||||
h2
|
||||
font-size 26px
|
||||
h3
|
||||
font-size 24px
|
||||
h4
|
||||
font-size 21px
|
||||
h5
|
||||
font-size 18px
|
||||
h6
|
||||
font-size 16px
|
||||
strong
|
||||
font-weight 500
|
||||
pre
|
||||
line-height 20px
|
||||
background #FAFAFA
|
||||
padding 20px
|
||||
white-space: pre
|
||||
display: block;
|
||||
color: #333333;
|
||||
overflow-x: auto;
|
||||
p code, ul code
|
||||
background #FAFAFA
|
||||
padding 2px 4px
|
||||
border-radius 2px
|
||||
border 1px solid #CCC
|
||||
color #000
|
||||
p + p, p + ul
|
||||
margin-top -.4em
|
||||
p + ul
|
||||
margin-top -.6em
|
||||
code
|
||||
font-size 14px
|
||||
line-height 20px
|
||||
overflow-x: auto;
|
||||
margin-bottom 40px
|
||||
|
||||
.markdown h1:first-child
|
||||
margin-top 0
|
||||
padding-top 0
|
||||
|
||||
.page-title
|
||||
background: #F9F9F9;
|
||||
padding 48px 0
|
||||
|
||||
h1
|
||||
margin 0 auto
|
||||
@extend $wrapper
|
||||
font-family: 'Raleway';
|
||||
font-size: 40px;
|
||||
font-weight 200
|
||||
color: #585858;
|
||||
line-height: 50px;
|
||||
|
||||
+below(600px)
|
||||
padding 30px 0
|
||||
|
||||
.docs
|
||||
@extend $wrapper
|
||||
|
||||
.docs-aside
|
||||
col(1/4)
|
||||
margin-top 60px
|
||||
+below(600px)
|
||||
padding 20px
|
||||
width 100%
|
||||
box-sizing content-box
|
||||
margin 0 -20px
|
||||
margin-bottom 30px
|
||||
background #F9F9F9
|
||||
|
||||
.docs-aside-group
|
||||
display block
|
||||
margin-bottom 40px
|
||||
h3
|
||||
font-family: 'Raleway';
|
||||
font-weight 500
|
||||
font-size 12px
|
||||
text-transform uppercase
|
||||
line-height 1.2em
|
||||
margin-bottom 1em
|
||||
color #AAA
|
||||
a
|
||||
display block
|
||||
font-size 15px
|
||||
font-weight 400
|
||||
line-height 22px
|
||||
height 28px
|
||||
padding 3px 0
|
||||
color #4A4A4A
|
||||
&.active
|
||||
font-weight 500
|
||||
line-height 21px
|
||||
color #E05B49
|
||||
|
||||
+below(600px)
|
||||
display none
|
||||
|
||||
.docs-aside-navselect
|
||||
margin-top -18px
|
||||
display none
|
||||
width 100%
|
||||
+below(600px)
|
||||
display block
|
||||
|
||||
.docs-content
|
||||
col(3/4)
|
||||
margin-top 60px
|
||||
margin-bottom 20px
|
||||
+below(600px)
|
||||
margin-top 10px
|
||||
col(1)
|
||||
|
||||
>h1
|
||||
margin 0
|
||||
@extend $title
|
||||
font-size 32px
|
||||
|
||||
.docs-next
|
||||
float right
|
||||
color #e05b49
|
||||
font-weight 400
|
|
@ -1,148 +0,0 @@
|
|||
@import 'nib'
|
||||
@import 'graphiql.css'
|
||||
|
||||
.playground
|
||||
position absolute
|
||||
top 106px
|
||||
left 0
|
||||
right 0
|
||||
bottom 0
|
||||
display flex
|
||||
flex-direction row
|
||||
min-width 960px
|
||||
.loading
|
||||
position absolute
|
||||
display block
|
||||
left 0
|
||||
right 0
|
||||
bottom 0
|
||||
top 0
|
||||
z-index 10000
|
||||
background rgba(255,255,255,.6)
|
||||
|
||||
.playground-schema
|
||||
min-width 400px
|
||||
width 36%
|
||||
border-right 1px solid #E0E0E0
|
||||
// box-shadow 0 0 8px rgba(0, 0, 0, 0.15)
|
||||
position relative
|
||||
z-index 100
|
||||
display flex
|
||||
flex-direction: column
|
||||
|
||||
.cm-def
|
||||
.cm-variable + .cm-keyword // lambda
|
||||
&:not(.CodeMirror-lint-mark-error)
|
||||
transition all .3s ease-in-out
|
||||
background transparent
|
||||
|
||||
.activeline
|
||||
.cm-def
|
||||
.cm-variable + .cm-keyword // lambda
|
||||
$color = #D7D3F1
|
||||
// $color = rgba(219, 89, 76, .2)
|
||||
background $color
|
||||
border-radius 1px
|
||||
box-shadow 0 0 0 2px $color
|
||||
|
||||
.playground-schema-editor
|
||||
flex 1
|
||||
position relative
|
||||
.CodeMirror
|
||||
font-size: 13px;
|
||||
position absolute
|
||||
height 100%
|
||||
width 100%
|
||||
top 0
|
||||
left 0
|
||||
right 0
|
||||
bottom 0
|
||||
font-family: 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace;
|
||||
color: #141823;
|
||||
.CodeMirror-lines
|
||||
padding 20px 0
|
||||
|
||||
.playground-schema-header
|
||||
// height 48px
|
||||
// font-family 'Raleway', sans-serif
|
||||
// font-weight 300
|
||||
// line-height 48px
|
||||
// padding 0 10px
|
||||
// border-bottom solid 1px #d0d0d0
|
||||
height: 48px;
|
||||
box-sizing border-box
|
||||
font-family: 'Raleway', sans-serif;
|
||||
color: #999;
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
line-height: 52px;
|
||||
padding: 0 12px;
|
||||
border-bottom: solid 1px #d0d0d0;
|
||||
background: #F9F9F9;
|
||||
|
||||
.playground-graphiql
|
||||
flex 1
|
||||
height 100%
|
||||
|
||||
|
||||
.cm-s-graphene
|
||||
|
||||
/* Comment */
|
||||
.cm-s-graphene .cm-comment
|
||||
color: #999;
|
||||
|
||||
/* Punctuation */
|
||||
.cm-s-graphene .cm-punctuation
|
||||
color: #555;
|
||||
|
||||
/* Keyword */
|
||||
.cm-s-graphene .cm-keyword
|
||||
// color: #B11A04;
|
||||
// color #D2054E
|
||||
color #a71d5d
|
||||
|
||||
/* OperationName, FragmentName */
|
||||
.cm-s-graphene .cm-def
|
||||
// color: #D2054E;
|
||||
color: #1F61A0;
|
||||
|
||||
/* FieldName */
|
||||
.cm-s-graphene .cm-property
|
||||
color: #333;
|
||||
|
||||
/* FieldAlias */
|
||||
.cm-s-graphene .cm-qualifier
|
||||
color: #1C92A9;
|
||||
|
||||
/* ArgumentName and ObjectFieldName */
|
||||
.cm-s-graphene .cm-attribute
|
||||
color: #8B2BB9;
|
||||
|
||||
/* Number */
|
||||
.cm-s-graphene .cm-number
|
||||
color: #2882F9;
|
||||
|
||||
/* String */
|
||||
.cm-s-graphene .cm-string
|
||||
color: #D64292;
|
||||
|
||||
/* Boolean */
|
||||
.cm-s-graphene .cm-builtin
|
||||
color: #D47509;
|
||||
|
||||
/* EnumValue */
|
||||
.cm-s-graphene .cm-string-2
|
||||
color: #0B7FC7;
|
||||
|
||||
/* Variable */
|
||||
.cm-s-graphene .cm-variable
|
||||
color: #333;
|
||||
|
||||
/* Directive */
|
||||
.cm-s-graphene .cm-meta
|
||||
color: #B33086;
|
||||
|
||||
/* Type */
|
||||
.cm-s-graphene .cm-atom
|
||||
color: #CA9800;
|
117
docs/execution/dataloader.rst
Normal file
117
docs/execution/dataloader.rst
Normal file
|
@ -0,0 +1,117 @@
|
|||
Dataloader
|
||||
==========
|
||||
|
||||
DataLoader is a generic utility to be used as part of your application's
|
||||
data fetching layer to provide a simplified and consistent API over
|
||||
various remote data sources such as databases or web services via batching
|
||||
and caching. It is provided by a separate package `aiodataloader <https://pypi.org/project/aiodataloader/>`.
|
||||
|
||||
|
||||
Batching
|
||||
--------
|
||||
|
||||
Batching is not an advanced feature, it's DataLoader's primary feature.
|
||||
Create loaders by providing a batch loading function.
|
||||
|
||||
.. code:: python
|
||||
|
||||
from aiodataloader import DataLoader
|
||||
|
||||
class UserLoader(DataLoader):
|
||||
async def batch_load_fn(self, keys):
|
||||
# Here we call a function to return a user for each key in keys
|
||||
return [get_user(id=key) for key in keys]
|
||||
|
||||
|
||||
A batch loading async function accepts a list of keys, and returns a list of ``values``.
|
||||
|
||||
|
||||
``DataLoader`` will coalesce all individual loads which occur within a
|
||||
single frame of execution (executed once the wrapping event loop is resolved)
|
||||
and then call your batch function with all requested keys.
|
||||
|
||||
|
||||
.. code:: python
|
||||
|
||||
user_loader = UserLoader()
|
||||
|
||||
user1 = await user_loader.load(1)
|
||||
user1_best_friend = await user_loader.load(user1.best_friend_id)
|
||||
|
||||
user2 = await user_loader.load(2)
|
||||
user2_best_friend = await user_loader.load(user2.best_friend_id)
|
||||
|
||||
|
||||
A naive application may have issued *four* round-trips to a backend for the
|
||||
required information, but with ``DataLoader`` this application will make at most *two*.
|
||||
|
||||
Note that loaded values are one-to-one with the keys and must have the same
|
||||
order. This means that if you load all values from a single query, you must
|
||||
make sure that you then order the query result for the results to match the keys:
|
||||
|
||||
|
||||
.. code:: python
|
||||
|
||||
class UserLoader(DataLoader):
|
||||
async def batch_load_fn(self, keys):
|
||||
users = {user.id: user for user in User.objects.filter(id__in=keys)}
|
||||
return [users.get(user_id) for user_id in keys]
|
||||
|
||||
|
||||
``DataLoader`` allows you to decouple unrelated parts of your application without
|
||||
sacrificing the performance of batch data-loading. While the loader presents
|
||||
an API that loads individual values, all concurrent requests will be coalesced
|
||||
and presented to your batch loading function. This allows your application to
|
||||
safely distribute data fetching requirements throughout your application and
|
||||
maintain minimal outgoing data requests.
|
||||
|
||||
|
||||
|
||||
Using with Graphene
|
||||
-------------------
|
||||
|
||||
DataLoader pairs nicely well with Graphene/GraphQL. GraphQL fields are designed
|
||||
to be stand-alone functions. Without a caching or batching mechanism, it's easy
|
||||
for a naive GraphQL server to issue new database requests each time a field is resolved.
|
||||
|
||||
Consider the following GraphQL request:
|
||||
|
||||
|
||||
.. code::
|
||||
|
||||
{
|
||||
me {
|
||||
name
|
||||
bestFriend {
|
||||
name
|
||||
}
|
||||
friends(first: 5) {
|
||||
name
|
||||
bestFriend {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
If ``me``, ``bestFriend`` and ``friends`` each need to send a request to the backend,
|
||||
there could be at most 13 database requests!
|
||||
|
||||
|
||||
When using DataLoader, we could define the User type using our previous example with
|
||||
leaner code and at most 4 database requests, and possibly fewer if there are cache hits.
|
||||
|
||||
|
||||
.. code:: python
|
||||
|
||||
class User(graphene.ObjectType):
|
||||
name = graphene.String()
|
||||
best_friend = graphene.Field(lambda: User)
|
||||
friends = graphene.List(lambda: User)
|
||||
|
||||
async def resolve_best_friend(root, info):
|
||||
return await user_loader.load(root.best_friend_id)
|
||||
|
||||
async def resolve_friends(root, info):
|
||||
return await user_loader.load_many(root.friend_ids)
|
138
docs/execution/execute.rst
Normal file
138
docs/execution/execute.rst
Normal file
|
@ -0,0 +1,138 @@
|
|||
.. _SchemaExecute:
|
||||
|
||||
Executing a query
|
||||
=================
|
||||
|
||||
For executing a query against a schema, you can directly call the ``execute`` method on it.
|
||||
|
||||
|
||||
.. code:: python
|
||||
|
||||
from graphene import Schema
|
||||
|
||||
schema = Schema(...)
|
||||
result = schema.execute('{ name }')
|
||||
|
||||
``result`` represents the result of execution. ``result.data`` is the result of executing the query, ``result.errors`` is ``None`` if no errors occurred, and is a non-empty list if an error occurred.
|
||||
|
||||
|
||||
.. _SchemaExecuteContext:
|
||||
|
||||
Context
|
||||
_______
|
||||
|
||||
You can pass context to a query via ``context``.
|
||||
|
||||
|
||||
.. code:: python
|
||||
|
||||
from graphene import ObjectType, String, Schema
|
||||
|
||||
class Query(ObjectType):
|
||||
name = String()
|
||||
|
||||
def resolve_name(root, info):
|
||||
return info.context.get('name')
|
||||
|
||||
schema = Schema(Query)
|
||||
result = schema.execute('{ name }', context={'name': 'Syrus'})
|
||||
assert result.data['name'] == 'Syrus'
|
||||
|
||||
|
||||
Variables
|
||||
_________
|
||||
|
||||
You can pass variables to a query via ``variables``.
|
||||
|
||||
|
||||
.. code:: python
|
||||
|
||||
from graphene import ObjectType, Field, ID, Schema
|
||||
|
||||
class Query(ObjectType):
|
||||
user = Field(User, id=ID(required=True))
|
||||
|
||||
def resolve_user(root, info, id):
|
||||
return get_user_by_id(id)
|
||||
|
||||
schema = Schema(Query)
|
||||
result = schema.execute(
|
||||
'''
|
||||
query getUser($id: ID) {
|
||||
user(id: $id) {
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
}
|
||||
''',
|
||||
variables={'id': 12},
|
||||
)
|
||||
|
||||
Root Value
|
||||
__________
|
||||
|
||||
Value used for :ref:`ResolverParamParent` in root queries and mutations can be overridden using ``root`` parameter.
|
||||
|
||||
.. code:: python
|
||||
|
||||
from graphene import ObjectType, Field, Schema
|
||||
|
||||
class Query(ObjectType):
|
||||
me = Field(User)
|
||||
|
||||
def resolve_user(root, info):
|
||||
return {'id': root.id, 'firstName': root.name}
|
||||
|
||||
schema = Schema(Query)
|
||||
user_root = User(id=12, name='bob')
|
||||
result = schema.execute(
|
||||
'''
|
||||
query getUser {
|
||||
user {
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
}
|
||||
''',
|
||||
root=user_root
|
||||
)
|
||||
assert result.data['user']['id'] == user_root.id
|
||||
|
||||
Operation Name
|
||||
______________
|
||||
|
||||
If there are multiple operations defined in a query string, ``operation_name`` should be used to indicate which should be executed.
|
||||
|
||||
.. code:: python
|
||||
|
||||
from graphene import ObjectType, Field, Schema
|
||||
|
||||
class Query(ObjectType):
|
||||
user = Field(User)
|
||||
|
||||
def resolve_user(root, info):
|
||||
return get_user_by_id(12)
|
||||
|
||||
schema = Schema(Query)
|
||||
query_string = '''
|
||||
query getUserWithFirstName {
|
||||
user {
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
}
|
||||
query getUserWithFullName {
|
||||
user {
|
||||
id
|
||||
fullName
|
||||
}
|
||||
}
|
||||
'''
|
||||
result = schema.execute(
|
||||
query_string,
|
||||
operation_name='getUserWithFullName'
|
||||
)
|
||||
assert result.data['user']['fullName']
|
8
docs/execution/fileuploading.rst
Normal file
8
docs/execution/fileuploading.rst
Normal file
|
@ -0,0 +1,8 @@
|
|||
File uploading
|
||||
==============
|
||||
|
||||
File uploading is not part of the official GraphQL spec yet and is not natively
|
||||
implemented in Graphene.
|
||||
|
||||
If your server needs to support file uploading then you can use the library: `graphene-file-upload <https://github.com/lmcgartland/graphene-file-upload>`_ which enhances Graphene to add file
|
||||
uploads and conforms to the unoffical GraphQL `multipart request spec <https://github.com/jaydenseric/graphql-multipart-request-spec>`_.
|
13
docs/execution/index.rst
Normal file
13
docs/execution/index.rst
Normal file
|
@ -0,0 +1,13 @@
|
|||
=========
|
||||
Execution
|
||||
=========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
execute
|
||||
middleware
|
||||
dataloader
|
||||
fileuploading
|
||||
subscriptions
|
||||
queryvalidation
|
70
docs/execution/middleware.rst
Normal file
70
docs/execution/middleware.rst
Normal file
|
@ -0,0 +1,70 @@
|
|||
Middleware
|
||||
==========
|
||||
|
||||
You can use ``middleware`` to affect the evaluation of fields in your schema.
|
||||
|
||||
A middleware is any object or function that responds to ``resolve(next_middleware, *args)``.
|
||||
|
||||
Inside that method, it should either:
|
||||
|
||||
- Send ``resolve`` to the next middleware to continue the evaluation; or
|
||||
- Return a value to end the evaluation early.
|
||||
|
||||
|
||||
Resolve arguments
|
||||
-----------------
|
||||
|
||||
Middlewares ``resolve`` is invoked with several arguments:
|
||||
|
||||
- ``next`` represents the execution chain. Call ``next`` to continue evaluation.
|
||||
- ``root`` is the root value object passed throughout the query.
|
||||
- ``info`` is the resolver info.
|
||||
- ``args`` is the dict of arguments passed to the field.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
This middleware only continues evaluation if the ``field_name`` is not ``'user'``
|
||||
|
||||
.. code:: python
|
||||
|
||||
class AuthorizationMiddleware(object):
|
||||
def resolve(self, next, root, info, **args):
|
||||
if info.field_name == 'user':
|
||||
return None
|
||||
return next(root, info, **args)
|
||||
|
||||
|
||||
And then execute it with:
|
||||
|
||||
.. code:: python
|
||||
|
||||
result = schema.execute('THE QUERY', middleware=[AuthorizationMiddleware()])
|
||||
|
||||
If the ``middleware`` argument includes multiple middlewares,
|
||||
these middlewares will be executed bottom-up, i.e. from last to first.
|
||||
|
||||
Functional example
|
||||
------------------
|
||||
|
||||
Middleware can also be defined as a function. Here we define a middleware that
|
||||
logs the time it takes to resolve each field:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from time import time as timer
|
||||
|
||||
def timing_middleware(next, root, info, **args):
|
||||
start = timer()
|
||||
return_value = next(root, info, **args)
|
||||
duration = round((timer() - start) * 1000, 2)
|
||||
parent_type_name = root._meta.name if root and hasattr(root, '_meta') else ''
|
||||
logger.debug(f"{parent_type_name}.{info.field_name}: {duration} ms")
|
||||
return return_value
|
||||
|
||||
|
||||
And then execute it with:
|
||||
|
||||
.. code:: python
|
||||
|
||||
result = schema.execute('THE QUERY', middleware=[timing_middleware])
|
123
docs/execution/queryvalidation.rst
Normal file
123
docs/execution/queryvalidation.rst
Normal file
|
@ -0,0 +1,123 @@
|
|||
Query Validation
|
||||
================
|
||||
GraphQL uses query validators to check if Query AST is valid and can be executed. Every GraphQL server implements
|
||||
standard query validators. For example, there is an validator that tests if queried field exists on queried type, that
|
||||
makes query fail with "Cannot query field on type" error if it doesn't.
|
||||
|
||||
To help with common use cases, graphene provides a few validation rules out of the box.
|
||||
|
||||
|
||||
Depth limit Validator
|
||||
---------------------
|
||||
The depth limit validator helps to prevent execution of malicious
|
||||
queries. It takes in the following arguments.
|
||||
|
||||
- ``max_depth`` is the maximum allowed depth for any operation in a GraphQL document.
|
||||
- ``ignore`` Stops recursive depth checking based on a field name. Either a string or regexp to match the name, or a function that returns a boolean
|
||||
- ``callback`` Called each time validation runs. Receives an Object which is a map of the depths for each operation.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Here is how you would implement depth-limiting on your schema.
|
||||
|
||||
.. code:: python
|
||||
|
||||
from graphql import validate, parse
|
||||
from graphene import ObjectType, Schema, String
|
||||
from graphene.validation import depth_limit_validator
|
||||
|
||||
|
||||
class MyQuery(ObjectType):
|
||||
name = String(required=True)
|
||||
|
||||
|
||||
schema = Schema(query=MyQuery)
|
||||
|
||||
# queries which have a depth more than 20
|
||||
# will not be executed.
|
||||
|
||||
validation_errors = validate(
|
||||
schema=schema.graphql_schema,
|
||||
document_ast=parse('THE QUERY'),
|
||||
rules=(
|
||||
depth_limit_validator(
|
||||
max_depth=20
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Disable Introspection
|
||||
---------------------
|
||||
the disable introspection validation rule ensures that your schema cannot be introspected.
|
||||
This is a useful security measure in production environments.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Here is how you would disable introspection for your schema.
|
||||
|
||||
.. code:: python
|
||||
|
||||
from graphql import validate, parse
|
||||
from graphene import ObjectType, Schema, String
|
||||
from graphene.validation import DisableIntrospection
|
||||
|
||||
|
||||
class MyQuery(ObjectType):
|
||||
name = String(required=True)
|
||||
|
||||
|
||||
schema = Schema(query=MyQuery)
|
||||
|
||||
# introspection queries will not be executed.
|
||||
|
||||
validation_errors = validate(
|
||||
schema=schema.graphql_schema,
|
||||
document_ast=parse('THE QUERY'),
|
||||
rules=(
|
||||
DisableIntrospection,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Implementing custom validators
|
||||
------------------------------
|
||||
All custom query validators should extend the `ValidationRule <https://github.com/graphql-python/graphql-core/blob/v3.0.5/src/graphql/validation/rules/__init__.py#L37>`_
|
||||
base class importable from the graphql.validation.rules module. Query validators are visitor classes. They are
|
||||
instantiated at the time of query validation with one required argument (context: ASTValidationContext). In order to
|
||||
perform validation, your validator class should define one or more of enter_* and leave_* methods. For possible
|
||||
enter/leave items as well as details on function documentation, please see contents of the visitor module. To make
|
||||
validation fail, you should call validator's report_error method with the instance of GraphQLError describing failure
|
||||
reason. Here is an example query validator that visits field definitions in GraphQL query and fails query validation
|
||||
if any of those fields are blacklisted:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from graphql import GraphQLError
|
||||
from graphql.language import FieldNode
|
||||
from graphql.validation import ValidationRule
|
||||
|
||||
|
||||
my_blacklist = (
|
||||
"disallowed_field",
|
||||
)
|
||||
|
||||
|
||||
def is_blacklisted_field(field_name: str):
|
||||
return field_name.lower() in my_blacklist
|
||||
|
||||
|
||||
class BlackListRule(ValidationRule):
|
||||
def enter_field(self, node: FieldNode, *_args):
|
||||
field_name = node.name.value
|
||||
if not is_blacklisted_field(field_name):
|
||||
return
|
||||
|
||||
self.report_error(
|
||||
GraphQLError(
|
||||
f"Cannot query '{field_name}': field is blacklisted.", node,
|
||||
)
|
||||
)
|
||||
|
40
docs/execution/subscriptions.rst
Normal file
40
docs/execution/subscriptions.rst
Normal file
|
@ -0,0 +1,40 @@
|
|||
.. _SchemaSubscription:
|
||||
|
||||
Subscriptions
|
||||
=============
|
||||
|
||||
To create a subscription, you can directly call the ``subscribe`` method on the
|
||||
schema. This method is async and must be awaited.
|
||||
|
||||
.. code:: python
|
||||
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
from graphene import ObjectType, String, Schema, Field
|
||||
|
||||
# Every schema requires a query.
|
||||
class Query(ObjectType):
|
||||
hello = String()
|
||||
|
||||
def resolve_hello(root, info):
|
||||
return "Hello, world!"
|
||||
|
||||
class Subscription(ObjectType):
|
||||
time_of_day = String()
|
||||
|
||||
async def subscribe_time_of_day(root, info):
|
||||
while True:
|
||||
yield datetime.now().isoformat()
|
||||
await asyncio.sleep(1)
|
||||
|
||||
schema = Schema(query=Query, subscription=Subscription)
|
||||
|
||||
async def main(schema):
|
||||
subscription = 'subscription { timeOfDay }'
|
||||
result = await schema.subscribe(subscription)
|
||||
async for item in result:
|
||||
print(item.data['timeOfDay'])
|
||||
|
||||
asyncio.run(main(schema))
|
||||
|
||||
The ``result`` is an async iterator which yields items in the same manner as a query.
|
|
@ -1,72 +0,0 @@
|
|||
var nib = require("nib");
|
||||
var jeet = require("jeet");
|
||||
var rupture = require("rupture");
|
||||
var path = require("path");
|
||||
var ExtractTextPlugin = require("extract-text-webpack-plugin");
|
||||
var webpack = require("webpack");
|
||||
var CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
|
||||
module.exports = function(config, env) {
|
||||
var IS_STATIC = env === 'static';
|
||||
var entry = config._config.entry.slice();
|
||||
var publicPath = config._config.output.publicPath;
|
||||
// var output = config._config.output;
|
||||
// output.filename = "[name].js";
|
||||
config._config.entry = {
|
||||
bundle: entry,
|
||||
};
|
||||
config.merge({
|
||||
stylus: {
|
||||
use: [nib(), jeet(), rupture()]
|
||||
},
|
||||
output: {
|
||||
filename: "[name].js",
|
||||
publicPath: "/",
|
||||
},
|
||||
resolveLoader: {
|
||||
root: path.join(__dirname, "node_modules"),
|
||||
modulesDirectories: ['./'],
|
||||
},
|
||||
resolve: {
|
||||
root: path.join(__dirname, "node_modules"),
|
||||
alias: {
|
||||
'original-react': path.join(__dirname, "node_modules", "react"),
|
||||
'react/lib': path.join(__dirname, "node_modules", "react", "lib"),
|
||||
'react': path.join(__dirname, 'patched-react.js'),
|
||||
'pypyjs': '../playground/graphene-js/pypyjs',
|
||||
'playground-page': (env != "static")?'../playground/page':'../pages/_empty',
|
||||
'playground-wrapper': (env == "develop")?'../playground/page':'../playground/wrapper',
|
||||
},
|
||||
modulesDirectories: ['./']
|
||||
}
|
||||
});
|
||||
if (IS_STATIC) {
|
||||
config.plugin('extract-css', ExtractTextPlugin, ["app.css"]);
|
||||
}
|
||||
config.plugin('static', CopyWebpackPlugin, [[{ from: '../static'}]]);
|
||||
config.plugin('define-env', webpack.DefinePlugin, [{
|
||||
"ENV": JSON.stringify(env),
|
||||
"PUBLIC_PATH": JSON.stringify(publicPath),
|
||||
}]);
|
||||
// if (env != "static") {
|
||||
// config.plugin('commons', webpack.optimize.CommonsChunkPlugin, ["commons.js"]);
|
||||
// }
|
||||
|
||||
config.loader('stylus', function(cfg) {
|
||||
cfg.test = /\.styl$/;
|
||||
if (IS_STATIC) {
|
||||
cfg.loader = ExtractTextPlugin.extract('style-loader', 'css-loader!stylus-loader', { allChunks: true });
|
||||
}
|
||||
else {
|
||||
cfg.loader = 'style-loader!css-loader!stylus-loader';
|
||||
}
|
||||
return cfg
|
||||
});
|
||||
config.removeLoader('png');
|
||||
config.loader('png', function(cfg) {
|
||||
cfg.test = /\.png$/;
|
||||
cfg.loader = 'url-loader'
|
||||
return cfg
|
||||
})
|
||||
return config;
|
||||
};
|
35
docs/html.js
35
docs/html.js
|
@ -1,35 +0,0 @@
|
|||
import React from 'react';
|
||||
import DocumentTitle from 'react-document-title';
|
||||
|
||||
export default class Html extends React.Component {
|
||||
render() {
|
||||
var title = this.props.title || DocumentTitle.rewind();
|
||||
return (
|
||||
<html lang={this.props.lang}>
|
||||
<head>
|
||||
<meta charSet="utf-8"/>
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0 maximum-scale=1.0'/>
|
||||
<title>{title}</title>
|
||||
<link rel="shortcut icon" href="/favicon.png"/>
|
||||
<link href='https://fonts.googleapis.com/css?family=Raleway:400,600,200,100' rel='stylesheet' type='text/css' />
|
||||
<link href='/app.css' rel='stylesheet' type='text/css' />
|
||||
</head>
|
||||
<body>
|
||||
<div id="react-mount" dangerouslySetInnerHTML={{__html: this.props.body}} />
|
||||
<script src="/bundle.js"/>
|
||||
{this.props.config.ga?<script dangerouslySetInnerHTML={{__html: `
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', '${this.props.config.ga}', 'auto');
|
||||
ga('send', 'pageview');
|
||||
`}}
|
||||
/>:null}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
}
|
26
docs/index.rst
Normal file
26
docs/index.rst
Normal file
|
@ -0,0 +1,26 @@
|
|||
Graphene
|
||||
========
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
quickstart
|
||||
types/index
|
||||
execution/index
|
||||
relay/index
|
||||
testing/index
|
||||
api/index
|
||||
|
||||
.. _Integrations:
|
||||
|
||||
Integrations
|
||||
------------
|
||||
|
||||
* `Graphene-Django <http://docs.graphene-python.org/projects/django/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-django/>`_)
|
||||
* Flask-Graphql (`source <https://github.com/graphql-python/flask-graphql>`_)
|
||||
* `Graphene-SQLAlchemy <http://docs.graphene-python.org/projects/sqlalchemy/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-sqlalchemy/>`_)
|
||||
* `Graphene-Mongo <http://graphene-mongo.readthedocs.io/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-mongo>`_)
|
||||
* `Starlette <https://www.starlette.io/graphql/>`_ (`source <https://github.com/encode/starlette>`_)
|
||||
* `FastAPI <https://fastapi.tiangolo.com/advanced/graphql/>`_ (`source <https://github.com/tiangolo/fastapi>`_)
|
|
@ -1,37 +0,0 @@
|
|||
{
|
||||
"name": "graphene-docs",
|
||||
"version": "1.0.0",
|
||||
"description": "Graphene docs",
|
||||
"main": "n/a",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "npm install && ./node_modules/.bin/gatsby build",
|
||||
"deploy": "npm run build"
|
||||
},
|
||||
"keywords": [
|
||||
"graphene"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"codemirror": "5.9.0",
|
||||
"copy-webpack-plugin": "^0.2.0",
|
||||
"es6-promise": "^3.0.2",
|
||||
"extract-text-webpack-plugin": "^0.9.1",
|
||||
"gatsby": "^0.7.7",
|
||||
"graphiql": "^0.4.5",
|
||||
"graphql": "^0.4.13",
|
||||
"jeet": "^6.1.2",
|
||||
"lodash": "^3.10.1",
|
||||
"nib": "^1.1.0",
|
||||
"react": "^0.14.6",
|
||||
"radium": "0.14.2",
|
||||
"react-burger-menu": "1.4.12",
|
||||
"react-document-title": "^2.0.1",
|
||||
"react-dom": "^0.14.6",
|
||||
"react-router": "^0.13.5",
|
||||
"rupture": "^0.6.1",
|
||||
"stylus-loader": "^1.4.2",
|
||||
"url-loader": "^0.5.7",
|
||||
"webpack": "^1.12.9"
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
class Empty extends React.Component {
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Empty;
|
|
@ -1,156 +0,0 @@
|
|||
var IN_BROWSER = typeof window != 'undefined';
|
||||
import React from 'react';
|
||||
var browser_supported;
|
||||
if (IN_BROWSER) {
|
||||
var isSafari = navigator.vendor && navigator.vendor.indexOf('Apple') > -1 &&
|
||||
navigator.userAgent && !navigator.userAgent.match('CriOS');
|
||||
var browser_supported = !isSafari;
|
||||
}
|
||||
if (IN_BROWSER && browser_supported) {
|
||||
var glfx = require('../vendor/glfx.optim')
|
||||
var particlesJS = require('../vendor/particles.js')
|
||||
}
|
||||
|
||||
class Header extends React.Component {
|
||||
update() {
|
||||
if (!this.mounted) return;
|
||||
var w = this.video.width;
|
||||
var h = this.video.height;
|
||||
if (window.devicePixelRatio > 1) {
|
||||
w = w/2;
|
||||
h = h/2;
|
||||
}
|
||||
this.texture._.initFromCanvas(w, h, this.video);
|
||||
|
||||
this.canvas.draw(this.texture).tiltShift(0, h*2/3, 10, h*2/3, 15, 200).update();
|
||||
requestAnimationFrame(this.update.bind(this));
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
}
|
||||
componentDidMount() {
|
||||
if (!(IN_BROWSER && browser_supported)) return;
|
||||
this.mounted = true;
|
||||
new particlesJS('header-background', {
|
||||
"particles": {
|
||||
"number": {
|
||||
"value": 40,
|
||||
"density": {
|
||||
"enable": true,
|
||||
"value_area": 800
|
||||
}
|
||||
},
|
||||
"color": {
|
||||
"value": "#ffffff"
|
||||
},
|
||||
"shape": {
|
||||
"type": "circle",
|
||||
"stroke": {
|
||||
"width": 0,
|
||||
"color": "#000000"
|
||||
},
|
||||
"polygon": {
|
||||
"nb_sides": 8
|
||||
}
|
||||
},
|
||||
"opacity": {
|
||||
"value": 1,
|
||||
"random": true,
|
||||
"anim": {
|
||||
"enable": true,
|
||||
"speed": .1,
|
||||
"opacity_min": .1,
|
||||
"sync": true
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"value": 2.2,
|
||||
"random": false,
|
||||
"anim": {
|
||||
"enable": false,
|
||||
"speed": .2,
|
||||
"size_min": 2.44,
|
||||
"sync": true
|
||||
}
|
||||
},
|
||||
"line_linked": {
|
||||
"enable": true,
|
||||
"distance": 240,
|
||||
"color": "#ffffff",
|
||||
"opacity": .2,
|
||||
"width": 3
|
||||
},
|
||||
"move": {
|
||||
"enable": true,
|
||||
"speed": 1.1,
|
||||
"direction": "none",
|
||||
"random": true,
|
||||
"straight": false,
|
||||
"out_mode": "bounce",
|
||||
"bounce": false,
|
||||
"attract": {
|
||||
"enable": false,
|
||||
"rotateX": 600,
|
||||
"rotateY": 600
|
||||
}
|
||||
}
|
||||
},
|
||||
"interactivity": {
|
||||
"detect_on": "canvas",
|
||||
"events": {
|
||||
"onhover": {
|
||||
"enable": true,
|
||||
"mode": "repulse"
|
||||
},
|
||||
"onclick": {
|
||||
"enable": true,
|
||||
"mode": "repulse"
|
||||
},
|
||||
"resize": true
|
||||
},
|
||||
"modes": {
|
||||
"grab": {
|
||||
"distance": 400,
|
||||
"line_linked": {
|
||||
"opacity": 1
|
||||
}
|
||||
},
|
||||
"bubble": {
|
||||
"distance": 250,
|
||||
"size": 0,
|
||||
"duration": 2,
|
||||
"opacity": 0,
|
||||
"speed": 3
|
||||
},
|
||||
"repulse": {
|
||||
"distance": 73.08694910712113,
|
||||
"duration": 0.4
|
||||
},
|
||||
"push": {
|
||||
"particles_nb": 4
|
||||
},
|
||||
"remove": {
|
||||
"particles_nb": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"retina_detect": true
|
||||
});
|
||||
var header_background = document.getElementById('header-background');
|
||||
this.canvas = glfx.canvas();
|
||||
this.video = header_background.children[0];
|
||||
this.context = window.pJSDom[0].pJS.canvas.ctx;
|
||||
this.texture = this.canvas.texture(this.video);
|
||||
header_background.appendChild(this.canvas);
|
||||
requestAnimationFrame(this.update.bind(this));
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div id="header-background">
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Header;
|
|
@ -1,51 +0,0 @@
|
|||
import React from 'react';
|
||||
import { RouteHandler, Link, State } from 'react-router';
|
||||
import Icon from 'assets/icon'
|
||||
import {stack as Menu} from 'react-burger-menu';
|
||||
|
||||
import Header from './_header'
|
||||
import logo from '!raw!assets/logo.svg'
|
||||
import styles from '../css/main.styl'
|
||||
|
||||
class Template extends React.Component {
|
||||
render() {
|
||||
var path = this.props.page.path;
|
||||
var isIndex = path == '/';
|
||||
return (
|
||||
<div>
|
||||
<Menu width={200} right>
|
||||
<span><Link to="/playground/" className={path.indexOf('/playground')==0?"active":null}>Try it out</Link></span>
|
||||
<span><Link to="/docs/quickstart/" className={path.indexOf('/docs')==0?"active":null}>Docs</Link></span>
|
||||
<span><Link to="/community/">Community</Link></span>
|
||||
<a href="https://github.com/graphql-python/graphene/">Github</a>
|
||||
</Menu>
|
||||
<header className="header">
|
||||
<div className="header-wrapper">
|
||||
<Link className="header-logo" to="/">
|
||||
<Icon src={logo} />
|
||||
Graphene
|
||||
</Link>
|
||||
<nav className="header-nav">
|
||||
<Link to="/playground/" className={path.indexOf('/playground')==0?"active":null}>Try it out</Link>
|
||||
<Link to="/docs/quickstart/" className={path.indexOf('/docs')==0?"active":null}>Docs</Link>
|
||||
<Link to="/community/">Community</Link>
|
||||
<a href="https://github.com/graphql-python/graphene/">Github</a>
|
||||
</nav>
|
||||
</div>
|
||||
{isIndex?
|
||||
<div className="header-extended">
|
||||
<h1>
|
||||
GraphQL in Python<br />
|
||||
made <strong>simple</strong>
|
||||
</h1>
|
||||
<Link to="/docs/quickstart/" className="get-started">Get Started</Link>
|
||||
<Header />
|
||||
</div>:null}
|
||||
</header>
|
||||
<RouteHandler {...this.props}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Template;
|
|
@ -1,45 +0,0 @@
|
|||
---
|
||||
layout: page
|
||||
title: Community
|
||||
active_tab: community
|
||||
description: The biggest GraphQL Community in Python
|
||||
---
|
||||
|
||||
Graphene is constantly developing thanks to an active volunteer community. There are many different places where you discuss Graphene, share your experiences or ask for help. **Your feedback and participation are very welcome**!
|
||||
|
||||
If you think working with Graphene is fun, there are many ways you can contribute to it. Please join us in the Slack community and help us shape the next generation API’s.
|
||||
|
||||
[](https://graphql-slack.herokuapp.com/)
|
||||
|
||||
## Our Repositories
|
||||
|
||||
- **GraphQL Core**: [Source Code] - [PyPI package]
|
||||
- **GraphQL Relay**: [Source Code][1] - [PyPI package][2]
|
||||
- **Graphene**: [Source Code][3] - [PyPI package][4]
|
||||
|
||||
Django integration:
|
||||
- **graphql-django-view**: [Source Code][5] - [PyPI package][6]
|
||||
- **django-graphiql**: [Source Code][7] - [PyPI package][8]
|
||||
|
||||
Flask integration:
|
||||
- **Flask-GraphQL**: [Source Code][9] - [PyPI package][10]
|
||||
|
||||
## Other related projects
|
||||
|
||||
- [Graphene Todo MVC Example](https://github.com/mickeyinfoshan/graphene_todo) by [@mickeyinfoshan](https://github.com/mickeyinfoshan)
|
||||
- [Flask GraphQL Demo](https://github.com/amitsaha/flask-graphql-demo) by [@echorand](https://twitter.com/echorand)
|
||||
- [Example GraphQL application](https://github.com/msoedov/flask-graphql-example) by [@msoedov](https://twitter.com/msoedov) with Flask, pypy/python3 and MongoDB (using Docker containers)
|
||||
|
||||
|
||||
[Source Code]: https://github.com/graphql-python/graphql-core
|
||||
[PyPI package]: https://pypi.python.org/pypi/graphql-core
|
||||
[1]: https://github.com/graphql-python/graphql-relay
|
||||
[2]: https://pypi.python.org/pypi/graphql-relay
|
||||
[3]: https://github.com/graphql-python/graphene
|
||||
[4]: https://pypi.python.org/pypi/graphene
|
||||
[5]: https://github.com/graphql-python/graphql-django-view
|
||||
[6]: https://pypi.python.org/pypi/graphql-django-view
|
||||
[7]: https://github.com/graphql-python/django-graphiql
|
||||
[8]: https://pypi.python.org/pypi/django-graphiql
|
||||
[9]: https://github.com/graphql-python/flask-graphql
|
||||
[10]: https://pypi.python.org/pypi/Flask-GraphQL
|
|
@ -1,59 +0,0 @@
|
|||
import React from 'react';
|
||||
import { RouteHandler, Link, State } from 'react-router';
|
||||
import _ from 'lodash';
|
||||
|
||||
class Template extends React.Component {
|
||||
goToPage(event) {
|
||||
event.target.blur();
|
||||
var page = event.target.value;
|
||||
this.context.router.transitionTo(page);
|
||||
}
|
||||
render() {
|
||||
var docs = this.props.config.docs;
|
||||
var docs_index = _.indexBy(this.props.pages, 'path');
|
||||
var pages = [];
|
||||
var aside_links = Object.keys(docs).map((key) => {
|
||||
let group = docs[key];
|
||||
return <div className="docs-aside-group" key={key}>
|
||||
<h3>{group.name}</h3>
|
||||
{group.pages.map((page) => {
|
||||
pages.push(page)
|
||||
return <Link key={page} to={page}>{docs_index[page].data.title}</Link>
|
||||
})}
|
||||
</div>;
|
||||
});
|
||||
var aside_options = Object.keys(docs).map((key) => {
|
||||
let group = docs[key];
|
||||
return <optgroup key={key} label={group.name}>
|
||||
{group.pages.map((page) => {
|
||||
return <option key={page} value={page}>{docs_index[page].data.title}</option>
|
||||
})}
|
||||
</optgroup>;
|
||||
});
|
||||
var next_page_index = pages.indexOf(this.props.page.path)+1;
|
||||
var next_page = pages[next_page_index];
|
||||
return (
|
||||
<div>
|
||||
<div className="page-title"><h1>Documentation</h1></div>
|
||||
<div className="docs">
|
||||
<aside className="docs-aside">
|
||||
{aside_links}
|
||||
<select className="docs-aside-navselect" value={this.props.page.path} onChange={this.goToPage.bind(this)}>
|
||||
{aside_options}
|
||||
</select>
|
||||
</aside>
|
||||
<div className="docs-content">
|
||||
<RouteHandler {...this.props} docs={true}/>
|
||||
{next_page?<Link className="docs-next" to={next_page}>Next - {docs_index[next_page].data.title} →</Link>:null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Template.contextTypes = {
|
||||
router: React.PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = Template;
|
|
@ -1,107 +0,0 @@
|
|||
---
|
||||
title: Basic Types
|
||||
description: Walkthrough Basic Types
|
||||
---
|
||||
|
||||
# Basic Types
|
||||
|
||||
Graphene define the following base Scalar Types:
|
||||
- `graphene.String`
|
||||
- `graphene.Int`
|
||||
- `graphene.Float`
|
||||
- `graphene.Boolean`
|
||||
- `graphene.ID`
|
||||
|
||||
Also the following Types are available:
|
||||
- `graphene.List`
|
||||
- `graphene.NonNull`
|
||||
|
||||
Graphene also provides custom scalars for Dates and JSON:
|
||||
- `graphene.core.types.custom_scalars.DateTime`
|
||||
- `graphene.core.types.custom_scalars.JSONString`
|
||||
|
||||
## Shortcuts
|
||||
|
||||
There are some shortcuts for building schemas more easily.
|
||||
The following are equivalent
|
||||
|
||||
```python
|
||||
# A list of strings
|
||||
string_list = graphene.List(graphene.String())
|
||||
string_list = graphene.String().List
|
||||
|
||||
# A non-null string
|
||||
string_non_null = graphene.String().NonNull
|
||||
string_non_null = graphene.NonNull(graphene.String())
|
||||
```
|
||||
|
||||
|
||||
## Custom scalars
|
||||
|
||||
You can also create a custom scalar for your schema.
|
||||
If you want to create a DateTime Scalar Type just type:
|
||||
|
||||
```python
|
||||
import datetime
|
||||
from graphene.core.classtypes import Scalar
|
||||
from graphql.core.language import ast
|
||||
|
||||
class DateTime(Scalar):
|
||||
'''DateTime'''
|
||||
@staticmethod
|
||||
def serialize(dt):
|
||||
return dt.isoformat()
|
||||
|
||||
@staticmethod
|
||||
def parse_literal(node):
|
||||
if isinstance(node, ast.StringValue):
|
||||
return datetime.datetime.strptime(
|
||||
node.value, "%Y-%m-%dT%H:%M:%S.%f")
|
||||
|
||||
@staticmethod
|
||||
def parse_value(value):
|
||||
return datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f")
|
||||
```
|
||||
|
||||
## Mounting in ClassTypes
|
||||
|
||||
This types if are mounted in a `ObjectType`, `Interface` or `Mutation`,
|
||||
would act as `Field`s.
|
||||
|
||||
```python
|
||||
class Person(graphene.ObjectType):
|
||||
name = graphene.String()
|
||||
|
||||
# Is equivalent to:
|
||||
class Person(graphene.ObjectType):
|
||||
name = graphene.Field(graphene.String())
|
||||
```
|
||||
|
||||
## Mounting in Fields
|
||||
|
||||
If the types are mounted in a `Field`, would act as `Argument`s.
|
||||
|
||||
```python
|
||||
graphene.Field(graphene.String(), to=graphene.String())
|
||||
|
||||
# Is equivalent to:
|
||||
graphene.Field(graphene.String(), to=graphene.Argument(graphene.String()))
|
||||
```
|
||||
|
||||
|
||||
## Using custom object types as argument
|
||||
|
||||
To use a custom object type as an argument, you need to inherit `graphene.InputObjectType`, not `graphene.ObjectType`.
|
||||
|
||||
```python
|
||||
class CustomArgumentObjectType(graphene.InputObjectType):
|
||||
field1 = graphene.String()
|
||||
field2 = graphene.String()
|
||||
|
||||
```
|
||||
|
||||
Then, when defining this in an argument, you need to wrap it in an `Argument` object.
|
||||
|
||||
```python
|
||||
graphene.Field(graphene.String(), to=graphene.Argument(CustomArgumentObjectType))
|
||||
```
|
|
@ -1,116 +0,0 @@
|
|||
---
|
||||
title: Authorization
|
||||
description: Details on how to restrict data access
|
||||
---
|
||||
|
||||
# Authorization in Django
|
||||
|
||||
There are two main ways you may want to limit access to data when working
|
||||
with Graphene and Django: limiting which fields are accessible via GraphQL
|
||||
and limiting which objects a user can access.
|
||||
|
||||
Let's use a simple example model.
|
||||
|
||||
```python
|
||||
from django.db import models
|
||||
|
||||
class Post(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
content = models.TextField()
|
||||
published = models.BooleanField(default=False)
|
||||
owner = models.ForeignKey('auth.User')
|
||||
```
|
||||
|
||||
## Limiting Field Access
|
||||
|
||||
This is easy, simply use the `only_fields` meta attribute.
|
||||
|
||||
```python
|
||||
from graphene.contrib.django.types import DjangoNode
|
||||
from .models import Post
|
||||
|
||||
class PostNode(DjangoNode):
|
||||
class Meta:
|
||||
model = Post
|
||||
only_fields = ('title', 'content')
|
||||
```
|
||||
|
||||
## Queryset Filtering On Lists
|
||||
|
||||
In order to filter which objects are available in a queryset-based list,
|
||||
define a resolve method for that field and return the desired queryset.
|
||||
|
||||
```python
|
||||
from graphene import ObjectType
|
||||
from graphene.contrib.django.filter import DjangoFilterConnectionField
|
||||
from .models import Post
|
||||
|
||||
class Query(ObjectType):
|
||||
all_posts = DjangoFilterConnectionField(CategoryNode)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def resolve_all_posts(self, args, info):
|
||||
return Post.objects.filter(published=True)
|
||||
```
|
||||
|
||||
## User-based Queryset Filtering
|
||||
|
||||
If you are using `graphql-django-view` you can access Django's request object
|
||||
via `with_context` decorator.
|
||||
|
||||
```python
|
||||
from graphene import ObjectType
|
||||
from graphene.contrib.django.filter import DjangoFilterConnectionField
|
||||
from .models import Post
|
||||
|
||||
class Query(ObjectType):
|
||||
my_posts = DjangoFilterConnectionField(CategoryNode)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@with_context
|
||||
def resolve_my_posts(self, args, context, info):
|
||||
# context will reference to the Django request
|
||||
if not context.user.is_authenticated():
|
||||
return []
|
||||
else:
|
||||
return Post.objects.filter(owner=context.user)
|
||||
```
|
||||
|
||||
If you're using your own view, passing the request context into the schema is
|
||||
simple.
|
||||
|
||||
```python
|
||||
result = schema.execute(query, context_value=request)
|
||||
```
|
||||
|
||||
## Filtering ID-based node access
|
||||
|
||||
In order to add authorization to id-based node access, we need to add a method
|
||||
to your `DjangoNode`.
|
||||
|
||||
```python
|
||||
from graphene.contrib.django.types import DjangoNode
|
||||
from .models import Post
|
||||
|
||||
class PostNode(DjangoNode):
|
||||
class Meta:
|
||||
model = Post
|
||||
only_fields = ('title', 'content')
|
||||
|
||||
@classmethod
|
||||
@with_context
|
||||
def get_node(Cls, id, context, info):
|
||||
try:
|
||||
post = Cls._meta.model.objects.get(id=id)
|
||||
except Cls._meta.model.DoesNotExist:
|
||||
return None
|
||||
|
||||
if post.published or context.user is post.owner:
|
||||
return Cls(instance)
|
||||
else:
|
||||
return None
|
||||
```
|
|
@ -1,55 +0,0 @@
|
|||
---
|
||||
title: Django Debug Middleware
|
||||
description: How to debug Django queries and requests using Graphene
|
||||
---
|
||||
|
||||
# Django Debug Middleware
|
||||
|
||||
You can debug your GraphQL queries in a similar way to [django-debug-toolbar](https://django-debug-toolbar.readthedocs.org/),
|
||||
but outputing in the results in GraphQL response as fields, instead of the graphical HTML interface.
|
||||
|
||||
|
||||
For that, you will need to add the plugin in your graphene schema.
|
||||
|
||||
## Installation
|
||||
|
||||
For use the Django Debug plugin in Graphene:
|
||||
* Import `DjangoDebugMiddleware` and add it to the `middlewares` argument when you initiate the `Schema`.
|
||||
* Add the `debug` field into the schema root `Query` with the value `graphene.Field(DjangoDebug, name='__debug')`.
|
||||
|
||||
|
||||
```python
|
||||
from graphene.contrib.django.debug import DjangoDebugMiddleware, DjangoDebug
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
# ...
|
||||
debug = graphene.Field(DjangoDebug, name='__debug')
|
||||
|
||||
schema = graphene.Schema(query=Query, middlewares=[DjangoDebugMiddleware()])
|
||||
```
|
||||
|
||||
|
||||
## Querying
|
||||
|
||||
You can query it for outputing all the sql transactions that happened in the GraphQL request, like:
|
||||
|
||||
```graphql
|
||||
{
|
||||
# A example that will use the ORM for interact with the DB
|
||||
allIngredients {
|
||||
edges {
|
||||
node {
|
||||
id,
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
# Here is the debug field that will output the SQL queries
|
||||
__debug {
|
||||
sql {
|
||||
rawSql
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Note that the `__debug` field must be the last field in your query.
|
|
@ -1,159 +0,0 @@
|
|||
---
|
||||
title: Filtering
|
||||
description: Details of how to perform filtering in Graphene Django
|
||||
---
|
||||
|
||||
# Filtering
|
||||
|
||||
Graphene integrates with [django-filter](https://django-filter.readthedocs.org)
|
||||
to provide filtering of results. See the
|
||||
[usage documentation](https://django-filter.readthedocs.org/en/latest/usage.html#the-filter)
|
||||
for details on the format for `filter_fields`.
|
||||
|
||||
This filtering is only available when using the Django integrations
|
||||
(i.e. nodes which extend `DjangoNode`). Additionally `django-filter`
|
||||
is an optional dependency of Graphene. You will need to
|
||||
install it manually, which can be done as follows:
|
||||
|
||||
```bash
|
||||
# You'll need to django-filter
|
||||
pip install django-filter
|
||||
```
|
||||
|
||||
**Note: The techniques below are demoed in the
|
||||
[cookbook example app](https://github.com/graphql-python/graphene/tree/master/examples/cookbook_django).**
|
||||
|
||||
## Filterable fields
|
||||
|
||||
The `filter_fields` parameter is used to specify the fields which can be filtered upon.
|
||||
The value specified here is passed directly to `django-filter`, so see the
|
||||
[filtering documentation](https://django-filter.readthedocs.org/en/latest/usage.html#the-filter)
|
||||
for full details on the range of options available.
|
||||
|
||||
For example:
|
||||
|
||||
```python
|
||||
class AnimalNode(DjangoNode):
|
||||
class Meta:
|
||||
# Assume you have an Animal model defined with the following fields
|
||||
model = Animal
|
||||
filter_fields = ['name', 'genus', 'is_domesticated']
|
||||
|
||||
class Query(ObjectType):
|
||||
animal = relay.NodeField(AnimalNode)
|
||||
all_animals = DjangoFilterConnectionField(AnimalNode)
|
||||
```
|
||||
|
||||
You could then perform a query such as:
|
||||
|
||||
```graphql
|
||||
query {
|
||||
# Note that fields names become camelcased
|
||||
allAnimals(genus: "cat", isDomesticated: true) {
|
||||
edges {
|
||||
node {
|
||||
id,
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also make more complex lookup types available:
|
||||
|
||||
```python
|
||||
class AnimalNode(DjangoNode):
|
||||
class Meta:
|
||||
model = Animal
|
||||
# Provide more complex lookup types
|
||||
filter_fields = {
|
||||
'name': ['exact', 'icontains', 'istartswith'],
|
||||
'genus': ['exact'],
|
||||
'is_domesticated': ['exact'],
|
||||
}
|
||||
```
|
||||
|
||||
Which you could query as follows:
|
||||
|
||||
```graphql
|
||||
query {
|
||||
# Note that fields names become camelcased
|
||||
allAnimals(name_Icontains: "lion") {
|
||||
edges {
|
||||
node {
|
||||
id,
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Orderable fields
|
||||
|
||||
Ordering can also be specified using `filter_order_by`. Like `filter_fields`,
|
||||
this value is also passed directly to `django-filter` as the `order_by` field.
|
||||
For full details see the
|
||||
[order_by documentation](https://django-filter.readthedocs.org/en/latest/usage.html#ordering-using-order-by).
|
||||
|
||||
For example:
|
||||
|
||||
```python
|
||||
class AnimalNode(DjangoNode):
|
||||
class Meta:
|
||||
model = Animal
|
||||
filter_fields = ['name', 'genus', 'is_domesticated']
|
||||
# Either a tuple/list of fields upon which ordering is allowed, or
|
||||
# True to allow filtering on all fields specified in filter_fields
|
||||
order_by_fields = True
|
||||
```
|
||||
|
||||
You can then control the ordering via the `orderBy` argument:
|
||||
|
||||
```graphql
|
||||
query {
|
||||
allAnimals(orderBy: "name") {
|
||||
edges {
|
||||
node {
|
||||
id,
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Custom Filtersets
|
||||
|
||||
By default Graphene provides easy access to the most commonly used
|
||||
features of `django-filter`. This is done by transparently creating a
|
||||
`django_filters.FilterSet` class for you and passing in the values for
|
||||
`filter_fields` and `order_by_fields`.
|
||||
|
||||
However, you may find this to be insufficient. In these cases you can
|
||||
create your own `Filterset` as follows:
|
||||
|
||||
```python
|
||||
class AnimalNode(DjangoNode):
|
||||
class Meta:
|
||||
# Assume you have an Animal model defined with the following fields
|
||||
model = Animal
|
||||
filter_fields = ['name', 'genus', 'is_domesticated']
|
||||
|
||||
|
||||
class AnimalFilter(django_filters.FilterSet):
|
||||
# Do case-insensitive lookups on 'name'
|
||||
name = django_filters.CharFilter(lookup_type='iexact')
|
||||
|
||||
class Meta:
|
||||
model = Animal
|
||||
fields = ['name', 'genus', 'is_domesticated']
|
||||
|
||||
|
||||
class Query(ObjectType):
|
||||
animal = relay.NodeField(AnimalNode)
|
||||
# We specify our custom AnimalFilter using the filterset_class param
|
||||
all_animals = DjangoFilterConnectionField(AnimalNode,
|
||||
filterset_class=AnimalFilter)
|
||||
```
|
|
@ -1,50 +0,0 @@
|
|||
---
|
||||
title: Introspection Schema
|
||||
description: A guide to instrospection schema in Django
|
||||
---
|
||||
|
||||
# Introspection Schema
|
||||
|
||||
Relay uses [Babel Relay Plugin](https://facebook.github.io/relay/docs/guides-babel-plugin.html)
|
||||
that requires you to provide your GraphQL schema data.
|
||||
|
||||
Graphene comes with a management command for Django to dump your schema data to
|
||||
`schema.json` that is compatible with babel-relay-plugin.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Include `graphene.contrib.django` to `INSTALLED_APPS` in you project settings:
|
||||
|
||||
```python
|
||||
INSTALLED_APPS += ('graphene.contrib.django')
|
||||
```
|
||||
|
||||
Assuming your Graphene schema is at `tutorial.quickstart.schema`, run the command:
|
||||
|
||||
```bash
|
||||
./manage.py graphql_schema --schema tutorial.quickstart.schema --out schema.json
|
||||
```
|
||||
|
||||
It dumps your full introspection schema to `schema.json` inside your project root
|
||||
directory. Point `babel-relay-plugin` to this file and you're ready to use Relay
|
||||
with Graphene GraphQL implementation.
|
||||
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
To simplify the command to `./manage.py graphql_schema`, you can specify the
|
||||
parameters in your settings.py:
|
||||
|
||||
```python
|
||||
GRAPHENE_SCHEMA = 'tutorial.quickstart.schema'
|
||||
GRAPHENE_SCHEMA_OUTPUT = 'data/schema.json' # defaults to schema.json
|
||||
```
|
||||
|
||||
Running `./manage.py graphql_schema` dumps your schema to
|
||||
`<project root>/data/schema.json`.
|
||||
|
||||
|
||||
## Help
|
||||
|
||||
Run `./manage.py graphql_schema -h` for command usage.
|
|
@ -1,312 +0,0 @@
|
|||
---
|
||||
title: Quickstart
|
||||
description: A Quick guide to Graphene in Django
|
||||
---
|
||||
|
||||
# Django Tutorial
|
||||
|
||||
Graphene has a number of additional features that are designed to make
|
||||
working with Django *really simple*.
|
||||
|
||||
**Note: The code in this quickstart is pulled from the
|
||||
[cookbook example app](https://github.com/graphql-python/graphene/tree/master/examples/cookbook_django)**.
|
||||
|
||||
|
||||
## Setup the Django project
|
||||
|
||||
We will setup the project, create the following:
|
||||
|
||||
* A Django project called `cookbook`
|
||||
* An app within `cookbook` called `ingredients`
|
||||
|
||||
```bash
|
||||
# Create the project directory
|
||||
mkdir cookbook
|
||||
cd cookbook
|
||||
|
||||
# Create a virtualenv to isolate our package dependencies locally
|
||||
virtualenv env
|
||||
source env/bin/activate # On Windows use `env\Scripts\activate`
|
||||
|
||||
# Install Django and Graphene with Django support
|
||||
pip install django
|
||||
pip install graphene[django]
|
||||
pip install django-graphiql
|
||||
|
||||
# Set up a new project with a single application
|
||||
django-admin.py startproject cookbook . # Note the trailing '.' character
|
||||
django-admin.py startapp ingredients
|
||||
```
|
||||
|
||||
Now sync your database for the first time:
|
||||
|
||||
```bash
|
||||
python manage.py migrate
|
||||
```
|
||||
|
||||
Let's create a few simple models...
|
||||
|
||||
|
||||
## Defining our models
|
||||
|
||||
Let's get started with these models:
|
||||
|
||||
```python
|
||||
# cookbook/ingredients/models.py
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Category(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Ingredient(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
notes = models.TextField()
|
||||
category = models.ForeignKey(Category, related_name='ingredients')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
```
|
||||
|
||||
## Schema
|
||||
|
||||
GraphQL presents your objects to the world as a graph structure rather than a more
|
||||
hierarchical structure to which you may be accustomed. In order to create this
|
||||
representation, Graphene needs to know about each *type* of object which will appear in
|
||||
the graph.
|
||||
|
||||
This graph also has a *root type* through which all access begins. This is the `Query` class below.
|
||||
In this example, we provide the ability to list all ingredients via `all_ingredients`, and the
|
||||
ability to obtain a specific ingredient via `ingredient`.
|
||||
|
||||
Create `cookbook/ingredients/schema.py` and type the following:
|
||||
|
||||
```python
|
||||
# cookbook/ingredients/schema.py
|
||||
from graphene import relay, ObjectType
|
||||
from graphene.contrib.django.filter import DjangoFilterConnectionField
|
||||
from graphene.contrib.django.types import DjangoNode
|
||||
|
||||
from cookbook.ingredients.models import Category, Ingredient
|
||||
|
||||
|
||||
# Graphene will automatically map the Category model's fields onto the CategoryNode.
|
||||
# This is configured in the CategoryNode's Meta class (as you can see below)
|
||||
class CategoryNode(DjangoNode):
|
||||
class Meta:
|
||||
model = Category
|
||||
filter_fields = ['name', 'ingredients']
|
||||
filter_order_by = ['name']
|
||||
|
||||
|
||||
class IngredientNode(DjangoNode):
|
||||
class Meta:
|
||||
model = Ingredient
|
||||
# Allow for some more advanced filtering here
|
||||
filter_fields = {
|
||||
'name': ['exact', 'icontains', 'istartswith'],
|
||||
'notes': ['exact', 'icontains'],
|
||||
'category': ['exact'],
|
||||
'category__name': ['exact'],
|
||||
}
|
||||
filter_order_by = ['name', 'category__name']
|
||||
|
||||
|
||||
class Query(ObjectType):
|
||||
category = relay.NodeField(CategoryNode)
|
||||
all_categories = DjangoFilterConnectionField(CategoryNode)
|
||||
|
||||
ingredient = relay.NodeField(IngredientNode)
|
||||
all_ingredients = DjangoFilterConnectionField(IngredientNode)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
```
|
||||
|
||||
The filtering functionality is provided by
|
||||
[django-filter](https://django-filter.readthedocs.org). See the
|
||||
[usage documentation](https://django-filter.readthedocs.org/en/latest/usage.html#the-filter)
|
||||
for details on the format for `filter_fields`. While optional, this tutorial makes use of this functionality so you will need to install `django-filter` for this tutorial to work:
|
||||
|
||||
```bash
|
||||
pip install django-filter
|
||||
```
|
||||
|
||||
Note that the above `Query` class is marked as 'abstract'. This is because we
|
||||
will now create a project-level query which will combine all our app-level
|
||||
queries.
|
||||
|
||||
Create the parent project-level `cookbook/schema.py`:
|
||||
|
||||
```python
|
||||
import graphene
|
||||
|
||||
import cookbook.ingredients.schema
|
||||
|
||||
|
||||
class Query(cookbook.ingredients.schema.Query):
|
||||
# This class will inherit from multiple Queries
|
||||
# as we begin to add more apps to our project
|
||||
pass
|
||||
|
||||
schema = graphene.Schema(name='Cookbook Schema')
|
||||
schema.query = Query
|
||||
```
|
||||
|
||||
You can think of this as being something like your top-level `urls.py`
|
||||
file (although it currently lacks any namespacing).
|
||||
|
||||
## Update `INSTALLED_APPS`
|
||||
|
||||
Next, install your app and GraphiQL in your Django project. GraphiQL is
|
||||
a web-based integrated development environment to assist in the writing and
|
||||
executing of GraphQL queries. It will provide us with a simple and easy way
|
||||
of testing our cookbook project.
|
||||
|
||||
Add `ingredients`, `graphene.contrib.django` and `django_graphiql` to
|
||||
`INSTALLED_APPS` in `cookbook/settings.py`:
|
||||
|
||||
```python
|
||||
INSTALLED_APPS = [
|
||||
...
|
||||
'django_graphiql',
|
||||
|
||||
# This will also make the `graphql_schema` management command available
|
||||
'graphene.contrib.django',
|
||||
|
||||
# Install the ingredients app
|
||||
'ingredients',
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
## Creating GraphQL and GraphiQL views
|
||||
|
||||
Unlike a RESTful API, there is only a single URL from which GraphQL is accessed.
|
||||
Requests to this URL are handled by Graphene's `GraphQLView` view.
|
||||
|
||||
Additionally, we'll add a URL for aforementioned GraphiQL, and for the Django admin
|
||||
interface (the latter can be useful for creating test data).
|
||||
|
||||
```python
|
||||
from django.conf.urls import url, include
|
||||
from django.contrib import admin
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from graphene.contrib.django.views import GraphQLView
|
||||
|
||||
from cookbook.schema import schema
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^admin/', admin.site.urls),
|
||||
url(r'^graphql', csrf_exempt(GraphQLView.as_view(schema=schema))),
|
||||
url(r'^graphiql', include('django_graphiql.urls')),
|
||||
]
|
||||
```
|
||||
|
||||
## Apply model changes to database
|
||||
|
||||
Tell Django that we've added models and update the database schema to reflect these additions.
|
||||
|
||||
```bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
```
|
||||
|
||||
## Load some test data
|
||||
|
||||
Now is a good time to load up some test data. The easiest option will be to
|
||||
[download the ingredients.json](https://raw.githubusercontent.com/graphql-python/graphene/master/examples/cookbook_django/cookbook/ingredients/fixtures/ingredients.json)
|
||||
fixture and place it in
|
||||
`cookbook/ingredients/fixtures/ingredients.json`. You can then run the following:
|
||||
|
||||
```
|
||||
$ python ./manage.py loaddata ingredients
|
||||
|
||||
Installed 6 object(s) from 1 fixture(s)
|
||||
```
|
||||
|
||||
Alternatively you can use the Django admin interface to create some data yourself.
|
||||
You'll need to run the development server (see below), and create a login
|
||||
for yourself too (`./manage.py createsuperuser`).
|
||||
|
||||
## Testing our GraphQL schema
|
||||
|
||||
We're now ready to test the API we've built. Let's fire up the server from the command line.
|
||||
|
||||
```bash
|
||||
$ python ./manage.py runserver
|
||||
|
||||
Performing system checks...
|
||||
Django version 1.9, using settings 'cookbook.settings'
|
||||
Starting development server at http://127.0.0.1:8000/
|
||||
Quit the server with CONTROL-C.
|
||||
```
|
||||
|
||||
Go to [localhost:8000/graphiql](http://localhost:8000/graphiql) and type your first query!
|
||||
|
||||
```graphql
|
||||
query {
|
||||
allIngredients {
|
||||
edges {
|
||||
node {
|
||||
id,
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The above will return the names & IDs for all ingredients. But perhaps you want
|
||||
a specific ingredient:
|
||||
|
||||
```graphql
|
||||
query {
|
||||
# Graphene creates globally unique IDs for all objects.
|
||||
# You may need to copy this value from the results of the first query
|
||||
ingredient(id: "SW5ncmVkaWVudE5vZGU6MQ==") {
|
||||
name
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also get each ingredient for each category:
|
||||
|
||||
```graphql
|
||||
query {
|
||||
allCategories {
|
||||
edges {
|
||||
node {
|
||||
name,
|
||||
ingredients {
|
||||
edges {
|
||||
node {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Or you can get only 'meat' ingredients containing the letter 'e':
|
||||
|
||||
```graphql
|
||||
query {
|
||||
# You can also use `category: "CATEGORY GLOBAL ID"`
|
||||
allIngredients(name_Icontains: "e", category_Name: "Meat") {
|
||||
edges {
|
||||
node {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
|
@ -1,33 +0,0 @@
|
|||
---
|
||||
title: Enums
|
||||
description: Walkthrough Enums
|
||||
---
|
||||
|
||||
# Enums
|
||||
|
||||
A `Enum` is a special `GraphQL` type that represents a set of symbolic names (members) bound to unique, constant values.
|
||||
|
||||
## Enum definition
|
||||
|
||||
You can create an `Enum` using classes:
|
||||
|
||||
```python
|
||||
import graphene
|
||||
|
||||
class Episode(graphene.Enum):
|
||||
NEWHOPE = 4
|
||||
EMPIRE = 5
|
||||
JEDI = 6
|
||||
```
|
||||
|
||||
But also using instances of Enum:
|
||||
|
||||
```python
|
||||
Episode = graphene.Enum('Episode', [('NEWHOPE', 4), ('EMPIRE', 5), ('JEDI', 6)])
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
Internally, `graphene.Enum` uses [`enum.Enum`](https://docs.python.org/3/library/enum.html) Python implementation if available, or a backport if not.
|
||||
|
||||
So you can use it in the same way as you would do with Python `enum.Enum`.
|
|
@ -1,53 +0,0 @@
|
|||
---
|
||||
title: Interfaces
|
||||
description: Walkthrough Interfaces
|
||||
---
|
||||
|
||||
# Interfaces
|
||||
|
||||
An Interface contains the essential fields that will be shared among multiple ObjectTypes.
|
||||
|
||||
The basics:
|
||||
- Each Interface is a Python class that inherits from `graphene.Interface`.
|
||||
- Each attribute of the Interface represents a GraphQL field.
|
||||
|
||||
## Quick example
|
||||
|
||||
This example model defines a Character, which has a name. `Human` and `Droid` inherit from it.
|
||||
|
||||
```python
|
||||
import graphene
|
||||
|
||||
# Character is an Interface
|
||||
class Character(graphene.Interface):
|
||||
name = graphene.String()
|
||||
|
||||
# Human is an ObjectType, as inherits an interface
|
||||
class Human(Character):
|
||||
born_in = graphene.String()
|
||||
|
||||
# Droid is an ObjectType, as inherits an interface
|
||||
class Droid(Character):
|
||||
function = graphene.String()
|
||||
|
||||
```
|
||||
|
||||
**name** is a field in the `Character` interface that will be in both `Human` and `Droid` ObjectTypes (as those inherit from `Character`). Each ObjectType also define extra fields at the same time.
|
||||
|
||||
The above types would have the following representation in a schema:
|
||||
|
||||
```graphql
|
||||
interface Character {
|
||||
name: String
|
||||
}
|
||||
|
||||
type Droid implements Character {
|
||||
name: String
|
||||
function: String
|
||||
}
|
||||
|
||||
type Human implements Character {
|
||||
name: String
|
||||
bornIn: String
|
||||
}
|
||||
```
|
|
@ -1,43 +0,0 @@
|
|||
---
|
||||
title: Middleware
|
||||
description: Walkthrough Middleware
|
||||
---
|
||||
|
||||
# Middleware
|
||||
|
||||
You can use _middleware_ to affect the evaluation of fields in your schema.
|
||||
|
||||
A middleware is any object that responds to `resolve(*args, next_middleware)`. Inside that method, it should either:
|
||||
|
||||
* Send `resolve` to the next middleware to continue the evaluation; or
|
||||
* Return a value to end the evaluation early.
|
||||
|
||||
Middlewares' `resolve` is invoked with several arguments:
|
||||
|
||||
* `next` represents the execution chain. Call `next` to continue evalution.
|
||||
* `root` is the root value object passed throughout the query
|
||||
* `args` is the hash of arguments passed to the field
|
||||
* `context` is the context object passed throughout the query
|
||||
* `info` is the resolver info
|
||||
|
||||
Add a middleware to a schema by adding to the `middlewares` list.
|
||||
|
||||
|
||||
### Example: Authorization
|
||||
|
||||
This middleware only continues evaluation if the `field_name` is not `'user'`:
|
||||
|
||||
```python
|
||||
class AuthorizationMiddleware(object):
|
||||
|
||||
def resolve(self, next, root, args, context, info):
|
||||
if info.field_name == 'user':
|
||||
return None
|
||||
return next(root, args, context, info)
|
||||
```
|
||||
|
||||
Then, add the middleware to your schema:
|
||||
|
||||
```python
|
||||
schema = Schema(middlewares=[AuthorizationMiddleware])
|
||||
```
|
|
@ -1,75 +0,0 @@
|
|||
---
|
||||
title: Mutations
|
||||
description: Walkthrough Mutations
|
||||
---
|
||||
|
||||
# Mutations
|
||||
|
||||
A Mutation is a special ObjectType that also defines an Input.
|
||||
|
||||
## Quick example
|
||||
|
||||
This example defines a Mutation:
|
||||
|
||||
```python
|
||||
import graphene
|
||||
|
||||
class CreatePerson(graphene.Mutation):
|
||||
class Input:
|
||||
name = graphene.String()
|
||||
|
||||
ok = graphene.Boolean()
|
||||
person = graphene.Field('Person')
|
||||
|
||||
@classmethod
|
||||
def mutate(cls, instance, args, info):
|
||||
person = Person(name=args.get('name'))
|
||||
ok = True
|
||||
return CreatePerson(person=person, ok=ok)
|
||||
```
|
||||
|
||||
**person** and **ok** are the output fields of the Mutation when is resolved.
|
||||
|
||||
**Input** attributes are the arguments that the Mutation `CreatePerson` needs for resolving, in this case **name** will be the only argument for the mutation.
|
||||
|
||||
**mutate** is the function that will be applied once the mutation is called.
|
||||
|
||||
So, we can finish our schema like this:
|
||||
|
||||
```python
|
||||
# ... the Mutation Class
|
||||
|
||||
class Person(graphene.ObjectType):
|
||||
name = graphene.String()
|
||||
|
||||
class MyMutations(graphene.ObjectType):
|
||||
create_person = graphene.Field(CreatePerson)
|
||||
|
||||
schema = graphene.Schema(mutation=MyMutations)
|
||||
```
|
||||
|
||||
## Executing the Mutation
|
||||
|
||||
Then, if we query (`schema.execute(query_str)`) the following:
|
||||
```graphql
|
||||
mutation myFirstMutation {
|
||||
createPerson(name:"Peter") {
|
||||
person {
|
||||
name
|
||||
}
|
||||
ok
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We should receive:
|
||||
|
||||
```json
|
||||
{
|
||||
"createPerson": {
|
||||
"person" : {
|
||||
name: "Peter"
|
||||
},
|
||||
"ok": true
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
---
|
||||
title: ObjectTypes
|
||||
description: Walkthrough ObjectTypes
|
||||
---
|
||||
|
||||
# ObjectTypes
|
||||
|
||||
An ObjectType is the single, definitive source of information about your data. It contains the essential fields and behaviors of the data you’re querying.
|
||||
|
||||
The basics:
|
||||
- Each ObjectType is a Python class that inherits `graphene.ObjectType` or inherits an implemented [Interface](/docs/interfaces/).
|
||||
- Each attribute of the ObjectType represents a GraphQL field.
|
||||
|
||||
## Quick example
|
||||
|
||||
This example model defines a Person, which has a first_name and last_name:
|
||||
|
||||
```python
|
||||
import graphene
|
||||
|
||||
class Person(graphene.ObjectType):
|
||||
first_name = graphene.String()
|
||||
last_name = graphene.String()
|
||||
full_name = graphene.String()
|
||||
|
||||
def resolve_full_name(self, args, info):
|
||||
return '{} {}'.format(self.first_name, self.last_name)
|
||||
```
|
||||
|
||||
**first_name** and **last_name** are fields of the ObjectType. Each field is specified as a class attribute, and each attribute maps to a GraphQL field.
|
||||
|
||||
The above `Person` ObjectType would have the following representation in a schema:
|
||||
|
||||
```graphql
|
||||
type Person {
|
||||
firstName: String
|
||||
lastName: String
|
||||
fullName: String
|
||||
}
|
||||
```
|
||||
|
||||
## Instances as containers
|
||||
|
||||
Graphene `ObjectType`s could act as containers too.
|
||||
So with the previous example you could do.
|
||||
|
||||
```python
|
||||
peter = Person(first_name='Peter', last_name='Griffin')
|
||||
```
|
|
@ -1,52 +0,0 @@
|
|||
---
|
||||
title: Getting started
|
||||
description: A Quick guide to Graphene
|
||||
---
|
||||
|
||||
# Getting started
|
||||
|
||||
Let's build a basic GraphQL schema from scratch.
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python (2.6.5+, 2.7, 3.2, 3.3, 3.4, 3.5, pypy)
|
||||
- Graphene (0.4+)
|
||||
|
||||
|
||||
## Project setup
|
||||
|
||||
```bash
|
||||
pip install graphene
|
||||
```
|
||||
|
||||
## Creating a basic Schema
|
||||
|
||||
A GraphQL schema describes your data model, and provides a GraphQL server with an associated set of resolve methods that know how to fetch data.
|
||||
|
||||
We are going to create a very simple schema, with a `Query` with only one field: `hello`. And when we query it should return `"World"`.
|
||||
|
||||
|
||||
```python
|
||||
import graphene
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
hello = graphene.String()
|
||||
|
||||
def resolve_hello(self, args, info):
|
||||
return 'World'
|
||||
|
||||
schema = graphene.Schema(query=Query)
|
||||
```
|
||||
|
||||
|
||||
## Querying
|
||||
|
||||
Then, we can start querying our schema:
|
||||
|
||||
```python
|
||||
result = schema.execute('{ hello }')
|
||||
print result.data['hello'] # "World"
|
||||
```
|
||||
|
||||
Congrats! You got your first graphene schema working!
|
|
@ -1,85 +0,0 @@
|
|||
---
|
||||
title: Relay
|
||||
description: A Relay implementation in Graphene
|
||||
---
|
||||
|
||||
# Relay
|
||||
|
||||
Graphene has complete support for [Relay](https://facebook.github.io/relay/docs/graphql-relay-specification.html) and offers some utils to make integration from Python easy.
|
||||
|
||||
## Nodes
|
||||
|
||||
A `Node` is an Interface provided by `graphene.relay` that contains a single field `id` (which is a `ID!`). Any object that inherits from it have to implement a `get_node` method for retrieving a `Node` by an *id*.
|
||||
|
||||
Example usage (taken from the [Starwars Relay example](https://github.com/graphql-python/graphene/blob/master/examples/starwars_relay/schema.py)):
|
||||
|
||||
```python
|
||||
class Ship(relay.Node):
|
||||
'''A ship in the Star Wars saga'''
|
||||
name = graphene.String(description='The name of the ship.')
|
||||
|
||||
@classmethod
|
||||
def get_node(cls, id, info):
|
||||
return get_ship(id)
|
||||
```
|
||||
|
||||
The `id` returned by the `Ship` type when you query it will be a scalar which contains the enough info for the server for knowing it's type and it's id.
|
||||
|
||||
For example, the instance `Ship(id=1)` will return `U2hpcDox` as the id when you query it (which is the base64 encoding of `Ship:1`), and which could be useful later if we want to query a node by its id.
|
||||
|
||||
|
||||
## Connection
|
||||
|
||||
A connection is a vitaminized version of a List that provides ways of slicing and paginating through it. The way you create Connection fields in `graphene` is using `relay.ConnectionField`.
|
||||
|
||||
You can create connection fields in any ObjectType, but the connection **must** be linked to an object which inherits from `Node` (in this case, a `Ship`).
|
||||
|
||||
```python
|
||||
class Faction(graphene.ObjectType):
|
||||
name = graphene.String()
|
||||
ships = relay.ConnectionField(Ship)
|
||||
|
||||
def resolve_ships(self, args, info):
|
||||
return []
|
||||
```
|
||||
|
||||
## Node Root field
|
||||
|
||||
As is required in the [Relay specification](https://facebook.github.io/relay/graphql/objectidentification.htm#sec-Node-root-field), the server must implement a root field called `node` that returns a `Node` Interface.
|
||||
|
||||
For this reason, `graphene` provides the field `relay.NodeField`, which links to any type in the Schema which inherits from `Node`. Example usage:
|
||||
|
||||
```python
|
||||
class Query(graphene.ObjectType):
|
||||
node = relay.NodeField()
|
||||
```
|
||||
|
||||
## Mutations
|
||||
|
||||
Most APIs don't just allow you to read data, they also allow you to write. In GraphQL, this is done using mutations. Just like queries, Relay puts some additional requirements on mutations, but Graphene nicely manages that for you. All you need to do is make your mutation a subclass of `relay.ClientIDMutation`.
|
||||
|
||||
```python
|
||||
class IntroduceShip(relay.ClientIDMutation):
|
||||
|
||||
class Input:
|
||||
ship_name = graphene.String(required=True)
|
||||
faction_id = graphene.String(required=True)
|
||||
|
||||
ship = graphene.Field(Ship)
|
||||
faction = graphene.Field(Faction)
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, input, info):
|
||||
ship_name = input.get('ship_name')
|
||||
faction_id = input.get('faction_id')
|
||||
ship = create_ship(ship_name, faction_id)
|
||||
faction = get_faction(faction_id)
|
||||
return IntroduceShip(ship=ship, faction=faction)
|
||||
```
|
||||
|
||||
## Useful links
|
||||
|
||||
* [Getting started with Relay](https://facebook.github.io/relay/docs/graphql-relay-specification.html)
|
||||
* [Relay Global Identification Specification](https://facebook.github.io/relay/graphql/objectidentification.htm)
|
||||
* [Relay Cursor Connection Specification](https://facebook.github.io/relay/graphql/connections.htm)
|
||||
* [Relay input Object Mutation](https://facebook.github.io/relay/graphql/mutations.htm)
|
|
@ -1,62 +0,0 @@
|
|||
---
|
||||
title: Resolvers
|
||||
description: Walkthrough Resolvers
|
||||
---
|
||||
|
||||
# Resolvers
|
||||
|
||||
A resolver is a method that resolves certain field within a `ObjectType`.
|
||||
The resolver of a field will be, if not specified otherwise, the `resolve_{field_name}` within the `ObjectType`.
|
||||
|
||||
By default a resolver will take the `args`, and `info` arguments.
|
||||
*This is likely to be simplified in the future*.
|
||||
|
||||
|
||||
## Quick example
|
||||
|
||||
This example model defines a `Query` type, which has a reverse field that reverses the given `word`
|
||||
argument using the `resolve_reverse` method in the class.
|
||||
|
||||
```python
|
||||
import graphene
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
reverse = graphene.String(word=graphene.String())
|
||||
|
||||
def resolve_reverse(self, args, info):
|
||||
word = args.get('word')
|
||||
return word[::-1]
|
||||
```
|
||||
|
||||
## Resolvers outside the class
|
||||
|
||||
A field could also specify a custom resolver outside the class:
|
||||
|
||||
```python
|
||||
import graphene
|
||||
|
||||
def reverse(root, args, info):
|
||||
word = args.get('word')
|
||||
return word[::-1]
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
reverse = graphene.String(word=graphene.String(), resolver=reverse)
|
||||
```
|
||||
|
||||
|
||||
## Context
|
||||
|
||||
A query in a GraphQL schema could have some context that we can use in any resolver.
|
||||
In this case we need to decorate the resolver function with `with_context`.
|
||||
|
||||
```python
|
||||
class Query(graphene.ObjectType):
|
||||
name = graphene.String()
|
||||
|
||||
@with_context
|
||||
def resolve_name(self, args, context, info):
|
||||
return context['name']
|
||||
|
||||
|
||||
result = schema.execute(query, context_value={'name': 'Peter'})
|
||||
```
|
|
@ -1,30 +0,0 @@
|
|||
---
|
||||
title: Tips
|
||||
description: Tips when SQLAlchemy in Graphene
|
||||
---
|
||||
|
||||
# Tips
|
||||
|
||||
## Querying
|
||||
|
||||
For make querying to the database work, there are two alternatives:
|
||||
|
||||
* Expose the db session when you create the `graphene.Schema`:
|
||||
|
||||
```python
|
||||
schema = graphene.Schema(session=session)
|
||||
```
|
||||
|
||||
* Create a query for the models.
|
||||
|
||||
```python
|
||||
Base = declarative_base()
|
||||
Base.query = db_session.query_property()
|
||||
|
||||
class MyModel(Base):
|
||||
# ...
|
||||
```
|
||||
|
||||
If you don't specify any, the following error will be displayed:
|
||||
|
||||
`A query in the model Base or a session in the schema is required for querying.`
|
|
@ -1,199 +0,0 @@
|
|||
---
|
||||
title: Tutorial
|
||||
description: Using SQLAlchemy with Graphene
|
||||
---
|
||||
|
||||
# SQLAlchemy + Flask Tutorial
|
||||
|
||||
Graphene comes with builtin support to SQLAlchemy, which makes quite easy to operate with your current models.
|
||||
|
||||
**Note: The code in this tutorial is pulled from the
|
||||
[Flask SQLAlchemy example app](https://github.com/graphql-python/graphene/tree/master/examples/flask_sqlalchemy)**.
|
||||
|
||||
|
||||
## Setup the Project
|
||||
|
||||
We will setup the project, execute the following:
|
||||
|
||||
```bash
|
||||
# Create the project directory
|
||||
mkdir flask_sqlalchemy
|
||||
cd flask_sqlalchemy
|
||||
|
||||
# Create a virtualenv to isolate our package dependencies locally
|
||||
virtualenv env
|
||||
source env/bin/activate # On Windows use `env\Scripts\activate`
|
||||
|
||||
# SQLAlchemy and Graphene with SQLAlchemy support
|
||||
pip install SQLAlchemy
|
||||
pip install graphene[sqlalchemy]
|
||||
|
||||
# Install Flask and GraphQL Flask for exposing the schema through HTTP
|
||||
pip install Flask
|
||||
pip install Flask-GraphQL
|
||||
```
|
||||
|
||||
## Defining our models
|
||||
|
||||
Let's get started with these models:
|
||||
|
||||
```python
|
||||
# flask_sqlalchemy/models.py
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import (scoped_session, sessionmaker, relationship,
|
||||
backref)
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
engine = create_engine('sqlite:///database.sqlite3', convert_unicode=True)
|
||||
db_session = scoped_session(sessionmaker(autocommit=False,
|
||||
autoflush=False,
|
||||
bind=engine))
|
||||
|
||||
Base = declarative_base()
|
||||
# We will need this for querying
|
||||
Base.query = db_session.query_property()
|
||||
|
||||
|
||||
class Department(Base):
|
||||
__tablename__ = 'department'
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
|
||||
|
||||
class Employee(Base):
|
||||
__tablename__ = 'employee'
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
hired_on = Column(DateTime, default=func.now())
|
||||
department_id = Column(Integer, ForeignKey('department.id'))
|
||||
department = relationship(
|
||||
Department,
|
||||
backref=backref('employees',
|
||||
uselist=True,
|
||||
cascade='delete,all'))
|
||||
```
|
||||
|
||||
## Schema
|
||||
|
||||
GraphQL presents your objects to the world as a graph structure rather than a more
|
||||
hierarchical structure to which you may be accustomed. In order to create this
|
||||
representation, Graphene needs to know about each *type* of object which will appear in
|
||||
the graph.
|
||||
|
||||
This graph also has a *root type* through which all access begins. This is the `Query` class below.
|
||||
In this example, we provide the ability to list all employees via `all_employees`, and the
|
||||
ability to obtain a specific node via `node`.
|
||||
|
||||
Create `flask_sqlalchemy/schema.py` and type the following:
|
||||
|
||||
```python
|
||||
# flask_sqlalchemy/schema.py
|
||||
import graphene
|
||||
from graphene import relay
|
||||
from graphene.contrib.sqlalchemy import SQLAlchemyNode, SQLAlchemyConnectionField
|
||||
from models import db_session, Department as DepartmentModel, Employee as EmployeeModel
|
||||
|
||||
schema = graphene.Schema()
|
||||
|
||||
|
||||
@schema.register
|
||||
class Department(SQLAlchemyNode):
|
||||
class Meta:
|
||||
model = DepartmentModel
|
||||
|
||||
|
||||
@schema.register
|
||||
class Employee(SQLAlchemyNode):
|
||||
class Meta:
|
||||
model = EmployeeModel
|
||||
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
node = relay.NodeField()
|
||||
all_employees = SQLAlchemyConnectionField(Employee)
|
||||
|
||||
schema.query = Query
|
||||
```
|
||||
|
||||
## Creating GraphQL and GraphiQL views in Flask
|
||||
|
||||
Unlike a RESTful API, there is only a single URL from which GraphQL is accessed.
|
||||
|
||||
We are going to use Flask to create a server that expose the GraphQL schema under `/graphql` and a interface for querying it easily: GraphiQL under `/graphiql`.
|
||||
|
||||
Fortunately for us, the library `Flask-GraphQL` that we previously installed makes this task quite easy.
|
||||
|
||||
```python
|
||||
# flask_sqlalchemy/app.py
|
||||
from flask import Flask
|
||||
from flask_graphql import GraphQL
|
||||
|
||||
from models import db_session
|
||||
from schema import schema, Department
|
||||
|
||||
app = Flask(__name__)
|
||||
app.debug = True
|
||||
|
||||
# This is creating the `/graphql` and `/graphiql` endpoints
|
||||
GraphQL(app, schema=schema)
|
||||
|
||||
@app.teardown_appcontext
|
||||
def shutdown_session(exception=None):
|
||||
db_session.remove()
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
||||
```
|
||||
|
||||
|
||||
## Creating some data
|
||||
|
||||
```bash
|
||||
$ python
|
||||
|
||||
>>> from models import engine, db_session, Base, Department, Employee
|
||||
>>> Base.metadata.create_all(bind=engine)
|
||||
|
||||
>>> # Fill the tables with some data
|
||||
>>> engineering = Department(name='Engineering')
|
||||
>>> db_session.add(engineering)
|
||||
>>> hr = Department(name='Human Resources')
|
||||
>>> db_session.add(hr)
|
||||
|
||||
>>> peter = Employee(name='Peter', department=engineering)
|
||||
>>> db_session.add(peter)
|
||||
>>> roy = Employee(name='Roy', department=engineering)
|
||||
>>> db_session.add(roy)
|
||||
>>> tracy = Employee(name='Tracy', department=hr)
|
||||
>>> db_session.add(tracy)
|
||||
>>> db_session.commit()
|
||||
```
|
||||
|
||||
|
||||
## Testing our GraphQL schema
|
||||
|
||||
We're now ready to test the API we've built. Let's fire up the server from the command line.
|
||||
|
||||
```bash
|
||||
$ python ./app.py
|
||||
|
||||
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
Go to [localhost:5000/graphiql](http://localhost:5000/graphiql) and type your first query!
|
||||
|
||||
```graphql
|
||||
{
|
||||
allEmployees {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
department {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
|
@ -1,10 +0,0 @@
|
|||
---
|
||||
path: /
|
||||
---
|
||||
<div class="starwars-example-wrapper"><a class="starwars-example" href="http://swapi.graphene-python.org/">Check our Django Starwars API example!</a></div>
|
||||
|
||||
## Meet Graphene
|
||||
|
||||
Graphene is a Python library for building GraphQL schemas/types fast and easily.
|
||||
|
||||
**But, what is GraphQL?** A GraphQL query is a string interpreted by a server that returns data in a specified format. We believe *GraphQL* is the next big thing after peanut butter and *REST*.
|
|
@ -1,13 +0,0 @@
|
|||
import React from 'react';
|
||||
import DocumentTitle from 'react-document-title';
|
||||
import PlaygroundWrapper from 'playground-wrapper';
|
||||
|
||||
class Playground extends React.Component {
|
||||
render() {
|
||||
return <DocumentTitle title="Playground - Graphene">
|
||||
<PlaygroundWrapper />
|
||||
</DocumentTitle>;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Playground;
|
|
@ -1,9 +0,0 @@
|
|||
// React patched version for render in server side always with same ids
|
||||
if (typeof window === "undefined") {
|
||||
var ServerReactRootIndex = require('react/lib/ServerReactRootIndex');
|
||||
ServerReactRootIndex.createReactRootIndex = function(){
|
||||
return "graphene";
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = require('original-react');
|
|
@ -1,234 +0,0 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { RouteHandler, Link, State } from 'react-router';
|
||||
import CodeMirror from 'codemirror';
|
||||
import { graphql } from 'graphql';
|
||||
import GraphiQL from 'graphiql';
|
||||
import schema from './schema';
|
||||
import pypyjs_vm from 'pypyjs';
|
||||
|
||||
import 'codemirror/mode/python/python';
|
||||
import 'codemirror/addon/lint/lint';
|
||||
import '../css/playground.styl';
|
||||
|
||||
if (typeof PUBLIC_PATH === "undefined") {
|
||||
var PUBLIC_PATH = '';
|
||||
}
|
||||
|
||||
pypyjs_vm.rootURL = `${PUBLIC_PATH}/playground/lib/`;
|
||||
pypyjs_vm.cacheKey = 'graphene';
|
||||
|
||||
CodeMirror.registerHelper('lint', 'python', function (text, options, editor) {
|
||||
return (options.errors || []).map((error) => {
|
||||
var tokens = editor.getLineTokens(error.line - 1);
|
||||
tokens = tokens.filter((token, pos) => {
|
||||
return !!token.type || token.string.trim().length > 0;
|
||||
});
|
||||
if (!tokens) return [];
|
||||
return {
|
||||
message: `${error.name}: ${error.message}`,
|
||||
severity: 'error',
|
||||
type: 'syntax',
|
||||
from: CodeMirror.Pos(error.line - 1, tokens[0].start),
|
||||
to: CodeMirror.Pos(error.line - 1, tokens[tokens.length-1].end),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
function graphQLFetcher(graphQLParams) {
|
||||
return graphql(schema, graphQLParams.query);
|
||||
}
|
||||
|
||||
var default_interpreter;
|
||||
class Playground extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {pypyjs: false, stdout: '', response:''};
|
||||
}
|
||||
stdout() {
|
||||
console.log('stdout', arguments);
|
||||
}
|
||||
componentDidMount() {
|
||||
if (default_interpreter) {
|
||||
this.pypy_interpreter = default_interpreter;
|
||||
this.pypy_interpreter.stdout = this.stdout.bind(this);
|
||||
}
|
||||
else {
|
||||
this.pypy_interpreter = new pypyjs_vm({
|
||||
stdin: function(){},
|
||||
stdout: this.stdout.bind(this),
|
||||
stderr: function(){},
|
||||
rootURL: `${PUBLIC_PATH}/playground/lib/`
|
||||
});
|
||||
default_interpreter = this.pypy_interpreter;
|
||||
}
|
||||
|
||||
this.pypyjs = this.pypy_interpreter.ready().then(() => {
|
||||
return this.pypy_interpreter.exec(`
|
||||
import graphene
|
||||
import js
|
||||
from collections import OrderedDict
|
||||
from graphql.core.execution.executor import Executor
|
||||
from graphql.core.execution.middlewares.sync import SynchronousExecutionMiddleware
|
||||
from graphql.core.error import GraphQLError, format_error
|
||||
|
||||
def get_wrapped(f):
|
||||
if hasattr(f, 'func_closure') and f.func_closure:
|
||||
return get_wrapped(f.func_closure[0].cell_contents)
|
||||
return f
|
||||
|
||||
class TrackResolver(SynchronousExecutionMiddleware):
|
||||
@staticmethod
|
||||
def run_resolve_fn(resolver, original_resolver):
|
||||
if resolver.func.__module__ == '__main__':
|
||||
line = get_wrapped(resolver.func).resolver.func_code.co_firstlineno
|
||||
js.globals.markLine(line-3)
|
||||
return SynchronousExecutionMiddleware.run_resolve_fn(resolver, original_resolver)
|
||||
|
||||
__graphene_executor = Executor([TrackResolver()], map_type=OrderedDict)
|
||||
`);
|
||||
}).then(() => {
|
||||
this.createSchema(this.props.initialSchema);
|
||||
}).then(() => {
|
||||
this.setState({pypyjs: true, response:'"Execute the query for see the results"'});
|
||||
});
|
||||
|
||||
window.markLine = (lineNo) => {
|
||||
this.markLine(lineNo);
|
||||
}
|
||||
|
||||
this.editor = CodeMirror(ReactDOM.findDOMNode(this.refs.schemaCode), {
|
||||
value: this.props.initialSchema,
|
||||
mode: "python",
|
||||
theme: "graphene",
|
||||
lineNumbers: true,
|
||||
tabSize: 4,
|
||||
indentUnit: 4,
|
||||
gutters: ["CodeMirror-linenumbers", "breakpoints"],
|
||||
lint: {
|
||||
errors: [],
|
||||
},
|
||||
});
|
||||
this.editor.on("change", this.onEditorChange.bind(this));
|
||||
}
|
||||
onEditorChange() {
|
||||
if (this.changeTimeout) {
|
||||
clearTimeout(this.changeTimeout);
|
||||
}
|
||||
if (this.props.onEditSchema) {
|
||||
var value = this.editor.getValue();
|
||||
if (value != this.props.initialSchema) {
|
||||
this.props.onEditSchema(value)
|
||||
}
|
||||
}
|
||||
|
||||
this.changeTimeout = setTimeout(() =>
|
||||
this.updateSchema()
|
||||
, 300);
|
||||
}
|
||||
updateSchema() {
|
||||
this.createSchema(this.editor.getValue());
|
||||
}
|
||||
createSchema(code) {
|
||||
if (this.previousCode == code) return;
|
||||
console.log('createSchema');
|
||||
this.validSchema = null;
|
||||
this.pypyjs.then(() => {
|
||||
return this.pypy_interpreter.exec(`
|
||||
schema = None
|
||||
${code}
|
||||
assert schema, 'You have to define a schema'
|
||||
`)
|
||||
}).then(() => {
|
||||
console.log('NO ERRORS');
|
||||
this.removeErrors();
|
||||
this.validSchema = true;
|
||||
}, (err) => {
|
||||
this.editor.options.lint.errors = [];
|
||||
console.log('ERRORS', err);
|
||||
this.logError(err);
|
||||
this.validSchema = false;
|
||||
}).then(this.updateGraphiQL.bind(this));
|
||||
this.previousCode = code;
|
||||
}
|
||||
updateGraphiQL() {
|
||||
if (this.validSchema) {
|
||||
this.refs.graphiql.state.schema = null;
|
||||
this.refs.graphiql.componentDidMount();
|
||||
this.refs.graphiql.forceUpdate();
|
||||
this.refs.graphiql.refs.docExplorer.forceUpdate();
|
||||
}
|
||||
}
|
||||
logError(error) {
|
||||
var lines = error.trace.split('\n');
|
||||
var file_errors = lines.map((errorLine) => {
|
||||
return errorLine.match(/File "<string>", line (\d+)/);
|
||||
}).filter((x) => !! x);
|
||||
if (!file_errors.length) return;
|
||||
var line = parseInt(file_errors[file_errors.length-1][1]);
|
||||
error.line = line-2;
|
||||
if (error.name == "ImportError" && error.message == "No module named django") {
|
||||
error.message = "Django is not supported yet in Playground editor";
|
||||
}
|
||||
this.editor.options.lint.errors.push(error);
|
||||
CodeMirror.signal(this.editor, 'change', this.editor);
|
||||
}
|
||||
removeErrors() {
|
||||
this.editor.options.lint.errors = [];
|
||||
CodeMirror.signal(this.editor, 'change', this.editor);
|
||||
}
|
||||
fetcher (graphQLParams) {
|
||||
if (!this.validSchema) {
|
||||
return graphQLFetcher(arguments);
|
||||
}
|
||||
return this.execute(graphQLParams.query);
|
||||
}
|
||||
execute(query) {
|
||||
// console.log('execute', query);
|
||||
return this.pypyjs.then(() => {
|
||||
var x = `
|
||||
import json
|
||||
result = __graphene_executor.execute(schema.schema, '''${query}''')
|
||||
result_dict = {};
|
||||
if result.errors:
|
||||
result_dict['errors'] = [format_error(e) for e in result.errors]
|
||||
if result.data:
|
||||
result_dict['data'] = result.data
|
||||
result_json = json.dumps(result_dict)
|
||||
`;
|
||||
return this.pypy_interpreter.exec(x)
|
||||
}
|
||||
).then(() =>
|
||||
this.pypy_interpreter.get(`result_json`)
|
||||
).then((data) => {
|
||||
var json_data = JSON.parse(data);
|
||||
return json_data;
|
||||
});
|
||||
}
|
||||
markLine(lineNo) {
|
||||
console.log(lineNo);
|
||||
var hlLine = this.editor.addLineClass(lineNo, "text", "activeline");
|
||||
// var mark = this.editor.markText({line: lineNo, ch: 0}, {line: lineNo, ch: 10}, {className: "called-function"});
|
||||
setTimeout(() => {
|
||||
this.editor.removeLineClass(lineNo, "text", "activeline");
|
||||
}, 1200);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div className="playground">
|
||||
{!this.state.pypyjs?<div className="loading" />:null}
|
||||
<div className="playground-schema">
|
||||
<header className="playground-schema-header">
|
||||
Schema
|
||||
</header>
|
||||
<div className="playground-schema-editor" ref="schemaCode" />
|
||||
</div>
|
||||
<div className="playground-graphiql">
|
||||
<GraphiQL ref="graphiql" fetcher={this.fetcher.bind(this)} response={this.state.response} onEditQuery={this.props.onEditQuery} query={this.props.initialQuery}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Playground;
|
|
@ -1,4 +0,0 @@
|
|||
query {
|
||||
hello
|
||||
ping(to:"Peter")
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
import graphene
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
hello = graphene.String()
|
||||
ping = graphene.String(to=graphene.String())
|
||||
|
||||
def resolve_hello(self, args, info):
|
||||
return 'World'
|
||||
|
||||
def resolve_ping(self, args, info):
|
||||
return 'Pinging {}'.format(args.get('to'))
|
||||
|
||||
schema = graphene.Schema(query=Query)
|
|
@ -1,20 +0,0 @@
|
|||
query {
|
||||
empire {
|
||||
id
|
||||
name
|
||||
ships(first:2) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
node(id:"U2hpcDo4") {
|
||||
id
|
||||
... on Ship {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
import graphene
|
||||
from graphene import relay, resolve_only_args
|
||||
|
||||
class Ship(relay.Node):
|
||||
'''A ship in the Star Wars saga'''
|
||||
name = graphene.String(description='The name of the ship.')
|
||||
|
||||
@classmethod
|
||||
def get_node(cls, id, info):
|
||||
return get_ship(id)
|
||||
|
||||
class Faction(relay.Node):
|
||||
'''A faction in the Star Wars saga'''
|
||||
name = graphene.String(description='The name of the faction.')
|
||||
ships = relay.ConnectionField(
|
||||
Ship, description='The ships used by the faction.')
|
||||
|
||||
@resolve_only_args
|
||||
def resolve_ships(self, **args):
|
||||
# Transform the instance ship_ids into real instances
|
||||
return [get_ship(ship_id) for ship_id in self.ships]
|
||||
|
||||
@classmethod
|
||||
def get_node(cls, id, info):
|
||||
return get_faction(id)
|
||||
|
||||
class IntroduceShip(relay.ClientIDMutation):
|
||||
class Input:
|
||||
ship_name = graphene.String(required=True)
|
||||
faction_id = graphene.String(required=True)
|
||||
|
||||
ship = graphene.Field(Ship)
|
||||
faction = graphene.Field(Faction)
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, input, info):
|
||||
ship_name = input.get('ship_name')
|
||||
faction_id = input.get('faction_id')
|
||||
ship = create_ship(ship_name, faction_id)
|
||||
faction = get_faction(faction_id)
|
||||
return IntroduceShip(ship=ship, faction=faction)
|
||||
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
rebels = graphene.Field(Faction)
|
||||
empire = graphene.Field(Faction)
|
||||
node = relay.NodeField()
|
||||
|
||||
@resolve_only_args
|
||||
def resolve_rebels(self):
|
||||
return get_rebels()
|
||||
|
||||
@resolve_only_args
|
||||
def resolve_empire(self):
|
||||
return get_empire()
|
||||
|
||||
|
||||
class Mutation(graphene.ObjectType):
|
||||
introduce_ship = graphene.Field(IntroduceShip)
|
||||
|
||||
schema = graphene.Schema(name='Starwars Relay Schema')
|
||||
schema.query = Query
|
||||
schema.mutation = Mutation
|
||||
|
||||
xwing = Ship(
|
||||
id='1',
|
||||
name='X-Wing',
|
||||
)
|
||||
ywing = Ship(
|
||||
id='2',
|
||||
name='Y-Wing',
|
||||
)
|
||||
awing = Ship(
|
||||
id='3',
|
||||
name='A-Wing',
|
||||
)
|
||||
|
||||
# Yeah, technically it's Corellian. But it flew in the service of the rebels,
|
||||
# so for the purposes of this demo it's a rebel ship.
|
||||
falcon = Ship(
|
||||
id='4',
|
||||
name='Millenium Falcon',
|
||||
)
|
||||
homeOne = Ship(
|
||||
id='5',
|
||||
name='Home One',
|
||||
)
|
||||
tieFighter = Ship(
|
||||
id='6',
|
||||
name='TIE Fighter',
|
||||
)
|
||||
tieInterceptor = Ship(
|
||||
id='7',
|
||||
name='TIE Interceptor',
|
||||
)
|
||||
executor = Ship(
|
||||
id='8',
|
||||
name='Executor',
|
||||
)
|
||||
rebels = Faction(
|
||||
id='1',
|
||||
name='Alliance to Restore the Republic',
|
||||
ships=['1', '2', '3', '4', '5']
|
||||
)
|
||||
empire = Faction(
|
||||
id='2',
|
||||
name='Galactic Empire',
|
||||
ships=['6', '7', '8']
|
||||
)
|
||||
data = {
|
||||
'Faction': {
|
||||
'1': rebels,
|
||||
'2': empire
|
||||
},
|
||||
'Ship': {
|
||||
'1': xwing,
|
||||
'2': ywing,
|
||||
'3': awing,
|
||||
'4': falcon,
|
||||
'5': homeOne,
|
||||
'6': tieFighter,
|
||||
'7': tieInterceptor,
|
||||
'8': executor
|
||||
}
|
||||
}
|
||||
|
||||
def create_ship(ship_name, faction_id):
|
||||
from .schema import Ship
|
||||
next_ship = len(data['Ship'].keys()) + 1
|
||||
new_ship = Ship(
|
||||
id=str(next_ship),
|
||||
name=ship_name
|
||||
)
|
||||
data['Ship'][new_ship.id] = new_ship
|
||||
data['Faction'][faction_id].ships.append(new_ship.id)
|
||||
return new_ship
|
||||
|
||||
def get_ship(_id):
|
||||
return data['Ship'][_id]
|
||||
|
||||
def get_faction(_id):
|
||||
return data['Faction'][_id]
|
||||
|
||||
def get_rebels():
|
||||
return get_faction('1')
|
||||
|
||||
def get_empire():
|
||||
return get_faction('2')
|
|
@ -1,8 +0,0 @@
|
|||
query {
|
||||
store {
|
||||
teas(orderBy:"name") {
|
||||
name
|
||||
steepingTime
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
import graphene
|
||||
|
||||
class Tea(graphene.ObjectType):
|
||||
name = graphene.String()
|
||||
steeping_time = graphene.Int()
|
||||
|
||||
TEAS = [
|
||||
Tea(name='Earl Grey Blue Star', steeping_time=5),
|
||||
Tea(name='Milk Oolong', steeping_time=3),
|
||||
Tea(name='Gunpowder Golden Temple', steeping_time=3),
|
||||
Tea(name='Assam Hatimara', steeping_time=5),
|
||||
Tea(name='Bancha', steeping_time=2),
|
||||
Tea(name='Ceylon New Vithanakande', steeping_time=5),
|
||||
Tea(name='Golden Tip Yunnan', steeping_time=5),
|
||||
Tea(name='Jasmine Phoenix Pearls', steeping_time=3),
|
||||
Tea(name='Kenya Milima', steeping_time=5),
|
||||
Tea(name='Pu Erh First Grade', steeping_time=4),
|
||||
Tea(name='Sencha Makoto', steeping_time=3),
|
||||
]
|
||||
|
||||
class Store(graphene.ObjectType):
|
||||
teas = graphene.List(Tea, order_by=graphene.String())
|
||||
|
||||
def resolve_teas(self, args, info):
|
||||
order_by = args.get("order_by")
|
||||
if order_by == "steepingTime":
|
||||
return sorted(self.teas, key=lambda tea: tea.steeping_time)
|
||||
elif order_by == "name":
|
||||
return sorted(self.teas, key=lambda tea: tea.name)
|
||||
return self.teas
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
store = graphene.Field(Store)
|
||||
|
||||
def resolve_store(self, args, info):
|
||||
return Store(teas=TEAS)
|
||||
|
||||
schema = graphene.Schema(query=Query)
|
1
docs/playground/graphene-js/.gitignore
vendored
1
docs/playground/graphene-js/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
pypy-release-nojit
|
|
@ -1,107 +0,0 @@
|
|||
//
|
||||
// FunctionPromise: possibly-asynchronous function constructor.
|
||||
//
|
||||
// This is a prototype polyfill for a FunctionPromise object as described in:
|
||||
//
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=854627
|
||||
//
|
||||
// Where possible it will arrange for the function body to be parsed/compiled
|
||||
// off of the main thread, with the function object returned asynchronously
|
||||
// via a promise. The fallback implementation processes just falls back to
|
||||
// the standard synchronous Function() constructor.
|
||||
//
|
||||
// It doesn't (yet) have the following features from the linked proposal:
|
||||
//
|
||||
// * ability to copy to different workers
|
||||
// * ability to store in IndexedDB
|
||||
//
|
||||
function FunctionPromise(/* [args1[, args2[, ...argN]],], functionBody) */) {
|
||||
|
||||
var useFallback =
|
||||
typeof window === "undefined" ||
|
||||
window.FunctionPromise !== FunctionPromise ||
|
||||
typeof document === "undefined" ||
|
||||
typeof document.createElement === "undefined" ||
|
||||
typeof document.head === "undefined" ||
|
||||
typeof document.head.appendChild === "undefined" ||
|
||||
typeof Blob === "undefined" ||
|
||||
typeof URL === "undefined" ||
|
||||
typeof URL.createObjectURL === "undefined";
|
||||
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
|
||||
// For the fallback case, we just use the normal Function constructor.
|
||||
|
||||
if (useFallback) {
|
||||
try {
|
||||
var fn = Function.apply(null, args);
|
||||
return Promise.resolve(fn);
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
// If we have all the necessary pieces, we can do this asynchronously
|
||||
// by writing a <script> tag into the DOM.
|
||||
|
||||
var funcid = FunctionPromise._nextid++;
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
try {
|
||||
var funcSrc = [];
|
||||
funcSrc.push("window.FunctionPromise._results[" + funcid + "]=");
|
||||
funcSrc.push("function(");
|
||||
if (args.length > 1) {
|
||||
funcSrc.push(args[0]);
|
||||
for (var i = 1; i < args.length - 1; i++) {
|
||||
funcSrc.push(",");
|
||||
funcSrc.push(args[i]);
|
||||
}
|
||||
}
|
||||
funcSrc.push("){");
|
||||
funcSrc.push(args[args.length - 1]);
|
||||
funcSrc.push("}");
|
||||
var dataUrl = URL.createObjectURL(new Blob(funcSrc));
|
||||
var scriptTag = document.createElement("script");
|
||||
var cleanup = function() {
|
||||
URL.revokeObjectURL(dataUrl);
|
||||
scriptTag.remove();
|
||||
delete window.FunctionPromise._results[funcid];
|
||||
}
|
||||
scriptTag.onerror = function() {
|
||||
reject(new Error("unknown error loading FunctionPromise"))
|
||||
cleanup();
|
||||
}
|
||||
scriptTag.onload = function() {
|
||||
if (window.FunctionPromise._results[funcid]) {
|
||||
resolve(window.FunctionPromise._results[funcid]);
|
||||
} else {
|
||||
// No function, something must have gone wrong.
|
||||
// Likely a syntax error in the function body string.
|
||||
// Fall back to Function() constructor to surface it.
|
||||
try {
|
||||
Function.apply(null, args);
|
||||
reject(new Error("unknown error fulfilling FunctionPromise"));
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}
|
||||
cleanup();
|
||||
}
|
||||
scriptTag.src = dataUrl;
|
||||
document.head.appendChild(scriptTag);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
FunctionPromise._nextid = 0;
|
||||
FunctionPromise._results = {};
|
||||
|
||||
if (typeof module !== "undefined" && typeof module.exports !== "undefined") {
|
||||
if (typeof Promise === "undefined") {
|
||||
Promise = require('es6-promise').Promise;
|
||||
}
|
||||
module.exports = FunctionPromise;
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
if [ ! -d pypyjs-release-nojit ] ; then
|
||||
git clone https://github.com/pypyjs/pypyjs-release-nojit.git
|
||||
fi
|
||||
|
||||
GRAPHENE_DIR="$(python -c "import os; import graphene; print os.path.dirname(graphene.__file__)")"
|
||||
GRAPHQL_DIR="$(python -c "import os; import graphql; print os.path.dirname(graphql.__file__)")"
|
||||
GRAPHQL_RELAY_DIR="$(python -c "import os; import graphql_relay; print os.path.dirname(graphql_relay.__file__)")"
|
||||
SIX_DIR="$(python -c "import os; import six; print six.__file__.rstrip('c')")"
|
||||
|
||||
cd pypyjs-release-nojit
|
||||
|
||||
eval python tools/module_bundler.py add ./lib/modules "$GRAPHENE_DIR"
|
||||
eval python tools/module_bundler.py add ./lib/modules "$GRAPHQL_DIR"
|
||||
eval python tools/module_bundler.py add ./lib/modules "$GRAPHQL_RELAY_DIR"
|
||||
eval python tools/module_bundler.py add ./lib/modules "$SIX_DIR"
|
||||
|
||||
python ./tools/module_bundler.py preload ./lib/modules graphene
|
||||
python ./tools/module_bundler.py preload ./lib/modules graphene.relay
|
||||
python ./tools/module_bundler.py preload ./lib/modules graphql
|
||||
python ./tools/module_bundler.py preload ./lib/modules graphql_relay
|
||||
python ./tools/module_bundler.py preload ./lib/modules six
|
||||
|
||||
python ./tools/module_bundler.py remove ./lib/modules unittest
|
||||
|
||||
lib_dirname=`perl -e 'use Cwd "abs_path";print abs_path(shift)' lib/`
|
||||
|
||||
if [ -d ../../../static/playground/lib ] ; then
|
||||
rm ../../../static/playground/lib
|
||||
fi
|
||||
|
||||
mkdir -p ../../../static/playground
|
||||
|
||||
exec ln -s "$lib_dirname/" ../../../static/playground/lib
|
|
@ -1,976 +0,0 @@
|
|||
//
|
||||
// pypyjs: an experimental in-browser python environment.
|
||||
//
|
||||
|
||||
(function() {
|
||||
|
||||
// Expose the main pypyjs function at global scope for this file,
|
||||
// as well as in any module exports or 'window' object we can find.
|
||||
if (this) {
|
||||
this.pypyjs = pypyjs;
|
||||
}
|
||||
if (typeof window !== "undefined") {
|
||||
window.pypyjs = pypyjs;
|
||||
}
|
||||
if (typeof module !== "undefined") {
|
||||
if (typeof module.exports !== "undefined") {
|
||||
module.exports = pypyjs;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Generic debugging printf.
|
||||
var debug = function(){};
|
||||
if (typeof console !== "undefined") {
|
||||
debug = console.log.bind(console);
|
||||
} else if (typeof print !== "undefined" && typeof window === "undefined") {
|
||||
debug = print;
|
||||
}
|
||||
|
||||
|
||||
// Find the directory containing this very file.
|
||||
// It can be quite difficult depending on execution environment...
|
||||
if (typeof __dirname === "undefined" || true) {
|
||||
var __dirname = "./";
|
||||
// A little hackery to find the URL of this very file.
|
||||
// Throw an error, then parse the stack trace looking for filenames.
|
||||
var errlines = (new Error()).stack.split("\n");
|
||||
for (var i = 0; i < errlines.length; i++) {
|
||||
var match = /(at Anonymous function \(|at |@)(.+\/)pypyjs.js/.exec(errlines[i]);
|
||||
if (match) {
|
||||
__dirname = match[2];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (__dirname.charAt(__dirname.length - 1) !== "/") {
|
||||
__dirname += "/";
|
||||
}
|
||||
|
||||
|
||||
if (typeof Promise === "undefined") {
|
||||
var Promise = require('es6-promise').Promise;
|
||||
}
|
||||
|
||||
|
||||
// Ensure we have reference to a 'FunctionPromise' constructor.
|
||||
var FunctionPromise = require("./FunctionPromise.js");
|
||||
|
||||
if (typeof FunctionPromise === "undefined") {
|
||||
throw "FunctionPromise object not found";
|
||||
}
|
||||
|
||||
|
||||
// Create functions for handling default stdio streams.
|
||||
// These will be shared by all VM instances by default.
|
||||
//
|
||||
// We default stdout and stderr to process outputs if available,
|
||||
// printing/logging functions otherwise, and /dev/null if nothing
|
||||
// else is available. Unfortunately there's no good way to read
|
||||
// synchronously from stdin in javascript, so that's always /dev/null.
|
||||
|
||||
var devNull = {
|
||||
stdin: function() { return null; },
|
||||
stdout: function() { },
|
||||
stderr: function() { }
|
||||
}
|
||||
|
||||
var stdio = {
|
||||
stdin: null,
|
||||
stdout: null,
|
||||
stderr: null
|
||||
}
|
||||
|
||||
stdio.stdin = devNull.stdin;
|
||||
|
||||
if (typeof process !== "undefined") {
|
||||
if (typeof process.stdout !== "undefined") {
|
||||
stdio.stdout = function(x) { process.stdout.write(x); }
|
||||
}
|
||||
if (typeof process.stderr !== "undefined") {
|
||||
stdio.stderr = function(x) { process.stderr.write(x); }
|
||||
}
|
||||
}
|
||||
|
||||
var _print, _printErr;
|
||||
if (typeof window === "undefined") {
|
||||
// print, printErr from v8, spidermonkey
|
||||
if (typeof print !== "undefined") {
|
||||
_print = print;
|
||||
}
|
||||
if (typeof printErr !== "undefined") {
|
||||
_printErr = printErr;
|
||||
}
|
||||
}
|
||||
if (typeof console !== "undefined") {
|
||||
if (typeof _print === "undefined") {
|
||||
_print = console.log.bind(console);
|
||||
}
|
||||
if (typeof _printErr === "undefined") {
|
||||
_printErr = console.error.bind(console);
|
||||
}
|
||||
}
|
||||
|
||||
if (stdio.stdout == null && typeof _print !== "undefined") {
|
||||
// print()/console.log() will add a newline, so we buffer until we
|
||||
// receive one and then let it add it for us.
|
||||
stdio.stdout = (function() {
|
||||
var buffer = [];
|
||||
return function(data) {
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var x = data.charAt(i);
|
||||
if (x !== "\n") {
|
||||
buffer.push(x);
|
||||
} else {
|
||||
_print(buffer.join(""));
|
||||
buffer.splice(undefined, buffer.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
if (stdio.stderr == null && typeof _printErr !== "undefined") {
|
||||
// printErr()/console.error() will add a newline, so we buffer until we
|
||||
// receive one and then let it add it for us.
|
||||
stdio.stderr = (function() {
|
||||
var buffer = [];
|
||||
return function(data) {
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var x = data.charAt(i);
|
||||
if (x !== "\n") {
|
||||
buffer.push(x);
|
||||
} else {
|
||||
_printErr(buffer.join(""));
|
||||
buffer.splice(undefined, buffer.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
if (stdio.stdout === null) {
|
||||
stdio.stdout = devNull.stdout;
|
||||
}
|
||||
|
||||
if (stdio.stderr === null) {
|
||||
stdio.stderr = devNull.stderr;
|
||||
}
|
||||
|
||||
function pypyjs(opts) {
|
||||
|
||||
opts = opts || {};
|
||||
this.rootURL = opts.rootURL;
|
||||
this.totalMemory = opts.totalMemory || 128 * 1024 * 1024;
|
||||
this.autoLoadModules = opts.autoLoadModules || true;
|
||||
this._pendingModules = {};
|
||||
this._loadedModules = {};
|
||||
this._allModules = {};
|
||||
|
||||
// Allow opts to override default IO streams.
|
||||
this.stdin = opts.stdin || stdio.stdin;
|
||||
this.stdout = opts.stdout || stdio.stdout;
|
||||
this.stderr = opts.stderr || stdio.stderr;
|
||||
|
||||
// Default to finding files relative to this very file.
|
||||
if (!this.rootURL && !pypyjs.rootURL) {
|
||||
pypyjs.rootURL = __dirname;
|
||||
}
|
||||
if (this.rootURL && this.rootURL.charAt(this.rootURL.length - 1) !== "/") {
|
||||
this.rootURL += "/";
|
||||
}
|
||||
|
||||
// If we haven't already done so, fetch and load the code for the VM.
|
||||
// We do this once and cache the result for re-use, so that we don't
|
||||
// have to pay asmjs compilation overhead each time we create the VM.
|
||||
|
||||
if (! pypyjs._vmBuilderPromise) {
|
||||
pypyjs._vmBuilderPromise = this.fetch("pypyjs.vm.js").then((function(xhr) {
|
||||
// Parse the compiled code, hopefully asynchronously.
|
||||
// Unfortunately our use of Function constructor here doesn't
|
||||
// play very well with nodejs, where things like 'module' and
|
||||
// 'require' are not in the global scope. We have to pass them
|
||||
// in explicitly as arguments.
|
||||
var funcBody = [
|
||||
// This is the compiled code for the VM.
|
||||
xhr.responseText,
|
||||
'\n',
|
||||
// Ensure that some functions are available on the Module,
|
||||
// for linking with jitted code.
|
||||
'if (!Module._jitInvoke && typeof _jitInvoke !== "undefined") {',
|
||||
' Module._jitInvoke = _jitInvoke;',
|
||||
'}',
|
||||
// Keep some functions that are not exported by default, but
|
||||
// which appear in this scope when evaluating the above.
|
||||
"Module._emjs_make_handle = _emjs_make_handle;",
|
||||
"Module._emjs_free = _emjs_free;",
|
||||
// Call dependenciesFulfilled if it won't be done automatically.
|
||||
"dependenciesFulfilled=function() { inDependenciesFulfilled(FS); };",
|
||||
"if(!memoryInitializer||(!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_WORKER))dependenciesFulfilled();",
|
||||
].join("");
|
||||
return FunctionPromise("Module", "inDependenciesFulfilled", "require",
|
||||
"module", "__filename", "__dirname", funcBody)
|
||||
}).bind(this));
|
||||
}
|
||||
|
||||
// Create a new instance of the compiled VM, bound to local state
|
||||
// and a local Module object.
|
||||
|
||||
this._ready = new Promise((function(resolve, reject) {
|
||||
|
||||
// Initialize the Module object.
|
||||
// We make it available on this object so that we can use
|
||||
// its methods to execute code in the VM.
|
||||
var Module = {};
|
||||
this._module = Module;
|
||||
Module.TOTAL_MEMORY = this.totalMemory;
|
||||
|
||||
// We will set up the filesystem manually when we're ready.
|
||||
Module.noFSInit = true;
|
||||
Module.thisProgram = "/lib/pypyjs/pypyjs.js";
|
||||
Module.filePackagePrefixURL = this.rootURL || pypyjs.rootURL;
|
||||
Module.memoryInitializerPrefixURL = this.rootURL || pypyjs.rootURL;
|
||||
Module.locateFile = function(name) {
|
||||
return (this.rootURL || pypyjs.rootURL) + name;
|
||||
}
|
||||
|
||||
// Don't start or stop the program, just set it up.
|
||||
// We'll call the API functions ourself.
|
||||
Module.noInitialRun = true;
|
||||
Module.noExitRuntime = true;
|
||||
|
||||
// Route stdin to an overridable method on the object.
|
||||
var stdin = (function stdin() {
|
||||
return this.stdin();
|
||||
}).bind(this);
|
||||
|
||||
// Route stdout to an overridable method on the object.
|
||||
// We buffer the output for efficiency.
|
||||
var stdout_buffer = []
|
||||
var stdout = (function stdout(x) {
|
||||
var c = String.fromCharCode(x);
|
||||
stdout_buffer.push(c);
|
||||
if (c === "\n" || stdout_buffer.length >= 128) {
|
||||
this.stdout(stdout_buffer.join(""));
|
||||
stdout_buffer = [];
|
||||
}
|
||||
}).bind(this);
|
||||
|
||||
// Route stderr to an overridable method on the object.
|
||||
// We do not buffer stderr.
|
||||
var stderr = (function stderr(x) {
|
||||
var c = String.fromCharCode(x);
|
||||
this.stderr(c);
|
||||
}).bind(this);
|
||||
|
||||
// This is where execution will continue after loading
|
||||
// the memory initialization data, if any.
|
||||
var initializedResolve, initializedReject;
|
||||
var initializedP = new Promise(function(resolve, reject) {
|
||||
initializedResolve = resolve;
|
||||
initializedReject = reject;
|
||||
});
|
||||
var FS;
|
||||
var dependenciesFulfilled = function(fs) {
|
||||
FS = fs;
|
||||
// Initialize the filesystem state.
|
||||
try {
|
||||
FS.init(stdin, stdout, stderr);
|
||||
Module.FS_createPath("/", "lib/pypyjs/lib_pypy", true, false);
|
||||
Module.FS_createPath("/", "lib/pypyjs/lib-python/2.7", true, false);
|
||||
initializedResolve();
|
||||
} catch (err) {
|
||||
initializedReject(err);
|
||||
}
|
||||
}
|
||||
|
||||
// Begin fetching the metadata for available python modules.
|
||||
// With luck these can download while we jank around compiling
|
||||
// all of that javascript.
|
||||
// XXX TODO: also load memory initializer this way.
|
||||
var moduleDataP = this.fetch("modules/index.json");
|
||||
|
||||
pypyjs._vmBuilderPromise.then((function(vmBuilder) {
|
||||
var args = [
|
||||
Module,
|
||||
dependenciesFulfilled,
|
||||
typeof undefined,
|
||||
typeof undefined,
|
||||
typeof undefined,
|
||||
typeof __dirname
|
||||
];
|
||||
// This links the async-compiled module into our Module object.
|
||||
vmBuilder.apply(null, args);
|
||||
return initializedP;
|
||||
}).bind(this)).then((function() {
|
||||
// Continue with processing the downloaded module metadata.
|
||||
return moduleDataP.then((function(xhr) {
|
||||
// Store the module index, and load any preload modules.
|
||||
var modIndex = JSON.parse(xhr.responseText);
|
||||
this._allModules = modIndex.modules;
|
||||
if (modIndex.preload) {
|
||||
for (var name in modIndex.preload) {
|
||||
this._writeModuleFile(name, modIndex.preload[name]);
|
||||
}
|
||||
}
|
||||
// It's finally safe to launch the VM.
|
||||
Module.run();
|
||||
Module._rpython_startup_code();
|
||||
var pypy_home = Module.intArrayFromString("/lib/pypyjs/pypyjs.js");
|
||||
pypy_home = Module.allocate(pypy_home, 'i8', Module.ALLOC_NORMAL);
|
||||
Module._pypy_setup_home(pypy_home, 0);
|
||||
Module._free(pypy_home);
|
||||
var initCode = [
|
||||
"import js",
|
||||
"import sys; sys.platform = 'js'",
|
||||
"import traceback",
|
||||
"top_level_scope = {'__name__': '__main__'}"
|
||||
];
|
||||
initCode.forEach(function(codeStr) {
|
||||
var code = Module.intArrayFromString(codeStr);
|
||||
var code = Module.allocate(code, 'i8', Module.ALLOC_NORMAL);
|
||||
if (!code) {
|
||||
throw new pypyjs.Error('Failed to allocate memory');
|
||||
}
|
||||
var res = Module._pypy_execute_source(code);
|
||||
if (res < 0) {
|
||||
throw new pypyjs.Error('Failed to execute python code');
|
||||
}
|
||||
Module._free(code);
|
||||
});
|
||||
}).bind(this))
|
||||
}).bind(this))
|
||||
.then(resolve, reject);
|
||||
}).bind(this));
|
||||
|
||||
};
|
||||
|
||||
|
||||
// A simple file-fetching wrapper around XMLHttpRequest,
|
||||
// that treats paths as relative to the pypyjs.js root url.
|
||||
//
|
||||
pypyjs.prototype.fetch = function (relpath, responseType) {
|
||||
if (typeof window === "undefined") {
|
||||
var localStorage = false;
|
||||
}
|
||||
else {
|
||||
var localStorage = window.localStorage;
|
||||
}
|
||||
var use_cache = pypyjs.cacheKey && localStorage && relpath != "pypyjs.vm.js";
|
||||
if (use_cache) {
|
||||
var item = localStorage.getItem(pypyjs.cacheKey+':'+relpath);
|
||||
if (item) {
|
||||
return new Promise((function(resolve, reject) {
|
||||
resolve({ responseText: item });
|
||||
}))
|
||||
}
|
||||
}
|
||||
// For the web, use XMLHttpRequest.
|
||||
if (typeof XMLHttpRequest !== "undefined") {
|
||||
return new Promise((function(resolve, reject) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onload = function() {
|
||||
if (xhr.status >= 400) {
|
||||
reject(xhr)
|
||||
} else {
|
||||
if (use_cache && xhr.responseText) {
|
||||
localStorage.setItem(pypyjs.cacheKey+':'+relpath, xhr.responseText);
|
||||
}
|
||||
resolve(xhr);
|
||||
}
|
||||
};
|
||||
var rootURL = this.rootURL || pypyjs.rootURL;
|
||||
xhr.open('GET', rootURL + relpath, true);
|
||||
xhr.responseType = responseType || "text";
|
||||
xhr.send(null);
|
||||
}).bind(this));
|
||||
}
|
||||
// For nodejs, use fs.readFile.
|
||||
if (typeof fs !== "undefined" && typeof fs.readFile !== "undefined") {
|
||||
return new Promise((function(resolve, reject) {
|
||||
var rootURL = this.rootURL || pypyjs.rootURL;
|
||||
fs.readFile(path.join(rootURL, relpath), function(err, data) {
|
||||
if (err) return reject(err);
|
||||
resolve({ responseText: data.toString() });
|
||||
});
|
||||
}).bind(this));
|
||||
}
|
||||
// For spidermonkey, use snarf (which has a binary read mode).
|
||||
if (typeof snarf !== "undefined") {
|
||||
return new Promise((function(resolve, reject) {
|
||||
var rootURL = this.rootURL || pypyjs.rootURL;
|
||||
var data = snarf(rootURL + relpath);
|
||||
resolve({ responseText: data });
|
||||
}).bind(this));
|
||||
}
|
||||
// For d8, use read() and readbuffer().
|
||||
if (typeof read !== "undefined" && typeof readbuffer !== "undefined") {
|
||||
return new Promise((function(resolve, reject) {
|
||||
var rootURL = this.rootURL || pypyjs.rootURL;
|
||||
var data = read(rootURL + relpath);
|
||||
resolve({ responseText: data });
|
||||
}).bind(this));
|
||||
}
|
||||
return new Promise(function(resolve, reject) {
|
||||
reject("unable to fetch files");
|
||||
});
|
||||
};
|
||||
|
||||
if (typeof localStorage !== "undefined") {
|
||||
var localStorage = false;
|
||||
}
|
||||
|
||||
// pypyjs.prototype.fetch = function fetch(relpath, responseType) {
|
||||
// // For the web, use XMLHttpRequest.
|
||||
// var use_cache = pypyjs.cacheKey && localStorage;
|
||||
// if (use_cache) {
|
||||
// if (var item = localStorage.getItem(pypyjs.cacheKey+'-'+relpath)) {
|
||||
// resolve({ responseText: item });
|
||||
// }
|
||||
// }
|
||||
// if (typeof XMLHttpRequest !== "undefined") {
|
||||
// return new Promise((function(resolve, reject) {
|
||||
// var xhr = new XMLHttpRequest();
|
||||
// xhr.onload = function() {
|
||||
// if (xhr.status >= 400) {
|
||||
// reject(xhr)
|
||||
// } else {
|
||||
// console.log(xhr.responseText);
|
||||
// if (use_cache && xhr.responseText) {
|
||||
// localStorage.setItem(pypyjs.cacheKey+'-'+relpath, xhr.responseText);
|
||||
// }
|
||||
// resolve(xhr);
|
||||
// }
|
||||
// };
|
||||
// var rootURL = this.rootURL || pypyjs.rootURL;
|
||||
// xhr.open('GET', rootURL + relpath, true);
|
||||
// xhr.responseType = responseType || "text";
|
||||
// xhr.send(null);
|
||||
// }).bind(this));
|
||||
// }
|
||||
// // For nodejs, use fs.readFile.
|
||||
// if (typeof fs !== "undefined" && typeof fs.readFile !== "undefined") {
|
||||
// return new Promise((function(resolve, reject) {
|
||||
// var rootURL = this.rootURL || pypyjs.rootURL;
|
||||
// fs.readFile(path.join(rootURL, relpath), function(err, data) {
|
||||
// if (err) return reject(err);
|
||||
// resolve({ responseText: data.toString() });
|
||||
// });
|
||||
// }).bind(this));
|
||||
// }
|
||||
// // For spidermonkey, use snarf (which has a binary read mode).
|
||||
// if (typeof snarf !== "undefined") {
|
||||
// return new Promise((function(resolve, reject) {
|
||||
// var rootURL = this.rootURL || pypyjs.rootURL;
|
||||
// var data = snarf(rootURL + relpath);
|
||||
// resolve({ responseText: data });
|
||||
// }).bind(this));
|
||||
// }
|
||||
// // For d8, use read() and readbuffer().
|
||||
// if (typeof read !== "undefined" && typeof readbuffer !== "undefined") {
|
||||
// return new Promise((function(resolve, reject) {
|
||||
// var rootURL = this.rootURL || pypyjs.rootURL;
|
||||
// var data = read(rootURL + relpath);
|
||||
// resolve({ responseText: data });
|
||||
// }).bind(this));
|
||||
// }
|
||||
// return new Promise(function(resolve, reject) {
|
||||
// reject("unable to fetch files");
|
||||
// });
|
||||
// };
|
||||
|
||||
|
||||
// Method to execute python source directly in the VM.
|
||||
//
|
||||
// This is the basic way to push code into the pypyjs VM.
|
||||
// Calling code should not use it directly; rather we use it
|
||||
// as a primitive to build up a nicer execution API.
|
||||
//
|
||||
pypyjs.prototype._execute_source = function _execute_source(code) {
|
||||
var Module = this._module;
|
||||
code = "try:\n" +
|
||||
" " + code + "\n" +
|
||||
"except Exception:\n" +
|
||||
" typ, val, tb = sys.exc_info()\n" +
|
||||
" err_name = getattr(typ, '__name__', str(typ))\n" +
|
||||
" err_msg = str(val)\n" +
|
||||
" err_trace = traceback.format_exception(typ, val, tb)\n" +
|
||||
" err_trace = ''.join(err_trace)\n" +
|
||||
" js.globals['pypyjs']._lastErrorName = err_name\n" +
|
||||
" js.globals['pypyjs']._lastErrorMessage = err_msg\n" +
|
||||
" js.globals['pypyjs']._lastErrorTrace = err_trace\n";
|
||||
var code_chars = Module.intArrayFromString(code);
|
||||
var code_ptr = Module.allocate(code_chars, 'i8', Module.ALLOC_NORMAL);
|
||||
if (!code_ptr) {
|
||||
return Promise.reject(new pypyjs.Error("Failed to allocate memory"));
|
||||
}
|
||||
var res = Module._pypy_execute_source(code_ptr);
|
||||
Module._free(code_ptr);
|
||||
// XXX TODO: races/re-entrancy on _lastError?
|
||||
if (pypyjs._lastErrorName) {
|
||||
var err = new pypyjs.Error(
|
||||
pypyjs._lastErrorName,
|
||||
pypyjs._lastErrorMessage,
|
||||
pypyjs._lastErrorTrace
|
||||
);
|
||||
pypyjs._lastErrorName = null;
|
||||
pypyjs._lastErrorMessage = null;
|
||||
pypyjs._lastErrorTrace = null;
|
||||
return Promise.reject(err);
|
||||
}
|
||||
if (res < 0) {
|
||||
return Promise.reject(new pypyjs.Error("Error executing python code"));
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
|
||||
function _escape(value) {
|
||||
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
||||
}
|
||||
|
||||
|
||||
// Method to determine when the interpreter is ready.
|
||||
//
|
||||
// This method returns a promise that will resolve once the interpreter
|
||||
// is ready for use.
|
||||
//
|
||||
pypyjs.prototype.ready = function ready() {
|
||||
return this._ready;
|
||||
}
|
||||
|
||||
|
||||
// Method to execute some python code.
|
||||
//
|
||||
// This passes the given python code to the VM for execution.
|
||||
// It's fairly directly analogous to the "exec" statement in python.
|
||||
// It is not possible to directly access the result of the code, if any.
|
||||
// Rather you should store it into a variable and then use the get() method.
|
||||
//
|
||||
pypyjs.prototype.exec = function exec(code) {
|
||||
return this._ready.then((function() {
|
||||
var p = Promise.resolve();
|
||||
// Find any "import" statements in the code,
|
||||
// and ensure the modules are ready for loading.
|
||||
if (this.autoLoadModules) {
|
||||
p = p.then((function() {
|
||||
return this.findImportedNames(code);
|
||||
}).bind(this))
|
||||
.then((function(imports) {
|
||||
return this.loadModuleData.apply(this, imports);
|
||||
}).bind(this))
|
||||
}
|
||||
// Now we can execute the code in custom top-level scope.
|
||||
code = 'exec \'\'\'' + _escape(code) + '\'\'\' in top_level_scope';
|
||||
p = p.then((function() {
|
||||
return this._execute_source(code);
|
||||
}).bind(this));
|
||||
return p;
|
||||
}).bind(this));
|
||||
}
|
||||
|
||||
|
||||
// Method to evaluate an expression.
|
||||
//
|
||||
// This method evaluates an expression and returns its value (assuming the
|
||||
// value can be translated into javascript). It's fairly directly analogous
|
||||
// to the "eval" function in python.
|
||||
//
|
||||
// For backwards-compatibility reasons, it will also evaluate statements.
|
||||
// This behaviour is deprecated and will be removed in a future release.
|
||||
//
|
||||
pypyjs.prototype.eval = function (expr) {
|
||||
return this._ready.then((function() {
|
||||
// First try to execute it as an expression.
|
||||
code = "r = eval('" + _escape(expr) + "', top_level_scope)";
|
||||
return this._execute_source(code);
|
||||
}).bind(this)).then(
|
||||
(function() {
|
||||
// If that succeeded, return the result.
|
||||
return this.get("r", true)
|
||||
}).bind(this),
|
||||
(function(err) {
|
||||
if (err && err.name && err.name !== "SyntaxError") {
|
||||
throw err;
|
||||
}
|
||||
// If that failed, try again via exec().
|
||||
if (typeof console !== "undefined") {
|
||||
console.warn("Calling pypyjs.eval() with statements is deprecated.");
|
||||
console.warn("Use eval() for expressions, exec() for statements.");
|
||||
}
|
||||
return this.exec(expr);
|
||||
}).bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
// Method to evaluate some python code from a file..
|
||||
//
|
||||
// This fetches the named file and passes it to the VM for execution.
|
||||
//
|
||||
pypyjs.prototype.execfile = function execfile(filename) {
|
||||
return this.fetch(filename).then((function(xhr) {
|
||||
var code = xhr.responseText;
|
||||
return this.exec(code);
|
||||
}).bind(this));
|
||||
}
|
||||
|
||||
|
||||
// Method to read a python variable.
|
||||
//
|
||||
// This tries to convert the value in the named python variable into an
|
||||
// equivalent javascript value and returns it. It will fail if the variable
|
||||
// does not exist or contains a value that cannot be converted.
|
||||
//
|
||||
pypyjs._resultsID = 0;
|
||||
pypyjs._resultsMap = {};
|
||||
pypyjs.prototype.get = function get(name, _fromGlobals) {
|
||||
var resid = ""+(pypyjs._resultsID++);
|
||||
// We can read from global scope for internal use; don't do this from calling code!
|
||||
if (_fromGlobals) {
|
||||
var namespace = "globals()";
|
||||
} else {
|
||||
var namespace = "top_level_scope";
|
||||
}
|
||||
return this._ready.then((function() {
|
||||
var code = namespace + ".get('" + _escape(name) + "', js.undefined)";
|
||||
code = "js.convert(" + code + ")"
|
||||
code = "js.globals['pypyjs']._resultsMap['" + resid + "'] = " + code;
|
||||
return this._execute_source(code);
|
||||
}).bind(this)).then((function() {
|
||||
var res = pypyjs._resultsMap[resid];
|
||||
delete pypyjs._resultsMap[resid];
|
||||
return res;
|
||||
}).bind(this));
|
||||
}
|
||||
|
||||
|
||||
// Method to set a python variable to a javascript value.
|
||||
//
|
||||
// This generates a handle to the given object, and arranges for the named
|
||||
// python variable to reference it via that handle.
|
||||
//
|
||||
pypyjs.prototype.set = function set(name, value) {
|
||||
return this._ready.then((function() {
|
||||
var Module = this._module;
|
||||
var h = Module._emjs_make_handle(value);
|
||||
name = _escape(name);
|
||||
var code = "top_level_scope['" + name + "'] = js.Value(" + h + ")";
|
||||
return this._execute_source(code);
|
||||
}).bind(this));
|
||||
}
|
||||
|
||||
|
||||
// Method to run an interactive REPL.
|
||||
//
|
||||
// This method takes takes callback function implementing the user
|
||||
// input prompt, and runs a REPL loop using it. The prompt function
|
||||
// may either return the input as a string, or a promise resolving to
|
||||
// the input as a string. If not specified, we read from stdin (which
|
||||
// works fine in e.g. nodejs, but is almost certainly not what you want
|
||||
// in the browser, because it's blocking).
|
||||
//
|
||||
pypyjs.prototype.repl = function repl(prmpt) {
|
||||
if (!prmpt) {
|
||||
// If there's a custom stdin, or we're not in nodejs, then we should
|
||||
// default to prompting on stdin/stdout. For nodejs, we can build
|
||||
// an async prompt atop process.stdin.
|
||||
var buffer = "";
|
||||
if (this.stdin !== devNull.stdin || typeof process === "undefined") {
|
||||
prmpt = (function(ps1) {
|
||||
var input;
|
||||
this.stdout(ps1);
|
||||
var c = this.stdin();
|
||||
while (c) {
|
||||
var idx = c.indexOf("\n");
|
||||
if (idx >= 0) {
|
||||
var input = buffer + c.substr(0, idx + 1);
|
||||
buffer = c.substr(idx + 1);
|
||||
return input;
|
||||
}
|
||||
buffer += c;
|
||||
c = this.stdin();
|
||||
}
|
||||
input = buffer;
|
||||
buffer = "";
|
||||
return input;
|
||||
}).bind(this);
|
||||
} else {
|
||||
prmpt = (function(ps1) {
|
||||
return new Promise((function(resolve, reject) {
|
||||
this.stdout(ps1);
|
||||
var slurp = function() {
|
||||
process.stdin.once("readable", function() {
|
||||
var chunk = process.stdin.read();
|
||||
if (chunk === null) {
|
||||
slurp();
|
||||
} else {
|
||||
chunk = chunk.toString();
|
||||
var idx = chunk.indexOf("\n");
|
||||
if (idx < 0) {
|
||||
buffer += chunk;
|
||||
slurp();
|
||||
} else {
|
||||
resolve(buffer + chunk.substr(0, idx + 1));
|
||||
buffer = chunk.substr(idx + 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
slurp();
|
||||
}).bind(this));
|
||||
}).bind(this);
|
||||
}
|
||||
}
|
||||
// Set up an InteractiveConsole instance,
|
||||
// then loop forever via recursive promises.
|
||||
return this._ready.then((function() {
|
||||
return this.loadModuleData("code");
|
||||
}).bind(this)).then((function() {
|
||||
return this._execute_source("import code");
|
||||
}).bind(this)).then((function() {
|
||||
return this._execute_source("c = code.InteractiveConsole(top_level_scope)");
|
||||
}).bind(this)).then((function() {
|
||||
return this._repl_loop(prmpt, ">>> ");
|
||||
}).bind(this));
|
||||
}
|
||||
|
||||
|
||||
pypyjs.prototype._repl_loop = function _repl_loop(prmpt, ps1) {
|
||||
return Promise.resolve().then((function() {
|
||||
// Prompt for input, which may happen via async promise.
|
||||
return prmpt.call(this, ps1);
|
||||
}).bind(this)).then((function(input) {
|
||||
// Push it into the InteractiveConsole, a line at a time.
|
||||
var p = Promise.resolve();
|
||||
input.split("\n").forEach((function(line) {
|
||||
// Find any "import" statements in the code,
|
||||
// and ensure the modules are ready for loading.
|
||||
if (this.autoLoadModules) {
|
||||
p = p.then((function() {
|
||||
return this.findImportedNames(line);
|
||||
}).bind(this))
|
||||
.then((function(imports) {
|
||||
return this.loadModuleData.apply(this, imports);
|
||||
}).bind(this))
|
||||
}
|
||||
var code = 'r = c.push(\'' + _escape(line) + '\')';
|
||||
p = p.then((function() {
|
||||
return this._execute_source(code);
|
||||
}).bind(this));
|
||||
}).bind(this));
|
||||
return p;
|
||||
}).bind(this)).then((function() {
|
||||
// Check the result from the final push.
|
||||
return this.get("r", true)
|
||||
}).bind(this)).then((function(r) {
|
||||
// If r == 1, we're in a multi-line definition.
|
||||
// Adjust the prompt accordingly.
|
||||
if (r) {
|
||||
return this._repl_loop(prmpt, "... ");
|
||||
} else {
|
||||
return this._repl_loop(prmpt, ">>> ");
|
||||
}
|
||||
}).bind(this));
|
||||
}
|
||||
|
||||
|
||||
// Method to look for "import" statements in a code string.
|
||||
// Returns a promise that will resolve to a list of imported module names.
|
||||
//
|
||||
// XXX TODO: this is far from complete and should not be done with a regex.
|
||||
// Perhaps we can call into python's "ast" module for this parsing?
|
||||
//
|
||||
var importStatementRE = /(from\s+([a-zA-Z0-9_\.]+)\s+)?import\s+\(?\s*([a-zA-Z0-9_\.\*]+(\s+as\s+[a-zA-Z0-9_]+)?[ \t]*,?[ \t]*)+[ \t]*\)?/g
|
||||
pypyjs.prototype.findImportedNames = function findImportedNames(code) {
|
||||
var match = null;
|
||||
var imports = [];
|
||||
importStatementRE.lastIndex = 0;
|
||||
while ((match = importStatementRE.exec(code)) !== null) {
|
||||
var relmod = match[2];
|
||||
if (relmod) {
|
||||
relmod = relmod + ".";
|
||||
} else {
|
||||
relmod = "";
|
||||
}
|
||||
var submods = match[0].split("import")[1];
|
||||
while (submods && /[\s(]/.test(submods.charAt(0))) {
|
||||
submods = submods.substr(1);
|
||||
}
|
||||
while (submods && /[\s)]/.test(submods.charAt(submods.length - 1))) {
|
||||
submods = submods.substr(0, submods.length - 1);
|
||||
}
|
||||
submods = submods.split(/\s*,\s*/);
|
||||
for (var i = 0; i < submods.length; i++) {
|
||||
var submod = submods[i];
|
||||
submod = submod.split(/\s*as\s*/)[0];
|
||||
imports.push(relmod + submod);
|
||||
}
|
||||
}
|
||||
return Promise.resolve(imports);
|
||||
}
|
||||
|
||||
|
||||
// Method to load the contents of a python module, along with
|
||||
// any dependencies. This populates the relevant paths within
|
||||
// the VMs simulated filesystem so that is can find and import
|
||||
// the specified module.
|
||||
//
|
||||
pypyjs.prototype.loadModuleData = function loadModuleData(/* names */) {
|
||||
// Each argument is a name that we want to import.
|
||||
// We must find the longest prefix that is an available module
|
||||
// and load it along with all its dependencies.
|
||||
var modules = Array.prototype.slice.call(arguments);
|
||||
return this._ready.then((function() {
|
||||
var toLoad = {};
|
||||
NEXTNAME: for (var i = 0; i < modules.length; i++) {
|
||||
var name = modules[i];
|
||||
// Find the nearest containing module for the given name.
|
||||
// Note that it may not match a module at all, in which case we ignore it.
|
||||
while (true) {
|
||||
if (this._allModules[name]) {
|
||||
break;
|
||||
}
|
||||
name = name.substr(0, name.lastIndexOf("."));
|
||||
if (!name) continue NEXTNAME;
|
||||
}
|
||||
this._findModuleDeps(name, toLoad);
|
||||
}
|
||||
// Now ensure that each module gets loaded.
|
||||
// XXX TODO: we could load these concurrently.
|
||||
var p = Promise.resolve();
|
||||
for (var name in toLoad) {
|
||||
p = p.then(this._makeLoadModuleData(name));
|
||||
}
|
||||
return p;
|
||||
}).bind(this));
|
||||
}
|
||||
|
||||
|
||||
pypyjs.prototype._findModuleDeps = function _findModuleDeps(name, seen) {
|
||||
if (!seen) seen = {};
|
||||
var deps = [];
|
||||
// If we don't know about this module, ignore it.
|
||||
if (!this._allModules[name]) {
|
||||
return seen;
|
||||
}
|
||||
// Depend on any explicitly-named imports.
|
||||
var imports = this._allModules[name].imports;
|
||||
if (imports) {
|
||||
for (var i = 0; i < imports.length; i++) {
|
||||
deps.push(imports[i]);
|
||||
}
|
||||
}
|
||||
// Depend on the __init__.py for packages.
|
||||
if (this._allModules[name].dir) {
|
||||
deps.push(name + ".__init__");
|
||||
}
|
||||
// Include the parent package, if any.
|
||||
var idx = name.lastIndexOf(".");
|
||||
if (idx !== -1) {
|
||||
deps.push(name.substr(0, idx));
|
||||
}
|
||||
// Recurse for any previously-unseen dependencies.
|
||||
seen[name] = true;
|
||||
for (var i = 0; i < deps.length; i++) {
|
||||
if (!seen[deps[i]]) {
|
||||
this._findModuleDeps(deps[i], seen);
|
||||
}
|
||||
}
|
||||
return seen;
|
||||
}
|
||||
|
||||
|
||||
pypyjs.prototype._makeLoadModuleData = function _makeLoadModuleData(name) {
|
||||
return (function() {
|
||||
// If we've already loaded this module, we're done.
|
||||
if (this._loadedModules[name]) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
// If we're already in the process of loading it, use the existing promise.
|
||||
if (this._pendingModules[name]) {
|
||||
return this._pendingModules[name];
|
||||
}
|
||||
// If it's a package directory, there's not actually anything to do.
|
||||
if (this._allModules[name].dir) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
// We need to fetch the module file and write it out.
|
||||
var modfile = this._allModules[name].file;
|
||||
var p = this.fetch("modules/" + modfile)
|
||||
.then((function(xhr) {
|
||||
var contents = xhr.responseText;
|
||||
this._writeModuleFile(name, contents)
|
||||
delete this._pendingModules[name];
|
||||
}).bind(this))
|
||||
this._pendingModules[name] = p;
|
||||
return p;
|
||||
}).bind(this);
|
||||
}
|
||||
|
||||
|
||||
pypyjs.prototype._writeModuleFile = function _writeModuleFile(name, data) {
|
||||
var Module = this._module;
|
||||
var file = this._allModules[name].file;
|
||||
// Create the containing directory first.
|
||||
var dir = file.split("/").slice(0, -1).join("/")
|
||||
try {
|
||||
Module.FS_createPath("/lib/pypyjs/lib_pypy", dir, true, false);
|
||||
} catch (e) { }
|
||||
// Now we can safely create the file.
|
||||
var fullpath = "/lib/pypyjs/lib_pypy/" + file;
|
||||
Module.FS_createDataFile(fullpath, "", data, true, false, true);
|
||||
this._loadedModules[name] = true;
|
||||
}
|
||||
|
||||
|
||||
// An error class for reporting python exceptions back to calling code.
|
||||
// XXX TODO: this could be a lot more user-friendly than a opaque error...
|
||||
|
||||
pypyjs.Error = function pypyjsError(name, message, trace) {
|
||||
if (name && typeof message === "undefined") {
|
||||
message = name;
|
||||
name = "";
|
||||
}
|
||||
this.name = name || "pypyjs.Error";
|
||||
this.message = message || "pypyjs Unknown Error";
|
||||
this.trace = trace || "";
|
||||
}
|
||||
pypyjs.Error.prototype = new Error();
|
||||
pypyjs.Error.prototype.constructor = pypyjs.Error;
|
||||
|
||||
|
||||
|
||||
// XXX TODO: expose the filesystem for manipulation by calling code.
|
||||
|
||||
|
||||
// Add convenience methods directly on the 'pypyjs' function, that
|
||||
// will invoke corresponding methods on a default VM instance.
|
||||
// This makes it look like 'pypyjs' is a singleton VM instance.
|
||||
|
||||
pypyjs._defaultVM = null;
|
||||
pypyjs.stdin = stdio.stdin
|
||||
pypyjs.stdout = stdio.stdout
|
||||
pypyjs.stderr = stdio.stderr
|
||||
|
||||
var PUBLIC_NAMES = ['ready', 'exec', 'eval', 'execfile', 'get', 'set',
|
||||
'repl', 'loadModuleData'];
|
||||
|
||||
PUBLIC_NAMES.forEach(function(name) {
|
||||
pypyjs[name] = function() {
|
||||
if (!pypyjs._defaultVM) {
|
||||
pypyjs._defaultVM = new pypyjs({
|
||||
stdin: function(){ return pypyjs.stdin.apply(this, arguments); },
|
||||
stdout: function(){ return pypyjs.stdout.apply(this, arguments); },
|
||||
stderr: function(){ return pypyjs.stderr.apply(this, arguments); },
|
||||
});
|
||||
}
|
||||
return pypyjs._defaultVM[name].apply(pypyjs._defaultVM, arguments)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// For nodejs, run a repl when invoked directly from the command-line.
|
||||
|
||||
return pypyjs;
|
||||
|
||||
})();
|
|
@ -1,150 +0,0 @@
|
|||
import React from 'react';
|
||||
import GraphenePlayground from './GraphenePlayground';
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
const DEFAULT_CACHE_KEY = 'default';
|
||||
|
||||
function filterObject(object, callback, context) {
|
||||
if (!object) {
|
||||
return null;
|
||||
}
|
||||
var result = {};
|
||||
for (var name in object) {
|
||||
if (hasOwnProperty.call(object, name) &&
|
||||
callback.call(context, object[name], name, object)) {
|
||||
result[name] = object[name];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
class Playground extends React.Component {
|
||||
componentWillMount() {
|
||||
var sourceWasInjected = false;
|
||||
var queryParams = this.context.router.getCurrentQuery();
|
||||
|
||||
var {
|
||||
cacheKey,
|
||||
noCache,
|
||||
} = queryParams;
|
||||
noCache = (noCache !== undefined) && (noCache !== 'false');
|
||||
if (noCache) {
|
||||
cacheKey = undefined;
|
||||
} else if (!cacheKey) {
|
||||
cacheKey = DEFAULT_CACHE_KEY;
|
||||
}
|
||||
this.schemaCacheKey = `rp-${cacheKey}-schema`;
|
||||
this.queryCacheKey = `rp-${cacheKey}-query`;
|
||||
this.cacheKey = cacheKey;
|
||||
|
||||
var initialSchema;
|
||||
var initialQuery;
|
||||
var storedSchema = localStorage.getItem(this.schemaCacheKey);
|
||||
var storedQuery = localStorage.getItem(this.queryCacheKey);
|
||||
if (noCache) {
|
||||
// Use case #1
|
||||
// We use the noCache param to force a playground to have certain contents.
|
||||
// eg. static example apps
|
||||
initialSchema = queryParams.schema || '';
|
||||
initialQuery = queryParams.query || '';
|
||||
sourceWasInjected = true;
|
||||
queryParams = {};
|
||||
} else if (cacheKey === DEFAULT_CACHE_KEY) {
|
||||
// Use case #2
|
||||
// The user loaded the playground without a custom cache key.
|
||||
// Allow code injection via the URL
|
||||
// OR load code from localStorage
|
||||
// OR prime the playground with some default 'hello world' code
|
||||
if (queryParams.schema != null) {
|
||||
initialSchema = queryParams.schema;
|
||||
sourceWasInjected = queryParams.schema !== storedSchema;
|
||||
} else if (storedSchema != null) {
|
||||
initialSchema = storedSchema;
|
||||
} else {
|
||||
initialSchema = require('!raw!./examples/hello.schema.py');
|
||||
}
|
||||
if (queryParams.query != null) {
|
||||
initialQuery = queryParams.query;
|
||||
sourceWasInjected = queryParams.query !== storedQuery;
|
||||
} else if (storedQuery != null) {
|
||||
initialQuery = storedQuery;
|
||||
} else {
|
||||
initialQuery = require('!raw!./examples/hello.graphql');
|
||||
}
|
||||
queryParams = filterObject({
|
||||
schema: queryParams.schema,
|
||||
query: queryParams.query,
|
||||
}, v => v !== undefined);
|
||||
} else if (cacheKey) {
|
||||
// Use case #3
|
||||
// Custom cache keys are useful in cases where you want to embed a playground
|
||||
// that features both custom boilerplate code AND saves the developer's
|
||||
// progress, without overwriting the default code cache. eg. a tutorial.
|
||||
if (storedSchema != null) {
|
||||
initialSchema = storedSchema;
|
||||
} else {
|
||||
initialSchema = queryParams[`schema_${cacheKey}`];
|
||||
if (initialSchema != null) {
|
||||
sourceWasInjected = true;
|
||||
}
|
||||
}
|
||||
if (storedQuery != null) {
|
||||
initialQuery = storedQuery;
|
||||
} else {
|
||||
initialQuery = queryParams[`query_${cacheKey}`];
|
||||
if (initialQuery != null) {
|
||||
sourceWasInjected = true;
|
||||
}
|
||||
}
|
||||
queryParams = {};
|
||||
}
|
||||
this.changeParams(queryParams);
|
||||
this.state = {initialSchema, initialQuery, sourceWasInjected};
|
||||
this.queryParams = queryParams;
|
||||
}
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
changeParams(queryParams) {
|
||||
var router = this.context.router;
|
||||
var routeName = router.getCurrentPathname();
|
||||
var params = router.getCurrentParams();
|
||||
queryParams = _.mapValues(queryParams, encodeURIComponent);
|
||||
router.replaceWith(routeName, params, queryParams);
|
||||
}
|
||||
render() {
|
||||
return (<GraphenePlayground
|
||||
initialSchema={this.state.initialSchema}
|
||||
initialQuery={this.state.initialQuery}
|
||||
onEditSchema={(source) => {
|
||||
localStorage.setItem(this.schemaCacheKey, source);
|
||||
if (this.cacheKey === DEFAULT_CACHE_KEY) {
|
||||
this.queryParams.schema = source;
|
||||
if (!this.queryParams.query) {
|
||||
this.queryParams.query = this.state.initialQuery;
|
||||
}
|
||||
this.changeParams(this.queryParams);
|
||||
}
|
||||
}}
|
||||
onEditQuery={(source) => {
|
||||
localStorage.setItem(this.queryCacheKey, source);
|
||||
if (this.cacheKey === DEFAULT_CACHE_KEY) {
|
||||
this.queryParams.query = source;
|
||||
if (!this.queryParams.schema) {
|
||||
this.queryParams.schema = this.state.initialSchema;
|
||||
}
|
||||
this.changeParams(this.queryParams);
|
||||
}
|
||||
}}
|
||||
/>);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Playground.contextTypes = {
|
||||
router: React.PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = Playground;
|
|
@ -1,15 +0,0 @@
|
|||
import {
|
||||
GraphQLObjectType,
|
||||
GraphQLString,
|
||||
GraphQLSchema,
|
||||
} from 'graphql';
|
||||
|
||||
|
||||
export default new GraphQLSchema({
|
||||
query: new GraphQLObjectType({
|
||||
name: 'Query',
|
||||
fields: () => ({
|
||||
__emptyField: {type: GraphQLString},
|
||||
}),
|
||||
}),
|
||||
});
|
|
@ -1,21 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
class PlaygroundWrapper extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = { currentComponent: null };
|
||||
}
|
||||
componentDidMount() {
|
||||
require(["playground-page"], (Playground) =>{
|
||||
this.setState({
|
||||
currentComponent: Playground
|
||||
});
|
||||
});
|
||||
}
|
||||
render() {
|
||||
var Current = this.state.currentComponent;
|
||||
return Current?<Current />:null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PlaygroundWrapper;
|
144
docs/quickstart.rst
Normal file
144
docs/quickstart.rst
Normal file
|
@ -0,0 +1,144 @@
|
|||
Getting started
|
||||
===============
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
What is GraphQL?
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
GraphQL is a query language for your API.
|
||||
|
||||
It provides a standard way to:
|
||||
|
||||
* *describe data provided by a server* in a statically typed **Schema**
|
||||
* *request data* in a **Query** which exactly describes your data requirements and
|
||||
* *receive data* in a **Response** containing only the data you requested.
|
||||
|
||||
For an introduction to GraphQL and an overview of its concepts, please refer to `the official GraphQL documentation`_.
|
||||
|
||||
.. _the official GraphQL documentation: http://graphql.org/learn/
|
||||
|
||||
What is Graphene?
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Graphene is a library that provides tools to implement a GraphQL API in Python using a *code-first* approach.
|
||||
|
||||
Compare Graphene's *code-first* approach to building a GraphQL API with *schema-first* approaches like `Apollo Server`_ (JavaScript) or Ariadne_ (Python). Instead of writing GraphQL **Schema Definition Language (SDL)**, we write Python code to describe the data provided by your server.
|
||||
|
||||
.. _Apollo Server: https://www.apollographql.com/docs/apollo-server/
|
||||
|
||||
.. _Ariadne: https://ariadnegraphql.org/
|
||||
|
||||
Graphene is fully featured with integrations for the most popular web frameworks and ORMs. Graphene produces schemas that are fully compliant with the GraphQL spec and provides tools and patterns for building a Relay-Compliant API as well.
|
||||
|
||||
An example in Graphene
|
||||
----------------------
|
||||
|
||||
Let’s build a basic GraphQL schema to say "hello" and "goodbye" in Graphene.
|
||||
|
||||
When we send a **Query** requesting only one **Field**, ``hello``, and specify a value for the ``firstName`` **Argument**...
|
||||
|
||||
.. code::
|
||||
|
||||
{
|
||||
hello(firstName: "friend")
|
||||
}
|
||||
|
||||
...we would expect the following Response containing only the data requested (the ``goodbye`` field is not resolved).
|
||||
|
||||
.. code::
|
||||
|
||||
{
|
||||
"data": {
|
||||
"hello": "Hello friend!"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Requirements
|
||||
~~~~~~~~~~~~
|
||||
|
||||
- Python (3.8, 3.9, 3.10, 3.11, 3.12, pypy)
|
||||
- Graphene (3.0)
|
||||
|
||||
Project setup
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
.. code:: bash
|
||||
|
||||
pip install "graphene>=3.0"
|
||||
|
||||
Creating a basic Schema
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In Graphene, we can define a simple schema using the following code:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from graphene import ObjectType, String, Schema
|
||||
|
||||
class Query(ObjectType):
|
||||
# this defines a Field `hello` in our Schema with a single Argument `first_name`
|
||||
# By default, the argument name will automatically be camel-based into firstName in the generated schema
|
||||
hello = String(first_name=String(default_value="stranger"))
|
||||
goodbye = String()
|
||||
|
||||
# our Resolver method takes the GraphQL context (root, info) as well as
|
||||
# Argument (first_name) for the Field and returns data for the query Response
|
||||
def resolve_hello(root, info, first_name):
|
||||
return f'Hello {first_name}!'
|
||||
|
||||
def resolve_goodbye(root, info):
|
||||
return 'See ya!'
|
||||
|
||||
schema = Schema(query=Query)
|
||||
|
||||
|
||||
A GraphQL **Schema** describes each **Field** in the data model provided by the server using scalar types like *String*, *Int* and *Enum* and compound types like *List* and *Object*. For more details refer to the Graphene :ref:`TypesReference`.
|
||||
|
||||
Our schema can also define any number of **Arguments** for our **Fields**. This is a powerful way for a **Query** to describe the exact data requirements for each **Field**.
|
||||
|
||||
For each **Field** in our **Schema**, we write a **Resolver** method to fetch data requested by a client's **Query** using the current context and **Arguments**. For more details, refer to this section on :ref:`Resolvers`.
|
||||
|
||||
Schema Definition Language (SDL)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In the `GraphQL Schema Definition Language`_, we could describe the fields defined by our example code as shown below.
|
||||
|
||||
.. _GraphQL Schema Definition Language: https://graphql.org/learn/schema/
|
||||
|
||||
.. code::
|
||||
|
||||
type Query {
|
||||
hello(firstName: String = "stranger"): String
|
||||
goodbye: String
|
||||
}
|
||||
|
||||
Further examples in this documentation will use SDL to describe schema created by ObjectTypes and other fields.
|
||||
|
||||
Querying
|
||||
~~~~~~~~
|
||||
|
||||
Then we can start querying our **Schema** by passing a GraphQL query string to ``execute``:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# we can query for our field (with the default argument)
|
||||
query_string = '{ hello }'
|
||||
result = schema.execute(query_string)
|
||||
print(result.data['hello'])
|
||||
# "Hello stranger!"
|
||||
|
||||
# or passing the argument in the query
|
||||
query_with_argument = '{ hello(firstName: "GraphQL") }'
|
||||
result = schema.execute(query_with_argument)
|
||||
print(result.data['hello'])
|
||||
# "Hello GraphQL!"
|
||||
|
||||
Next steps
|
||||
~~~~~~~~~~
|
||||
|
||||
Congrats! You got your first Graphene schema working!
|
||||
|
||||
Normally, we don't need to directly execute a query string against our schema as Graphene provides many useful Integrations with popular web frameworks like Flask and Django. Check out :ref:`Integrations` for more information on how to get started serving your GraphQL API.
|
45
docs/relay/connection.rst
Normal file
45
docs/relay/connection.rst
Normal file
|
@ -0,0 +1,45 @@
|
|||
Connection
|
||||
==========
|
||||
|
||||
A connection is a vitaminized version of a List that provides ways of
|
||||
slicing and paginating through it. The way you create Connection types
|
||||
in ``graphene`` is using ``relay.Connection`` and ``relay.ConnectionField``.
|
||||
|
||||
Quick example
|
||||
-------------
|
||||
|
||||
If we want to create a custom Connection on a given node, we have to subclass the
|
||||
``Connection`` class.
|
||||
|
||||
In the following example, ``extra`` will be an extra field in the connection,
|
||||
and ``other`` an extra field in the Connection Edge.
|
||||
|
||||
.. code:: python
|
||||
|
||||
class ShipConnection(Connection):
|
||||
extra = String()
|
||||
|
||||
class Meta:
|
||||
node = Ship
|
||||
|
||||
class Edge:
|
||||
other = String()
|
||||
|
||||
The ``ShipConnection`` connection class, will have automatically a ``pageInfo`` field,
|
||||
and a ``edges`` field (which is a list of ``ShipConnection.Edge``).
|
||||
This ``Edge`` will have a ``node`` field linking to the specified node
|
||||
(in ``ShipConnection.Meta``) and the field ``other`` that we defined in the class.
|
||||
|
||||
Connection Field
|
||||
----------------
|
||||
You can create connection fields in any Connection, in case any ObjectType
|
||||
that implements ``Node`` will have a default Connection.
|
||||
|
||||
.. code:: python
|
||||
|
||||
class Faction(graphene.ObjectType):
|
||||
name = graphene.String()
|
||||
ships = relay.ConnectionField(ShipConnection)
|
||||
|
||||
def resolve_ships(root, info):
|
||||
return []
|
26
docs/relay/index.rst
Normal file
26
docs/relay/index.rst
Normal file
|
@ -0,0 +1,26 @@
|
|||
Relay
|
||||
=====
|
||||
|
||||
Graphene has complete support for `Relay`_ and offers some utils to make
|
||||
integration from Python easy.
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
nodes
|
||||
connection
|
||||
mutations
|
||||
|
||||
|
||||
Useful links
|
||||
------------
|
||||
|
||||
- `Getting started with Relay`_
|
||||
- `Relay Global Identification Specification`_
|
||||
- `Relay Cursor Connection Specification`_
|
||||
|
||||
.. _Relay: https://relay.dev/docs/guides/graphql-server-specification/
|
||||
.. _Getting started with Relay: https://relay.dev/docs/getting-started/step-by-step-guide/
|
||||
.. _Relay Global Identification Specification: https://relay.dev/graphql/objectidentification.htm
|
||||
.. _Relay Cursor Connection Specification: https://relay.dev/graphql/connections.htm
|
57
docs/relay/mutations.rst
Normal file
57
docs/relay/mutations.rst
Normal file
|
@ -0,0 +1,57 @@
|
|||
Mutations
|
||||
=========
|
||||
|
||||
Most APIs don’t just allow you to read data, they also allow you to
|
||||
write.
|
||||
|
||||
In GraphQL, this is done using mutations. Just like queries,
|
||||
Relay puts some additional requirements on mutations, but Graphene
|
||||
nicely manages that for you. All you need to do is make your mutation a
|
||||
subclass of ``relay.ClientIDMutation``.
|
||||
|
||||
.. code:: python
|
||||
|
||||
class IntroduceShip(relay.ClientIDMutation):
|
||||
|
||||
class Input:
|
||||
ship_name = graphene.String(required=True)
|
||||
faction_id = graphene.String(required=True)
|
||||
|
||||
ship = graphene.Field(Ship)
|
||||
faction = graphene.Field(Faction)
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **input):
|
||||
ship_name = input.ship_name
|
||||
faction_id = input.faction_id
|
||||
ship = create_ship(ship_name, faction_id)
|
||||
faction = get_faction(faction_id)
|
||||
return IntroduceShip(ship=ship, faction=faction)
|
||||
|
||||
|
||||
|
||||
Accepting Files
|
||||
---------------
|
||||
|
||||
Mutations can also accept files, that's how it will work with different integrations:
|
||||
|
||||
.. code:: python
|
||||
|
||||
class UploadFile(graphene.ClientIDMutation):
|
||||
class Input:
|
||||
pass
|
||||
# nothing needed for uploading file
|
||||
|
||||
# your return fields
|
||||
success = graphene.String()
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **input):
|
||||
# When using it in Django, context will be the request
|
||||
files = info.context.FILES
|
||||
# Or, if used in Flask, context will be the flask global request
|
||||
# files = context.files
|
||||
|
||||
# do something with files
|
||||
|
||||
return UploadFile(success=True)
|
102
docs/relay/nodes.rst
Normal file
102
docs/relay/nodes.rst
Normal file
|
@ -0,0 +1,102 @@
|
|||
Nodes
|
||||
=====
|
||||
|
||||
A ``Node`` is an Interface provided by ``graphene.relay`` that contains
|
||||
a single field ``id`` (which is a ``ID!``). Any object that inherits
|
||||
from it has to implement a ``get_node`` method for retrieving a
|
||||
``Node`` by an *id*.
|
||||
|
||||
|
||||
Quick example
|
||||
-------------
|
||||
|
||||
Example usage (taken from the `Starwars Relay example`_):
|
||||
|
||||
.. code:: python
|
||||
|
||||
class Ship(graphene.ObjectType):
|
||||
'''A ship in the Star Wars saga'''
|
||||
class Meta:
|
||||
interfaces = (relay.Node, )
|
||||
|
||||
name = graphene.String(description='The name of the ship.')
|
||||
|
||||
@classmethod
|
||||
def get_node(cls, info, id):
|
||||
return get_ship(id)
|
||||
|
||||
The ``id`` returned by the ``Ship`` type when you query it will be a
|
||||
scalar which contains enough info for the server to know its type and
|
||||
its id.
|
||||
|
||||
For example, the instance ``Ship(id=1)`` will return ``U2hpcDox`` as the
|
||||
id when you query it (which is the base64 encoding of ``Ship:1``), and
|
||||
which could be useful later if we want to query a node by its id.
|
||||
|
||||
|
||||
Custom Nodes
|
||||
------------
|
||||
|
||||
You can use the predefined ``relay.Node`` or you can subclass it, defining
|
||||
custom ways of how a node id is encoded (using the ``to_global_id`` method in the class)
|
||||
or how we can retrieve a Node given a encoded id (with the ``get_node_from_global_id`` method).
|
||||
|
||||
Example of a custom node:
|
||||
|
||||
.. code:: python
|
||||
|
||||
class CustomNode(Node):
|
||||
|
||||
class Meta:
|
||||
name = 'Node'
|
||||
|
||||
@staticmethod
|
||||
def to_global_id(type_, id):
|
||||
return f"{type_}:{id}"
|
||||
|
||||
@staticmethod
|
||||
def get_node_from_global_id(info, global_id, only_type=None):
|
||||
type_, id = global_id.split(':')
|
||||
if only_type:
|
||||
# We assure that the node type that we want to retrieve
|
||||
# is the same that was indicated in the field type
|
||||
assert type_ == only_type._meta.name, 'Received not compatible node.'
|
||||
|
||||
if type_ == 'User':
|
||||
return get_user(id)
|
||||
elif type_ == 'Photo':
|
||||
return get_photo(id)
|
||||
|
||||
|
||||
The ``get_node_from_global_id`` method will be called when ``CustomNode.Field`` is resolved.
|
||||
|
||||
|
||||
Accessing node types
|
||||
--------------------
|
||||
|
||||
If we want to retrieve node instances from a ``global_id`` (scalar that identifies an instance by it's type name and id),
|
||||
we can simply do ``Node.get_node_from_global_id(info, global_id)``.
|
||||
|
||||
In the case we want to restrict the instance retrieval to a specific type, we can do:
|
||||
``Node.get_node_from_global_id(info, global_id, only_type=Ship)``. This will raise an error
|
||||
if the ``global_id`` doesn't correspond to a Ship type.
|
||||
|
||||
|
||||
Node Root field
|
||||
---------------
|
||||
|
||||
As is required in the `Relay specification`_, the server must implement
|
||||
a root field called ``node`` that returns a ``Node`` Interface.
|
||||
|
||||
For this reason, ``graphene`` provides the field ``relay.Node.Field``,
|
||||
which links to any type in the Schema which implements ``Node``.
|
||||
Example usage:
|
||||
|
||||
.. code:: python
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
# Should be CustomNode.Field() if we want to use our custom Node
|
||||
node = relay.Node.Field()
|
||||
|
||||
.. _Relay specification: https://facebook.github.io/relay/docs/graphql-relay-specification.html
|
||||
.. _Starwars Relay example: https://github.com/graphql-python/graphene/blob/master/examples/starwars_relay/schema.py
|
5
docs/requirements.txt
Normal file
5
docs/requirements.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Required library
|
||||
Sphinx==6.1.3
|
||||
sphinx-autobuild==2021.3.14
|
||||
# Docs template
|
||||
http://graphene-python.org/sphinx_graphene_theme.zip
|
1
docs/static/CNAME
vendored
1
docs/static/CNAME
vendored
|
@ -1 +0,0 @@
|
|||
graphene-python.org
|
BIN
docs/static/favicon.png
vendored
BIN
docs/static/favicon.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 1.3 KiB |
71
docs/testing/index.rst
Normal file
71
docs/testing/index.rst
Normal file
|
@ -0,0 +1,71 @@
|
|||
===================
|
||||
Testing in Graphene
|
||||
===================
|
||||
|
||||
|
||||
Automated testing is an extremely useful bug-killing tool for the modern developer. You can use a collection of tests – a test suite – to solve, or avoid, a number of problems:
|
||||
|
||||
- When you’re writing new code, you can use tests to validate your code works as expected.
|
||||
- When you’re refactoring or modifying old code, you can use tests to ensure your changes haven’t affected your application’s behavior unexpectedly.
|
||||
|
||||
Testing a GraphQL application is a complex task, because a GraphQL application is made of several layers of logic – schema definition, schema validation, permissions and field resolution.
|
||||
|
||||
With Graphene test-execution framework and assorted utilities, you can simulate GraphQL requests, execute mutations, inspect your application’s output and generally verify your code is doing what it should be doing.
|
||||
|
||||
|
||||
Testing tools
|
||||
-------------
|
||||
|
||||
Graphene provides a small set of tools that come in handy when writing tests.
|
||||
|
||||
|
||||
Test Client
|
||||
~~~~~~~~~~~
|
||||
|
||||
The test client is a Python class that acts as a dummy GraphQL client, allowing you to test your views and interact with your Graphene-powered application programmatically.
|
||||
|
||||
Some of the things you can do with the test client are:
|
||||
|
||||
- Simulate Queries and Mutations and observe the response.
|
||||
- Test that a given query request is rendered by a given Django template, with a template context that contains certain values.
|
||||
|
||||
|
||||
Overview and a quick example
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To use the test client, instantiate ``graphene.test.Client`` and retrieve GraphQL responses:
|
||||
|
||||
|
||||
.. code:: python
|
||||
|
||||
from graphene.test import Client
|
||||
|
||||
def test_hey():
|
||||
client = Client(my_schema)
|
||||
executed = client.execute('''{ hey }''')
|
||||
assert executed == {
|
||||
'data': {
|
||||
'hey': 'hello!'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Execute parameters
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can also add extra keyword arguments to the ``execute`` method, such as
|
||||
``context``, ``root``, ``variables``, ...:
|
||||
|
||||
|
||||
.. code:: python
|
||||
|
||||
from graphene.test import Client
|
||||
|
||||
def test_hey():
|
||||
client = Client(my_schema)
|
||||
executed = client.execute('''{ hey }''', context={'user': 'Peter'})
|
||||
assert executed == {
|
||||
'data': {
|
||||
'hey': 'hello Peter!'
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user