Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/serve pyodide locally #195

Merged
merged 11 commits into from
Nov 1, 2021
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
Development
-----------
- Serve pyodide JS files locally [PR #186](https://github.com/berinhard/pyp5js/pull/186)
- Create sketch using p5.js from CDN [PR #191](https://github.com/berinhard/pyp5js/pull/191)
- Add `keyIsDown` event to Transcrypt mode - [PR #187](https://github.com/berinhard/pyp5js/pull/187)
- Fix bug with multiple events calls - PR #187 too
- Serve JS files if `--local` flag [PR #195](https://github.com/berinhard/pyp5js/pull/195)

0.7.0
-----
Expand Down
4 changes: 2 additions & 2 deletions pyp5js/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ def command_line_entrypoint():
@click.option('--interpreter', '-i', type=click.Choice(AVAILABLE_INTERPRETERS), default=PYODIDE_INTERPRETER, help='Which python tool to use to run the sketch. (defaults to pyodide)')
@click.option('--template', '-t', type=click.Path(exists=True), help='Specify a custom index.html template to use.')
@click.option('--cdn/--local', default=True)
def configure_new_sketch(sketch_name, monitor, interpreter, template, use_cdn):
def configure_new_sketch(sketch_name, monitor, interpreter, template, cdn):
"""
Create dir and configure boilerplate - Example:\n
$ pyp5js new my_sketch -i pyodide
"""
files = commands.new_sketch(sketch_name, interpreter, template_file=template, use_cdn=use_cdn)
files = commands.new_sketch(sketch_name, interpreter, template_file=template, use_cdn=cdn)

cprint.ok(f"Your sketch was created!")

Expand Down
14 changes: 1 addition & 13 deletions pyp5js/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from cprint import cprint
from jinja2 import Environment, FileSystemLoader

from pyp5js.config.fs import PYP5JS_FILES
from pyp5js.compiler import compile_sketch_js
from pyp5js.exceptions import PythonSketchDoesNotExist
from pyp5js.sketch import Sketch
Expand All @@ -30,21 +29,10 @@ def new_sketch(sketch_name, interpreter=PYODIDE_INTERPRETER, template_file="", u
"interpreter": interpreter,
"index_template": template_file,
}
if not use_cdn:
cfg["p5_js_url"] = "/static/p5.js"
# TODO: static version for pyodide too

sketch = Sketch(sketch_name, **cfg)
sketch.create_sketch_dir()

templates_files = [
(sketch.config.get_base_sketch_template(), sketch.sketch_py),
]
if not use_cdn:
templates_files.append((PYP5JS_FILES.p5js, sketch.p5js))
# TODO: copy pyodide files to static dir too
for src, dest in templates_files:
shutil.copyfile(src, dest)
sketch.copy_initial_files(use_cdn=use_cdn)

index_contet = get_sketch_index_content(sketch)
with open(sketch.index_html, "w") as fd:
Expand Down
4 changes: 4 additions & 0 deletions pyp5js/config/fs.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,9 @@ def pyodide_index_html(self):
def pyodide_base_sketch_template(self):
return self.templates_dir.joinpath('pyodide', 'base_sketch.py.template')

@property
def pyodide_js_dir(self):
return self.static_dir / "js" / "pyodide"


PYP5JS_FILES = LibFiles()
9 changes: 7 additions & 2 deletions pyp5js/config/sketch.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
TRANSCRYPT_INTERPRETER = 'transcrypt'
PYODIDE_INTERPRETER = 'pyodide'
P5_JS_CDN = 'https://cdn.jsdelivr.net/npm/[email protected]/lib/p5.min.js'
PYODIDE_JS_CDN = 'https://cdn.jsdelivr.net/pyodide/v0.18.1/full/pyodide.js'


class SketchConfig:

Expand All @@ -19,6 +21,7 @@ def __init__(self, interpreter, **kwargs):
self.interpreter = interpreter
self.index_template = kwargs.get("index_template", "")
self.p5_js_url = kwargs.get("p5_js_url", P5_JS_CDN)
self.pyodide_js_url = kwargs.get("pyodide_js_url", PYODIDE_JS_CDN)

@property
def index_template_path(self):
Expand All @@ -31,10 +34,12 @@ def write(self, fname):
with open(fname, "w") as fd:
data = {
"interpreter": self.interpreter,
"index_template": index_template,
# TODO: also store pyodide_js_url
"p5_js_url": self.p5_js_url,
}
if self.index_template:
data.update({"index_template": index_template})
if self.is_pyodide:
data.update({"pyodide_js_url": self.pyodide_js_url})
json.dump(data, fd)

@property
Expand Down
12 changes: 6 additions & 6 deletions pyp5js/http/templates/view_sketch.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@
}

</style>

<script src="/static/js/p5/p5.js"></script>
{% if live_run %}
<script src="/static/js/pyodide/pyodide_v0.18.1.js"></script>
{% endif %}
{% endblock %}

{% block content %}
Expand Down Expand Up @@ -119,11 +124,6 @@
});
</script>

{% if live_run %}
<script src="/static/js/pyodide/pyodide_v0.18.1.js"></script>
{% endif %}

<script src="/static/p5/p5.js.min"></script>
<script src="{{ sketch_js_url }}" {% if js_as_module %}type="module"{% endif %}></script>

<script type="text/javascript">
Expand Down Expand Up @@ -171,7 +171,7 @@
{% endif %}
}
}

});
</script>

