├── api ├── __init__.py ├── models.py ├── requirements.txt ├── routes.py ├── test_app.py ├── config.py └── main.py ├── .husky ├── .gitignore └── pre-commit ├── .prettierignore ├── src ├── __tests__ │ ├── index.html │ ├── index.js │ ├── header │ │ ├── EmbedCode.spec.js │ │ ├── Download.spec.js │ │ ├── Commit.spec.js │ │ ├── Share.spec.js │ │ ├── Run.spec.js │ │ └── Header.spec.js │ ├── test.js │ └── panes │ │ ├── PaneLeft.spec.js │ │ ├── PaneRight.spec.js │ │ └── PaneSwitch.spec.js ├── main.js ├── App.vue ├── header │ ├── Commit.vue │ ├── Share.vue │ ├── Header.vue │ ├── Download.vue │ ├── Run.vue │ └── EmbedCode.vue ├── panes │ ├── PaneRight.vue │ ├── PaneSwitch.vue │ ├── PaneLeft.vue │ └── PaneSplit.vue ├── codemirror │ ├── CodeMirror.vue │ ├── python.js │ ├── theme.js │ └── codemirror.js ├── assets │ └── logo.svg └── store.js ├── app.json ├── .gitignore ├── heroku.yml ├── .github ├── renovate.json ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md ├── workflows │ ├── release.yml │ └── ci.yml └── contributing.md ├── public └── _headers ├── netlify.toml ├── scripts ├── install_deps.sh ├── run_code_style.sh ├── gen_pycompletions.py └── release.js ├── Dockerfile ├── windi.config.js ├── LICENSE ├── vite.config.js ├── package.json ├── index.html ├── CHANGELOG.md ├── README.md └── pnpm-lock.yaml /api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | pnpm-lock.yaml 3 | -------------------------------------------------------------------------------- /src/__tests__/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/__tests__/index.js: -------------------------------------------------------------------------------- 1 | const modules = import.meta.globEager('./**/*.spec.js') 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | pnpm lint-staged 5 | -------------------------------------------------------------------------------- /api/models.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class InputCode(BaseModel): 5 | source: str 6 | -------------------------------------------------------------------------------- /api/requirements.txt: -------------------------------------------------------------------------------- 1 | uvicorn[standard]==0.17.6 2 | pydantic==1.9.1 3 | fastapi==0.78.0 4 | asyncpg==0.25.0 5 | gunicorn==20.1.0 6 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildpacks": [ 3 | { 4 | "url": "heroku/python" 5 | } 6 | ], 7 | "stack": "container" 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | .vscode 7 | *.log 8 | __pycache__ 9 | **/python.json 10 | -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | build: 2 | docker: 3 | web: Dockerfile 4 | run: 5 | web: gunicorn -w 4 -k uvicorn.workers.UvicornWorker api.main:app 6 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import 'virtual:windi.css' 4 | 5 | createApp(App).mount('#app') 6 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base", "schedule:weekly", "group:allNonMajor"], 3 | "labels": ["dependencies"], 4 | "pin": false, 5 | "rangeStrategy": "bump" 6 | } 7 | -------------------------------------------------------------------------------- /public/_headers: -------------------------------------------------------------------------------- 1 | /assets/* 2 | cache-control: max-age=31536000 3 | cache-control: immutable 4 | 5 | /* 6 | X-XSS-Protection: 1; mode=block 7 | Referrer-Policy: no-referrer 8 | X-Content-Type-Options: nosniff 9 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build.environment] 2 | NODE_VERSION = "14" 3 | PYTHON_VERSION = "3.7" 4 | 5 | [build] 6 | publish = "dist" 7 | command = "npx pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && npx pnpm run build" 8 | -------------------------------------------------------------------------------- /scripts/install_deps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -xeu 4 | 5 | pip install -r ./api/requirements.txt --no-cache-dir 6 | 7 | EXTRA=./api/requirements-extra.txt 8 | 9 | if [ -e $EXTRA ]; then 10 | pip install -r $EXTRA --no-cache-dir 11 | fi 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Questions & Discussions 4 | url: https://github.com/toyai/python-playground/discussions 5 | about: 6 | Use GitHub discussions for message-board style questions and discussions. 7 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10.4-slim-buster 2 | 3 | WORKDIR /workspace 4 | 5 | COPY ./ /workspace/ 6 | 7 | RUN apt-get update && \ 8 | apt-get upgrade -y && \ 9 | apt-get autoremove -y && \ 10 | apt-get clean && \ 11 | python -m pip install -U pip wheel setuptools --no-cache-dir && \ 12 | sh ./scripts/install_deps.sh 13 | 14 | CMD [ "gunicorn", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "api.main:app" ] 15 | -------------------------------------------------------------------------------- /src/header/Commit.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 18 | -------------------------------------------------------------------------------- /scripts/run_code_style.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -xeu 4 | 5 | if [ $1 = "lint" ]; then 6 | flake8 . --max-line-length 80 7 | isort . --check --profile black 8 | black . -l 80 --check 9 | elif [ $1 = "fmt" ]; then 10 | isort . --profile black 11 | black . -l 80 12 | elif [ $1 = "install" ]; then 13 | pip install \ 14 | pytest \ 15 | requests \ 16 | black \ 17 | isort \ 18 | flake8 \ 19 | flake8-bugbear \ 20 | flake8-comprehensions \ 21 | flake8-executable 22 | fi 23 | -------------------------------------------------------------------------------- /src/__tests__/header/EmbedCode.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import EmbedCode from '../../header/EmbedCode.vue' 3 | import { test } from 'uvu' 4 | import * as assert from 'uvu/assert' 5 | 6 | test('EmbedCode button', () => { 7 | const wrapper = mount(EmbedCode) 8 | 9 | const embed = wrapper.get('[data-test="embedText"]') 10 | 11 | assert.ok(embed.exists(), 'embed text should exist.') 12 | assert.ok(embed.isVisible(), 'embed text should be visible.') 13 | }) 14 | 15 | test.run() 16 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Description 4 | 5 | fix # 6 | 7 | 8 | 9 | ### Additional context 10 | 11 | 12 | 13 | --- 14 | 15 | ### What is the purpose of this pull request? 16 | 17 | - [ ] Bug fix 18 | - [ ] New feature 19 | - [ ] Other 20 | -------------------------------------------------------------------------------- /src/__tests__/header/Download.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import Download from '../../header/Download.vue' 3 | import { test } from 'uvu' 4 | import * as assert from 'uvu/assert' 5 | 6 | test('download button', () => { 7 | const wrapper = mount(Download) 8 | 9 | const download = wrapper.get('[data-test="downloadText"]') 10 | 11 | assert.ok(download.exists(), 'Download text should exist.') 12 | assert.ok(download.isVisible(), 'Download text should be visible.') 13 | }) 14 | 15 | test.run() 16 | -------------------------------------------------------------------------------- /src/__tests__/header/Commit.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import { version } from '../../../package.json' 3 | import Commit from '../../header/Commit.vue' 4 | import { test } from 'uvu' 5 | import * as assert from 'uvu/assert' 6 | 7 | test('renders a commit', () => { 8 | const wrapper = mount(Commit) 9 | 10 | const commit = wrapper.get('[data-test="commit"]') 11 | 12 | assert.is( 13 | commit.text(), 14 | `v${version}@${__COMMIT__.slice(0, 7)}`, 15 | 'commit should match.', 16 | ) 17 | }) 18 | 19 | test.run() 20 | -------------------------------------------------------------------------------- /src/__tests__/header/Share.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import Share from '../../header/Share.vue' 3 | import { test } from 'uvu' 4 | import * as assert from 'uvu/assert' 5 | 6 | test('copy shareable URL', async () => { 7 | const wrapper = mount(Share) 8 | 9 | await wrapper.find('button').trigger('click') 10 | const share = wrapper.get('[data-test="shareText"]') 11 | 12 | assert.ok(share.exists(), 'Share text should exist.') 13 | assert.ok(share.isVisible(), 'Share text should be visible.') 14 | }) 15 | 16 | test.run() 17 | -------------------------------------------------------------------------------- /src/__tests__/header/Run.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import Run from '../../header/Run.vue' 3 | import { test } from 'uvu' 4 | import * as assert from 'uvu/assert' 5 | 6 | test('run code button', async () => { 7 | const wrapper = mount(Run) 8 | 9 | const status = wrapper.get('[data-test="runStatus"]') 10 | 11 | assert.is(status.text(), 'Run', 'Button should include Run') 12 | 13 | await wrapper.find('button').trigger('click') 14 | assert.is(status.text(), 'Running', 'Button should include Running') 15 | }) 16 | 17 | test.run() 18 | -------------------------------------------------------------------------------- /api/routes.py: -------------------------------------------------------------------------------- 1 | import io 2 | import traceback 3 | from contextlib import redirect_stdout 4 | 5 | from fastapi import APIRouter, Body 6 | 7 | from api.models import InputCode 8 | 9 | route = APIRouter() 10 | input = Body(...) 11 | 12 | 13 | @route.post("/") 14 | def index(input: InputCode = input): 15 | with io.StringIO() as f: 16 | with redirect_stdout(f): 17 | try: 18 | exec(input.source) 19 | except Exception: 20 | return traceback.format_exc() 21 | result = f.getvalue() 22 | return result 23 | -------------------------------------------------------------------------------- /windi.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { defineConfig } from 'windicss/helpers' 3 | 4 | export default defineConfig({ 5 | theme: { 6 | extend: { 7 | cursor: { 8 | 'ew-resize': 'ew-resize', 9 | }, 10 | }, 11 | }, 12 | alias: { 13 | 'header-btn': 'mx-1 inline-flex items-center content-center', 14 | 'file-tab-btn': 15 | 'cursor-pointer px-4 py-1 w-max h-32px border-b-3 border-solid border-b-blue-500 font-mono text-sm', 16 | 'pane-switch-btn': 17 | 'border rounded mx-1 py-1 px-4 hover:(bg-blue-600 text-white)', 18 | }, 19 | }) 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Bug Report' 3 | about: 'Submit a report to help us improve' 4 | title: '' 5 | labels: 'bug: pending triage' 6 | --- 7 | 8 | ### Reproduction link 9 | 10 | 11 | 12 | ### Steps to reproduce 13 | 14 | 15 | 16 | ### What is expected? 17 | 18 | 19 | 20 | ### What is actually happening? 21 | 22 | 23 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | tags: 5 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 6 | 7 | jobs: 8 | release: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v3 13 | 14 | - uses: yyx990803/release-tag@master 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | with: 18 | tag_name: ${{ github.ref }} 19 | body: | 20 | Please refer to [CHANGELOG.md](https://github.com/toyai/python-playground/blob/main/CHANGELOG.md) for details. 21 | -------------------------------------------------------------------------------- /src/panes/PaneRight.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 |