mirror of
https://github.com/graphql-python/graphene.git
synced 2025-02-02 12:44:15 +03:00
Merge pull request #56 from graphql-python/docs-playground
Docs playground
This commit is contained in:
commit
79b8870618
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -63,5 +63,12 @@ target/
|
||||||
/tests/django.sqlite
|
/tests/django.sqlite
|
||||||
|
|
||||||
/graphene/index.json
|
/graphene/index.json
|
||||||
|
|
||||||
/graphene/meta.json
|
/graphene/meta.json
|
||||||
|
|
||||||
|
/meta.json
|
||||||
|
/index.json
|
||||||
|
|
||||||
|
/docs/playground/graphene-js/pypyjs-release-nojit/
|
||||||
|
/docs/static/playground/lib
|
||||||
|
|
||||||
|
/docs/static/playground
|
||||||
|
|
13
.travis.yml
13
.travis.yml
|
@ -26,30 +26,35 @@ install:
|
||||||
pip install --download-cache $HOME/.cache/pip/ pytest pytest-cov coveralls six pytest-django
|
pip install --download-cache $HOME/.cache/pip/ pytest pytest-cov coveralls six pytest-django
|
||||||
pip install --download-cache $HOME/.cache/pip/ -e .[django]
|
pip install --download-cache $HOME/.cache/pip/ -e .[django]
|
||||||
python setup.py develop
|
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
|
elif [ "$TEST_TYPE" = lint ]; then
|
||||||
pip install --download-cache $HOME/.cache/pip/ flake8
|
pip install --download-cache $HOME/.cache/pip/ flake8
|
||||||
fi
|
fi
|
||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
if [ "$TEST_TYPE" = build_website ]; then
|
if [ "$TEST_TYPE" = build_website ]; then
|
||||||
if [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = false ]; then
|
if [ "$TRAVIS_BRANCH" = "docs-playground" ] && [ "$TRAVIS_PULL_REQUEST" = false ]; then
|
||||||
echo "Building the web."
|
echo "Building the web."
|
||||||
nvm install 4.0
|
nvm install 4.0
|
||||||
|
|
||||||
GH_PAGES_DIR="$TRAVIS_BUILD_DIR"/docs/public
|
GH_PAGES_DIR="$TRAVIS_BUILD_DIR"/docs/public
|
||||||
git config --global user.name "Travis CI"
|
git config --global user.name "Travis CI"
|
||||||
git config --global user.email "travis@graphene-python.org"
|
git config --global user.email "travis@graphene-python.org"
|
||||||
git clone --branch gh-pages --depth=50 \
|
git clone --branch gh-pages-seg --depth=50 \
|
||||||
https://graphql-python-bot@github.com/graphql-python/graphene.git \
|
https://graphql-python-bot@github.com/graphql-python/graphene.git \
|
||||||
$GH_PAGES_DIR
|
$GH_PAGES_DIR
|
||||||
cd docs
|
cd docs
|
||||||
npm run build
|
./playground/graphene-js/build.sh
|
||||||
|
npm run deploy
|
||||||
cd $GH_PAGES_DIR
|
cd $GH_PAGES_DIR
|
||||||
git status
|
git status
|
||||||
|
git add --intent-to-add .
|
||||||
if ! git diff-index --quiet HEAD --; then
|
if ! git diff-index --quiet HEAD --; then
|
||||||
git add -A .
|
git add -A .
|
||||||
git commit -m "Rebuild website"
|
git commit -m "Rebuild website"
|
||||||
git push "https://${GITHUB_TOKEN}@github.com/graphql-python/graphene.git" gh-pages
|
git push "https://${GITHUB_TOKEN}@github.com/graphql-python/graphene.git" gh-pages-seg
|
||||||
fi
|
fi
|
||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
1079
docs/css/graphiql.css
Normal file
1079
docs/css/graphiql.css
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -1,12 +1,13 @@
|
||||||
@import 'nib'
|
@import 'nib'
|
||||||
@import 'jeet'
|
@import 'jeet'
|
||||||
|
|
||||||
@import 'hljs.css'
|
|
||||||
@import 'bm.css'
|
|
||||||
@import 'https://fonts.googleapis.com/css?family=Raleway:400,500,600,200,100&.css'
|
@import 'https://fonts.googleapis.com/css?family=Raleway:400,500,600,200,100&.css'
|
||||||
|
|
||||||
normalize-css()
|
normalize-css()
|
||||||
|
|
||||||
|
@import 'hljs.css'
|
||||||
|
@import 'bm.css'
|
||||||
|
|
||||||
$wrapper
|
$wrapper
|
||||||
center(960px, pad:20px)
|
center(960px, pad:20px)
|
||||||
|
|
||||||
|
@ -29,6 +30,9 @@ html, body
|
||||||
font-size 16px
|
font-size 16px
|
||||||
color #606060
|
color #606060
|
||||||
line-height 1.5
|
line-height 1.5
|
||||||
|
height 100%
|
||||||
|
margin 0
|
||||||
|
width 100%
|
||||||
|
|
||||||
.header
|
.header
|
||||||
clearfix()
|
clearfix()
|
||||||
|
@ -339,7 +343,7 @@ $title
|
||||||
margin-top 0
|
margin-top 0
|
||||||
padding-top 0
|
padding-top 0
|
||||||
|
|
||||||
.title
|
.page-title
|
||||||
background: #F9F9F9;
|
background: #F9F9F9;
|
||||||
padding 48px 0
|
padding 48px 0
|
||||||
|
|
||||||
|
|
147
docs/css/playground.styl
Normal file
147
docs/css/playground.styl
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
@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;
|
|
@ -3,27 +3,39 @@ var jeet = require("jeet");
|
||||||
var rupture = require("rupture");
|
var rupture = require("rupture");
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var ExtractTextPlugin = require("extract-text-webpack-plugin");
|
var ExtractTextPlugin = require("extract-text-webpack-plugin");
|
||||||
|
var webpack = require("webpack");
|
||||||
var CopyWebpackPlugin = require('copy-webpack-plugin');
|
var CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
|
|
||||||
module.exports = function(config, env) {
|
module.exports = function(config, env) {
|
||||||
var IS_STATIC = env === 'static';
|
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({
|
config.merge({
|
||||||
stylus: {
|
stylus: {
|
||||||
use: [nib(), jeet(), rupture()]
|
use: [nib(), jeet(), rupture()]
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: "[name].js",
|
||||||
|
publicPath: "/",
|
||||||
},
|
},
|
||||||
resolveLoader: {
|
resolveLoader: {
|
||||||
root: path.join(__dirname, "node_modules"),
|
root: path.join(__dirname, "node_modules"),
|
||||||
modulesDirectories: ['./'],
|
modulesDirectories: ['./'],
|
||||||
alias: {
|
|
||||||
'copy': 'file-loader?name=[path][name].[ext]&context=./static',
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
root: path.join(__dirname, "node_modules"),
|
root: path.join(__dirname, "node_modules"),
|
||||||
alias: {
|
alias: {
|
||||||
'original-react': path.join(__dirname, "node_modules", "react"),
|
'original-react': path.join(__dirname, "node_modules", "react"),
|
||||||
'react/lib': path.join(__dirname, "node_modules", "react", "lib"),
|
'react/lib': path.join(__dirname, "node_modules", "react", "lib"),
|
||||||
'react': path.join(__dirname, 'patched-react.js')
|
'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: ['./']
|
modulesDirectories: ['./']
|
||||||
}
|
}
|
||||||
|
@ -32,6 +44,13 @@ module.exports = function(config, env) {
|
||||||
config.plugin('extract-css', ExtractTextPlugin, ["app.css"]);
|
config.plugin('extract-css', ExtractTextPlugin, ["app.css"]);
|
||||||
}
|
}
|
||||||
config.plugin('static', CopyWebpackPlugin, [[{ from: '../static'}]]);
|
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) {
|
config.loader('stylus', function(cfg) {
|
||||||
cfg.test = /\.styl$/;
|
cfg.test = /\.styl$/;
|
||||||
|
|
|
@ -5,7 +5,7 @@ export default class Html extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
var title = this.props.title || DocumentTitle.rewind();
|
var title = this.props.title || DocumentTitle.rewind();
|
||||||
return (
|
return (
|
||||||
<html lang={this.props.lang}>
|
<html lang={this.props.lang} manifest="/graphene.appcache">
|
||||||
<head>
|
<head>
|
||||||
<meta charSet="utf-8"/>
|
<meta charSet="utf-8"/>
|
||||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge"/>
|
<meta httpEquiv="X-UA-Compatible" content="IE=edge"/>
|
||||||
|
|
|
@ -13,9 +13,13 @@
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"codemirror": "5.9.0",
|
||||||
"copy-webpack-plugin": "^0.2.0",
|
"copy-webpack-plugin": "^0.2.0",
|
||||||
|
"es6-promise": "^3.0.2",
|
||||||
"extract-text-webpack-plugin": "^0.9.1",
|
"extract-text-webpack-plugin": "^0.9.1",
|
||||||
"gatsby": "^0.7.2",
|
"gatsby": "^0.7.3",
|
||||||
|
"graphiql": "^0.4.2",
|
||||||
|
"graphql": "^0.4.13",
|
||||||
"jeet": "^6.1.2",
|
"jeet": "^6.1.2",
|
||||||
"lodash": "^3.10.1",
|
"lodash": "^3.10.1",
|
||||||
"nib": "^1.1.0",
|
"nib": "^1.1.0",
|
||||||
|
|
9
docs/pages/_empty.js
Normal file
9
docs/pages/_empty.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
class Empty extends React.Component {
|
||||||
|
render() {
|
||||||
|
return <div />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Empty;
|
|
@ -14,7 +14,7 @@ class Template extends React.Component {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Menu width={200} right>
|
<Menu width={200} right>
|
||||||
<span><Link to="/try/">Try it out</Link></span>
|
<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="/docs/quickstart/" className={path.indexOf('/docs')==0?"active":null}>Docs</Link></span>
|
||||||
<span><Link to="/community/">Community</Link></span>
|
<span><Link to="/community/">Community</Link></span>
|
||||||
<a href="https://github.com/graphql-python/graphene/">Github</a>
|
<a href="https://github.com/graphql-python/graphene/">Github</a>
|
||||||
|
@ -26,7 +26,7 @@ class Template extends React.Component {
|
||||||
Graphene
|
Graphene
|
||||||
</Link>
|
</Link>
|
||||||
<nav className="header-nav">
|
<nav className="header-nav">
|
||||||
<Link to="/try/">Try it out</Link>
|
<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="/docs/quickstart/" className={path.indexOf('/docs')==0?"active":null}>Docs</Link>
|
||||||
<Link to="/community/">Community</Link>
|
<Link to="/community/">Community</Link>
|
||||||
<a href="https://github.com/graphql-python/graphene/">Github</a>
|
<a href="https://github.com/graphql-python/graphene/">Github</a>
|
||||||
|
|
|
@ -34,7 +34,7 @@ class Template extends React.Component {
|
||||||
var next_page = pages[next_page_index];
|
var next_page = pages[next_page_index];
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="title"><h1>Documentation</h1></div>
|
<div className="page-title"><h1>Documentation</h1></div>
|
||||||
<div className="docs">
|
<div className="docs">
|
||||||
<aside className="docs-aside">
|
<aside className="docs-aside">
|
||||||
{aside_links}
|
{aside_links}
|
||||||
|
|
13
docs/pages/playground.js
Normal file
13
docs/pages/playground.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
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;
|
234
docs/playground/GraphenePlayground.js
Normal file
234
docs/playground/GraphenePlayground.js
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
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;
|
4
docs/playground/examples/hello.graphql
Normal file
4
docs/playground/examples/hello.graphql
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
query {
|
||||||
|
hello
|
||||||
|
ping(to:"Peter")
|
||||||
|
}
|
13
docs/playground/examples/hello.schema.py
Normal file
13
docs/playground/examples/hello.schema.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
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)
|
8
docs/playground/examples/tea_store.graphql
Normal file
8
docs/playground/examples/tea_store.graphql
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
query {
|
||||||
|
store {
|
||||||
|
teas(orderBy:"name") {
|
||||||
|
name
|
||||||
|
steepingTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
docs/playground/examples/tea_store.schema.py
Normal file
38
docs/playground/examples/tea_store.schema.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
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
Normal file
1
docs/playground/graphene-js/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pypy-release-nojit
|
107
docs/playground/graphene-js/FunctionPromise.js
Normal file
107
docs/playground/graphene-js/FunctionPromise.js
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
//
|
||||||
|
// 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;
|
||||||
|
}
|
36
docs/playground/graphene-js/build.sh
Executable file
36
docs/playground/graphene-js/build.sh
Executable file
|
@ -0,0 +1,36 @@
|
||||||
|
#!/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 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
|
976
docs/playground/graphene-js/pypyjs.js
Normal file
976
docs/playground/graphene-js/pypyjs.js
Normal file
|
@ -0,0 +1,976 @@
|
||||||
|
//
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
})();
|
150
docs/playground/page.js
Normal file
150
docs/playground/page.js
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
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;
|
15
docs/playground/schema.js
Normal file
15
docs/playground/schema.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import {
|
||||||
|
GraphQLObjectType,
|
||||||
|
GraphQLString,
|
||||||
|
GraphQLSchema,
|
||||||
|
} from 'graphql';
|
||||||
|
|
||||||
|
|
||||||
|
export default new GraphQLSchema({
|
||||||
|
query: new GraphQLObjectType({
|
||||||
|
name: 'Query',
|
||||||
|
fields: () => ({
|
||||||
|
__emptyField: {type: GraphQLString},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
21
docs/playground/wrapper.js
Normal file
21
docs/playground/wrapper.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
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;
|
BIN
docs/static/favicon.png
vendored
BIN
docs/static/favicon.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
7
docs/static/graphene.appcache
vendored
Normal file
7
docs/static/graphene.appcache
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
CACHE MANIFEST
|
||||||
|
# 2015-11-27 v1.0.0
|
||||||
|
/playground/lib/pypyjs.vm.js
|
||||||
|
/playground/lib/pypyjs.vm.js.zmem
|
||||||
|
|
||||||
|
NETWORK:
|
||||||
|
*
|
|
@ -13,7 +13,7 @@ class Markdown extends React.Component {
|
||||||
return (
|
return (
|
||||||
<DocumentTitle title={`${post.title?post.title+' - ':''}${this.props.config.siteTitle}`}>
|
<DocumentTitle title={`${post.title?post.title+' - ':''}${this.props.config.siteTitle}`}>
|
||||||
<div>
|
<div>
|
||||||
{showTitle?<div className="title">
|
{showTitle?<div className="page-title">
|
||||||
<h1>{post.title}</h1>
|
<h1>{post.title}</h1>
|
||||||
</div>:null}
|
</div>:null}
|
||||||
<div className="markdown">
|
<div className="markdown">
|
||||||
|
|
31
examples/complex_example.py
Normal file
31
examples/complex_example.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import graphene
|
||||||
|
|
||||||
|
|
||||||
|
class GeoInput(graphene.InputObjectType):
|
||||||
|
lat = graphene.Float(required=True)
|
||||||
|
lng = graphene.Float(required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Address(graphene.ObjectType):
|
||||||
|
latlng = graphene.String()
|
||||||
|
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
address = graphene.Field(Address, geo=graphene.Argument(GeoInput))
|
||||||
|
|
||||||
|
def resolve_address(self, args, info):
|
||||||
|
geo = args.get('geo')
|
||||||
|
return Address(latlng="({},{})".format(geo.get('lat'), geo.get('lng')))
|
||||||
|
|
||||||
|
|
||||||
|
schema = graphene.Schema(query=Query)
|
||||||
|
query = '''
|
||||||
|
query something{
|
||||||
|
address(geo: {lat:32.2, lng:12}) {
|
||||||
|
latlng
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
||||||
|
result = schema.execute(query)
|
||||||
|
print(result.data['address']['latlng'])
|
|
@ -21,7 +21,7 @@ query = '''
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
result = schema.execute(query)
|
result = schema.execute(query)
|
||||||
print(result.data['patron'])
|
print(result.data['patron'])
|
|
@ -1,4 +1,5 @@
|
||||||
from graphene.contrib.django.types import (
|
from graphene.contrib.django.types import (
|
||||||
|
DjangoConnection,
|
||||||
DjangoObjectType,
|
DjangoObjectType,
|
||||||
DjangoInterface,
|
DjangoInterface,
|
||||||
DjangoNode
|
DjangoNode
|
||||||
|
@ -9,4 +10,4 @@ from graphene.contrib.django.fields import (
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = ['DjangoObjectType', 'DjangoInterface', 'DjangoNode',
|
__all__ = ['DjangoObjectType', 'DjangoInterface', 'DjangoNode',
|
||||||
'DjangoConnectionField', 'DjangoModelField']
|
'DjangoConnection', 'DjangoConnectionField', 'DjangoModelField']
|
||||||
|
|
|
@ -1,26 +1,22 @@
|
||||||
|
import warnings
|
||||||
|
|
||||||
from ...core.exceptions import SkipField
|
from ...core.exceptions import SkipField
|
||||||
from ...core.fields import Field
|
from ...core.fields import Field
|
||||||
from ...core.types.base import FieldType
|
from ...core.types.base import FieldType
|
||||||
from ...core.types.definitions import List
|
from ...core.types.definitions import List
|
||||||
from ...relay import ConnectionField
|
from ...relay import ConnectionField
|
||||||
from ...relay.utils import is_node
|
from ...relay.utils import is_node
|
||||||
from .utils import get_type_for_model, lazy_map
|
from .utils import get_type_for_model
|
||||||
|
|
||||||
|
|
||||||
class DjangoConnectionField(ConnectionField):
|
class DjangoConnectionField(ConnectionField):
|
||||||
|
|
||||||
def wrap_resolved(self, value, instance, args, info):
|
def __init__(self, *args, **kwargs):
|
||||||
return lazy_map(value, self.type)
|
cls = self.__class__
|
||||||
|
warnings.warn("Using {} will be not longer supported."
|
||||||
|
" Use relay.ConnectionField instead".format(cls.__name__),
|
||||||
class LazyListField(Field):
|
FutureWarning)
|
||||||
|
return super(DjangoConnectionField, self).__init__(*args, **kwargs)
|
||||||
def get_type(self, schema):
|
|
||||||
return List(self.type)
|
|
||||||
|
|
||||||
def resolver(self, instance, args, info):
|
|
||||||
resolved = super(LazyListField, self).resolver(instance, args, info)
|
|
||||||
return lazy_map(resolved, self.type)
|
|
||||||
|
|
||||||
|
|
||||||
class ConnectionOrListField(Field):
|
class ConnectionOrListField(Field):
|
||||||
|
@ -33,8 +29,8 @@ class ConnectionOrListField(Field):
|
||||||
if is_node(field_object_type):
|
if is_node(field_object_type):
|
||||||
field = DjangoConnectionField(field_object_type)
|
field = DjangoConnectionField(field_object_type)
|
||||||
else:
|
else:
|
||||||
field = LazyListField(field_object_type)
|
field = Field(List(field_object_type))
|
||||||
field.contribute_to_class(self.object_type, self.name)
|
field.contribute_to_class(self.object_type, self.attname)
|
||||||
return schema.T(field)
|
return schema.T(field)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ def test_node_replacedfield():
|
||||||
|
|
||||||
|
|
||||||
def test_interface_resolve_type():
|
def test_interface_resolve_type():
|
||||||
resolve_type = Character.resolve_type(schema, Human())
|
resolve_type = Character._resolve_type(schema, Human())
|
||||||
assert isinstance(resolve_type, GraphQLObjectType)
|
assert isinstance(resolve_type, GraphQLObjectType)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ import six
|
||||||
|
|
||||||
from ...core.types import BaseObjectType, ObjectTypeMeta
|
from ...core.types import BaseObjectType, ObjectTypeMeta
|
||||||
from ...relay.fields import GlobalIDField
|
from ...relay.fields import GlobalIDField
|
||||||
from ...relay.types import BaseNode
|
from ...relay.types import BaseNode, Connection
|
||||||
from .converter import convert_django_field
|
from .converter import convert_django_field
|
||||||
from .options import DjangoOptions
|
from .options import DjangoOptions
|
||||||
from .utils import get_reverse_fields
|
from .utils import get_reverse_fields, maybe_queryset
|
||||||
|
|
||||||
|
|
||||||
class DjangoObjectTypeMeta(ObjectTypeMeta):
|
class DjangoObjectTypeMeta(ObjectTypeMeta):
|
||||||
|
@ -30,7 +30,7 @@ class DjangoObjectTypeMeta(ObjectTypeMeta):
|
||||||
is_excluded = field.name in cls._meta.exclude_fields or is_already_created
|
is_excluded = field.name in cls._meta.exclude_fields or is_already_created
|
||||||
if is_not_in_only or is_excluded:
|
if is_not_in_only or is_excluded:
|
||||||
# We skip this field if we specify only_fields and is not
|
# We skip this field if we specify only_fields and is not
|
||||||
# in there. Or when we excldue this field in exclude_fields
|
# in there. Or when we exclude this field in exclude_fields
|
||||||
continue
|
continue
|
||||||
converted_field = convert_django_field(field)
|
converted_field = convert_django_field(field)
|
||||||
cls.add_to_class(field.name, converted_field)
|
cls.add_to_class(field.name, converted_field)
|
||||||
|
@ -71,6 +71,14 @@ class DjangoInterface(six.with_metaclass(
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DjangoConnection(Connection):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_list(cls, iterable, *args, **kwargs):
|
||||||
|
iterable = maybe_queryset(iterable)
|
||||||
|
return super(DjangoConnection, cls).from_list(iterable, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class DjangoNode(BaseNode, DjangoInterface):
|
class DjangoNode(BaseNode, DjangoInterface):
|
||||||
id = GlobalIDField()
|
id = GlobalIDField()
|
||||||
|
|
||||||
|
@ -81,3 +89,5 @@ class DjangoNode(BaseNode, DjangoInterface):
|
||||||
return cls(instance)
|
return cls(instance)
|
||||||
except cls._meta.model.DoesNotExist:
|
except cls._meta.model.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
connection_type = DjangoConnection
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.manager import Manager
|
from django.db.models.manager import Manager
|
||||||
from django.db.models.query import QuerySet
|
|
||||||
|
|
||||||
from ...utils import LazyMap
|
|
||||||
|
|
||||||
|
|
||||||
def get_type_for_model(schema, model):
|
def get_type_for_model(schema, model):
|
||||||
|
@ -22,9 +19,7 @@ def get_reverse_fields(model):
|
||||||
yield related
|
yield related
|
||||||
|
|
||||||
|
|
||||||
def lazy_map(value, func):
|
def maybe_queryset(value):
|
||||||
if isinstance(value, Manager):
|
if isinstance(value, Manager):
|
||||||
value = value.get_queryset()
|
value = value.get_queryset()
|
||||||
if isinstance(value, QuerySet):
|
|
||||||
return LazyMap(value, func)
|
|
||||||
return value
|
return value
|
||||||
|
|
|
@ -13,7 +13,10 @@ class Character(Interface):
|
||||||
|
|
||||||
|
|
||||||
class Pet(ObjectType):
|
class Pet(ObjectType):
|
||||||
type = String(resolver=lambda *_: 'Dog')
|
type = String()
|
||||||
|
|
||||||
|
def resolve_type(self, args, info):
|
||||||
|
return 'Dog'
|
||||||
|
|
||||||
|
|
||||||
class Human(Character):
|
class Human(Character):
|
||||||
|
|
|
@ -182,7 +182,7 @@ class BaseObjectType(BaseType):
|
||||||
signals.post_init.send(self.__class__, instance=self)
|
signals.post_init.send(self.__class__, instance=self)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resolve_type(cls, schema, instance, *args):
|
def _resolve_type(cls, schema, instance, *args):
|
||||||
return schema.T(instance.__class__)
|
return schema.T(instance.__class__)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -191,7 +191,7 @@ class BaseObjectType(BaseType):
|
||||||
return GraphQLInterfaceType(
|
return GraphQLInterfaceType(
|
||||||
cls._meta.type_name,
|
cls._meta.type_name,
|
||||||
description=cls._meta.description,
|
description=cls._meta.description,
|
||||||
resolve_type=partial(cls.resolve_type, schema),
|
resolve_type=partial(cls._resolve_type, schema),
|
||||||
fields=partial(cls.get_fields, schema)
|
fields=partial(cls.get_fields, schema)
|
||||||
)
|
)
|
||||||
elif cls._meta.is_union:
|
elif cls._meta.is_union:
|
||||||
|
@ -219,6 +219,10 @@ class BaseObjectType(BaseType):
|
||||||
|
|
||||||
return OrderedDict(fields)
|
return OrderedDict(fields)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def wrap(cls, instance, args, info):
|
||||||
|
return cls(_root=instance)
|
||||||
|
|
||||||
|
|
||||||
class Interface(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
|
class Interface(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -75,7 +75,7 @@ def test_union_cannot_initialize():
|
||||||
|
|
||||||
|
|
||||||
def test_interface_resolve_type():
|
def test_interface_resolve_type():
|
||||||
resolve_type = Character.resolve_type(schema, Human(object()))
|
resolve_type = Character._resolve_type(schema, Human(object()))
|
||||||
assert isinstance(resolve_type, GraphQLObjectType)
|
assert isinstance(resolve_type, GraphQLObjectType)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
from collections import Iterable
|
|
||||||
|
|
||||||
from graphql_relay.connection.arrayconnection import connection_from_list
|
|
||||||
from graphql_relay.node.node import from_global_id
|
from graphql_relay.node.node import from_global_id
|
||||||
|
|
||||||
from ..core.fields import Field
|
from ..core.fields import Field
|
||||||
|
@ -30,24 +27,12 @@ class ConnectionField(Field):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def resolver(self, instance, args, info):
|
def resolver(self, instance, args, info):
|
||||||
from graphene.relay.types import PageInfo
|
|
||||||
schema = info.schema.graphene_schema
|
schema = info.schema.graphene_schema
|
||||||
|
connection_type = self.get_type(schema)
|
||||||
resolved = super(ConnectionField, self).resolver(instance, args, info)
|
resolved = super(ConnectionField, self).resolver(instance, args, info)
|
||||||
if resolved:
|
if isinstance(resolved, connection_type):
|
||||||
resolved = self.wrap_resolved(resolved, instance, args, info)
|
return resolved
|
||||||
assert isinstance(
|
return connection_type.from_list(resolved, args, info)
|
||||||
resolved, Iterable), 'Resolved value from the connection field have to be iterable'
|
|
||||||
type = schema.T(self.type)
|
|
||||||
node = schema.objecttype(type)
|
|
||||||
connection_type = self.get_connection_type(node)
|
|
||||||
edge_type = self.get_edge_type(node)
|
|
||||||
|
|
||||||
connection = connection_from_list(
|
|
||||||
resolved, args, connection_type=connection_type,
|
|
||||||
edge_type=edge_type, pageinfo_type=PageInfo)
|
|
||||||
connection.set_connection_data(resolved)
|
|
||||||
return connection
|
|
||||||
|
|
||||||
def get_connection_type(self, node):
|
def get_connection_type(self, node):
|
||||||
connection_type = self.connection_type or node.get_connection_type()
|
connection_type = self.connection_type or node.get_connection_type()
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import inspect
|
import inspect
|
||||||
import warnings
|
import warnings
|
||||||
|
from collections import Iterable
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
|
from graphql_relay.connection.arrayconnection import connection_from_list
|
||||||
from graphql_relay.node.node import to_global_id
|
from graphql_relay.node.node import to_global_id
|
||||||
|
|
||||||
from ..core.types import (Boolean, Field, InputObjectType, Interface, List,
|
from ..core.types import (Boolean, Field, InputObjectType, Interface, List,
|
||||||
|
@ -63,6 +66,16 @@ class Connection(ObjectType):
|
||||||
(cls,),
|
(cls,),
|
||||||
{'edge_type': edge_type, 'edges': edges})
|
{'edge_type': edge_type, 'edges': edges})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_list(cls, iterable, args, info):
|
||||||
|
assert isinstance(
|
||||||
|
iterable, Iterable), 'Resolved value from the connection field have to be iterable'
|
||||||
|
connection = connection_from_list(
|
||||||
|
iterable, args, connection_type=cls,
|
||||||
|
edge_type=cls.edge_type, pageinfo_type=PageInfo)
|
||||||
|
connection.set_connection_data(iterable)
|
||||||
|
return connection
|
||||||
|
|
||||||
def set_connection_data(self, data):
|
def set_connection_data(self, data):
|
||||||
self._connection_data = data
|
self._connection_data = data
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
from blinker import Signal
|
try:
|
||||||
|
from blinker import Signal
|
||||||
|
except ImportError:
|
||||||
|
class Signal(object):
|
||||||
|
def send(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
init_schema = Signal()
|
init_schema = Signal()
|
||||||
class_prepared = Signal()
|
class_prepared = Signal()
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
from .str_converters import to_camel_case, to_snake_case
|
from .str_converters import to_camel_case, to_snake_case
|
||||||
from .proxy_snake_dict import ProxySnakeDict
|
from .proxy_snake_dict import ProxySnakeDict
|
||||||
from .caching import cached_property, memoize
|
from .caching import cached_property, memoize
|
||||||
from .lazymap import LazyMap
|
|
||||||
from .misc import enum_to_graphql_enum
|
from .misc import enum_to_graphql_enum
|
||||||
from .resolve_only_args import resolve_only_args
|
from .resolve_only_args import resolve_only_args
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['to_camel_case', 'to_snake_case', 'ProxySnakeDict',
|
__all__ = ['to_camel_case', 'to_snake_case', 'ProxySnakeDict',
|
||||||
'cached_property', 'memoize', 'LazyMap', 'enum_to_graphql_enum',
|
'cached_property', 'memoize', 'enum_to_graphql_enum',
|
||||||
'resolve_only_args']
|
'resolve_only_args']
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
class LazyMap(object):
|
|
||||||
|
|
||||||
def __init__(self, origin, _map, state=None):
|
|
||||||
self._origin = origin
|
|
||||||
self._origin_iter = origin.__iter__()
|
|
||||||
self._state = state or []
|
|
||||||
self._finished = False
|
|
||||||
self._map = _map
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return self if not self._finished else iter(self._state)
|
|
||||||
|
|
||||||
def iter(self):
|
|
||||||
return self.__iter__()
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return self._origin.__len__()
|
|
||||||
|
|
||||||
def __next__(self):
|
|
||||||
try:
|
|
||||||
n = next(self._origin_iter)
|
|
||||||
n = self._map(n)
|
|
||||||
except StopIteration as e:
|
|
||||||
self._finished = True
|
|
||||||
raise e
|
|
||||||
else:
|
|
||||||
self._state.append(n)
|
|
||||||
return n
|
|
||||||
|
|
||||||
def next(self):
|
|
||||||
return self.__next__()
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
item = self._origin[key]
|
|
||||||
if isinstance(key, slice):
|
|
||||||
return LazyMap(item, self._map)
|
|
||||||
return self._map(item)
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
return getattr(self._origin, name)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<LazyMap %s>" % repr(self._origin)
|
|
|
@ -1,23 +0,0 @@
|
||||||
from py.test import raises
|
|
||||||
|
|
||||||
from ..lazymap import LazyMap
|
|
||||||
|
|
||||||
|
|
||||||
def test_lazymap():
|
|
||||||
data = list(range(10))
|
|
||||||
lm = LazyMap(data, lambda x: 2 * x)
|
|
||||||
assert len(lm) == 10
|
|
||||||
assert lm[1] == 2
|
|
||||||
assert isinstance(lm[1:4], LazyMap)
|
|
||||||
assert lm.append == data.append
|
|
||||||
assert repr(lm) == '<LazyMap [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>'
|
|
||||||
|
|
||||||
|
|
||||||
def test_lazymap_iter():
|
|
||||||
data = list(range(2))
|
|
||||||
lm = LazyMap(data, lambda x: 2 * x)
|
|
||||||
iter_lm = iter(lm)
|
|
||||||
assert iter_lm.next() == 0
|
|
||||||
assert iter_lm.next() == 2
|
|
||||||
with raises(StopIteration):
|
|
||||||
iter_lm.next()
|
|
|
@ -1,6 +1,9 @@
|
||||||
[flake8]
|
[flake8]
|
||||||
exclude = setup.py
|
exclude = setup.py,docs/*
|
||||||
max-line-length = 120
|
max-line-length = 120
|
||||||
|
|
||||||
[coverage:run]
|
[coverage:run]
|
||||||
omit = core/ntypes/tests/*
|
omit = core/ntypes/tests/*
|
||||||
|
|
||||||
|
[isort]
|
||||||
|
known_first_party=graphene
|
||||||
|
|
3
setup.py
3
setup.py
|
@ -24,7 +24,7 @@ class PyTest(TestCommand):
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='graphene',
|
name='graphene',
|
||||||
version='0.4.1.1',
|
version='0.4.2',
|
||||||
|
|
||||||
description='Graphene: Python DSL for GraphQL',
|
description='Graphene: Python DSL for GraphQL',
|
||||||
long_description=open('README.rst').read(),
|
long_description=open('README.rst').read(),
|
||||||
|
@ -55,7 +55,6 @@ setup(
|
||||||
|
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'six>=1.10.0',
|
'six>=1.10.0',
|
||||||
'blinker',
|
|
||||||
'graphql-core==0.4.9',
|
'graphql-core==0.4.9',
|
||||||
'graphql-relay==0.3.3'
|
'graphql-relay==0.3.3'
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in New Issue
Block a user