{% endblock %}
50 changes: 45 additions & 5 deletions pyp5js/sketch.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import os
import re
import shutil
from collections import namedtuple

from pyp5js import config
from pyp5js.config.fs import PYP5JS_FILES
from pyp5js.config.sketch import SketchConfig
from pyp5js.exceptions import SketchDirAlreadyExistException, InvalidName


SketchUrls = namedtuple('SketchUrls', ['p5_js_url', 'sketch_js_url'])
SketchUrls = namedtuple('SketchUrls', ['p5_js_url', 'pyodide_js_url', 'sketch_js_url'])


class Sketch:
Expand All @@ -29,10 +30,13 @@ def validate_name(self):
does_not_start_with_letter_or_underscore = r'^[^a-zA-Z_]'
contains_non_alphanumerics_except_underscore = r'[^a-zA-Z0-9_]'
if re.match(does_not_start_with_letter_or_underscore, self.sketch_name) or \
re.search(contains_non_alphanumerics_except_underscore, self.sketch_name):
re.search(contains_non_alphanumerics_except_underscore, self.sketch_name):
raise InvalidName(self)

def create_sketch_dir(self):
"""
Create sketch required directories
"""
self.validate_name()

if self.sketch_dir.exists():
Expand All @@ -43,6 +47,28 @@ def create_sketch_dir(self):
self.target_dir.mkdir()
self.config.write(self.config_file)

def copy_initial_files(self, use_cdn=True):
"""
Copy requlred template files to the sketch directory
"""
if not use_cdn:
self.config.p5_js_url = "/static/p5.js"
if self.config.is_pyodide:
self.config.pyodide_js_url = "/static/pyodide/pyodide_v0.18.1.js"

templates_files = [
(self.config.get_base_sketch_template(), self.sketch_py),
]
if not use_cdn:
templates_files.append((PYP5JS_FILES.p5js, self.p5js))
if self.config.is_pyodide:
shutil.copytree(PYP5JS_FILES.pyodide_js_dir, self.static_dir / "pyodide")
# delete packages.json that's not necessary
(self.static_dir / "pyodide" / "packages.json").unlink()

for src, dest in templates_files:
shutil.copyfile(src, dest)

@property
def sketch_exists(self):
return self.sketch_py.exists()
Expand All @@ -54,7 +80,6 @@ def sketch_content(self):
with self.sketch_py.open() as fd:
return fd.read()


@property
def has_all_files(self):
return all([
Expand Down Expand Up @@ -109,7 +134,22 @@ def __eq__(self, other):
@property
def urls(self):
return SketchUrls(
# TODO: add pyodide_js_url
p5_js_url=self.config.p5_js_url,
pyodide_js_url=self.config.pyodide_js_url,
sketch_js_url=f"{self.TARGET_NAME}/target_sketch.js",
)

def get_target_sketch_context(self):
"""
This method is used by the template renderers to get the context to be used
to render final target/target_sketch.js file.
"""
context = {
"sketch_name": self.sketch_name,
"sketch_content": self.sketch_content,
}
if self.config.is_pyodide:
index = "/".join(self.config.pyodide_js_url.split("/")[:-1]) + "/"
context.update({'pyodide_index_url': index})
return context

2 changes: 1 addition & 1 deletion pyp5js/templates/pyodide/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<title>{{ sketch_name }} - pyp5js (using pyodide)</title>
<style> body, html, canvas {padding: 0; margin: 0; overflow: hidden;} </style>

<script src="/static/js/pyodide/pyodide_v0.18.1.js"></script>
<script src="{{ pyodide_js_url }}"></script>
<script src="{{ p5_js_url }}"></script>
<script src="{{ sketch_js_url }}" type="module"></script>
</head>
Expand Down
5 changes: 3 additions & 2 deletions pyp5js/templates/pyodide/target_sketch.js.template
Original file line number Diff line number Diff line change
Expand Up @@ -1674,7 +1674,7 @@ function runCode() {

async function main() {
const config = {
indexURL : "/static/js/pyodide/", # TODO: replace hardcoded string by {{ indexURL }}
indexURL : "{{ pyodide_index_url }}",
fullStdLib: false,
}
window.pyodide = await loadPyodide(config);
Expand All @@ -1692,4 +1692,5 @@ async function main() {
runCode();
};

await main();
// async method
main();
8 changes: 2 additions & 6 deletions pyp5js/templates_renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def get_sketch_index_content(sketch):
context = {
"sketch_name": sketch.sketch_name,
"p5_js_url": sketch.urls.p5_js_url,
"pyodide_js_url": sketch.urls.pyodide_js_url,
"sketch_js_url": sketch.urls.sketch_js_url,
"sketch_content": sketch.sketch_content,
}
Expand All @@ -31,11 +32,6 @@ def get_target_sketch_content(sketch):
"""
Renders the content to be written in the temporary SKETCH_NAME/target_sketch.py file
"""
context = {
"sketch_name": sketch.sketch_name,
"sketch_content": sketch.sketch_content,
# TODO: if pyodide, add the pyodide indexUrl here too
# (details about this here https://github.com/berinhard/pyp5js/pull/186#pullrequestreview-782362038)
}
context = sketch.get_target_sketch_context()
target_js_file = sketch.config.get_target_js_template()
return _template_from_file(target_js_file, context)
8 changes: 8 additions & 0 deletions pyp5js/tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ def sketch():
yield files
shutil.rmtree(SKETCHBOOK_DIR)


@fixture()
def sketch_pyodide():
files = Sketch('foo', interpreter=PYODIDE_INTERPRETER)
files.create_sketch_dir()
yield files
shutil.rmtree(SKETCHBOOK_DIR)

@fixture
def transcrypt_json_file():
try:
Expand Down
1 change: 0 additions & 1 deletion pyp5js/tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,3 @@ def test_create_sketch_using_local_installed_assets(self):
commands.new_sketch(self.sketch_name, use_cdn=False)

assert self.sketch.p5js.exists()
# TODO: assertion on pyodide assets too
8 changes: 5 additions & 3 deletions pyp5js/tests/test_config/test_sketch.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from tempfile import NamedTemporaryFile

from pyp5js.config import TRANSCRYPT_INTERPRETER, PYODIDE_INTERPRETER
from pyp5js.config.sketch import SketchConfig, P5_JS_CDN
from pyp5js.config.sketch import SketchConfig, P5_JS_CDN, PYODIDE_JS_CDN
from pyp5js.config.fs import PYP5JS_FILES

from ..fixtures import transcrypt_json_file, pyodide_json_file, transcrypt_config, pyodide_config, custom_index_json_file
Expand Down Expand Up @@ -36,15 +36,17 @@ def test_write_sketch_interpreter_config(custom_index_json_file):
assert data == expected


def test_wrrite_defaults():
def test_write_defaults():
config = SketchConfig(PYODIDE_INTERPRETER)
fd = NamedTemporaryFile(mode="w", delete=False)
config.write(fd.name)
fd.close()
with open(fd.name) as fd:
data = json.load(fd)

assert "" == data["index_template"]
assert PYODIDE_INTERPRETER == data["interpreter"]
assert P5_JS_CDN == data["p5_js_url"]
assert PYODIDE_JS_CDN == data["pyodide_js_url"]


def test_get_transcrypt_index_template(transcrypt_config):
Expand Down
35 changes: 33 additions & 2 deletions pyp5js/tests/test_sketch.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from unittest import TestCase

from pyp5js.config import SKETCHBOOK_DIR, PYODIDE_INTERPRETER
from pyp5js.config.sketch import P5_JS_CDN
from pyp5js.config.sketch import P5_JS_CDN, PYODIDE_JS_CDN
from pyp5js.exceptions import SketchDirAlreadyExistException
from pyp5js.sketch import Sketch
from pyp5js.exceptions import InvalidName
Expand Down Expand Up @@ -90,12 +90,43 @@ def test_loads_config_from_config_file(self):
assert same_files.config.interpreter == PYODIDE_INTERPRETER

def test_sketch_custom_urls(self):
files = Sketch(self.files.sketch_name, p5_js_url="static/p5.js")
files = Sketch(self.files.sketch_name, p5_js_url="static/p5.js", pyodide_js_url="static/pyodide/pyodide.js")
urls = files.urls
assert "static/p5.js" == urls.p5_js_url
assert "static/pyodide/pyodide.js" == urls.pyodide_js_url
assert "target/target_sketch.js" == urls.sketch_js_url

def test_sketch_urls(self):
urls = self.files.urls
assert P5_JS_CDN == urls.p5_js_url
assert PYODIDE_JS_CDN == urls.pyodide_js_url
assert "target/target_sketch.js" == urls.sketch_js_url

def test_get_target_sketch_context_for_local_pyodide(self):
files = Sketch(
self.files.sketch_name, pyodide_js_url="static/pyodide/pyodide.js",
interpreter=PYODIDE_INTERPRETER)
expected_context = {
"sketch_name": files.sketch_name,
"sketch_content": files.sketch_content,
"pyodide_index_url": "static/pyodide/"
}
assert expected_context == files.get_target_sketch_context()

def test_copy_local_files_if_not_using_cdn(self):
files = Sketch('test', interpreter=PYODIDE_INTERPRETER)
files.create_sketch_dir()
files.copy_initial_files(use_cdn=False)
pyodide_js_dir = files.static_dir / "pyodide"

assert files.config.p5_js_url == "/static/p5.js"
assert files.config.pyodide_js_url == "/static/pyodide/pyodide_v0.18.1.js"
assert files.p5js.exists()
assert pyodide_js_dir.exists()

assert (pyodide_js_dir / "pyodide.asm.data").exists()
assert (pyodide_js_dir / "pyodide.asm.js").exists()
assert (pyodide_js_dir / "pyodide.asm.wasm").exists()
assert (pyodide_js_dir / "pyodide.js.map").exists()
assert (pyodide_js_dir / "pyodide_v0.18.1.js").exists()
assert not (pyodide_js_dir / "packages.json").exists()
Loading