Poetry+Hatch Monorepo (#1002)

* re-organize dirs + start using hatch

* setup root pyproject.toml + basic invoke tasks

* add publish task

* more ruff fixes

* get workflows to run

* split up script runs

* rename to check

* change matrix order

* make ruff happy

* get tests to pass

* check semver

* more fixes

* ignore missing coverage

* fix cov

* fix import sort

* try build in env-js

* try latest hatch-build-scripts

* misc fixes

* try to fix npm in gh action

* do not set registry url by default

* allow re-runs

* no need for extra build

* fix doc build and tests

* remove scripts

* fix tests

* update contributor guide
This commit is contained in:
Ryan Morshead 2023-06-03 15:56:52 -06:00 committed by GitHub
parent cf7950d468
commit ac582666eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
198 changed files with 3856 additions and 1913 deletions

View file

@ -1,4 +1,4 @@
name: Nox Session
name: hatch-run
on:
workflow_call:
@ -6,12 +6,9 @@ on:
job-name:
required: true
type: string
nox-args:
hatch-run:
required: true
type: string
nox-session-args:
required: false
type: string
runs-on-array:
required: false
type: string
@ -20,6 +17,10 @@ on:
required: false
type: string
default: '["3.x"]'
node-registry-url:
required: false
type: string
default: ""
secrets:
node-auth-token:
required: false
@ -29,19 +30,19 @@ on:
required: false
jobs:
nox-session:
hatch:
name: ${{ format(inputs.job-name, matrix.python-version, matrix.runs-on) }}
strategy:
matrix:
runs-on: ${{ fromJson(inputs.runs-on-array) }}
python-version: ${{ fromJson(inputs.python-version-array) }}
runs-on: ${{ fromJson(inputs.runs-on-array) }}
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: "14.x"
registry-url: "https://registry.npmjs.org"
registry-url: ${{ inputs.node-registry-url }}
- name: Pin NPM Version
run: npm install -g npm@8.19.3
- name: Use Python ${{ matrix.python-version }}
@ -49,10 +50,10 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
- name: Install Python Dependencies
run: pip install -r requirements/nox-deps.txt
- name: Run Sessions
run: pip install hatch poetry
- name: Run Scripts
env:
NODE_AUTH_TOKEN: ${{ secrets.node-auth-token }}
PYPI_USERNAME: ${{ secrets.pypi-username }}
PYPI_PASSWORD: ${{ secrets.pypi-password }}
run: nox ${{ inputs.nox-args }} --stop-on-first-error -- ${{ inputs.nox-session-args }}
run: hatch run ${{ inputs.hatch-run }}

45
.github/workflows/check.yml vendored Normal file
View file

@ -0,0 +1,45 @@
name: check
on:
push:
branches:
- main
pull_request:
branches:
- main
schedule:
- cron: "0 0 * * 0"
jobs:
test-py-cov:
uses: ./.github/workflows/.hatch-run.yml
with:
job-name: "python-{0}"
hatch-run: "test-py"
lint-py:
uses: ./.github/workflows/.hatch-run.yml
with:
job-name: "python-{0}"
hatch-run: "lint-py"
test-py-matrix:
uses: ./.github/workflows/.hatch-run.yml
with:
job-name: "python-{0} {1}"
hatch-run: "test-py --no-cov"
runs-on-array: '["ubuntu-latest", "macos-latest", "windows-latest"]'
python-version-array: '["3.9", "3.10", "3.11"]'
test-docs:
uses: ./.github/workflows/.hatch-run.yml
with:
job-name: "python-{0}"
hatch-run: "test-docs"
test-js:
uses: ./.github/workflows/.hatch-run.yml
with:
job-name: "{1}"
hatch-run: "test-js"
lint-js:
uses: ./.github/workflows/.hatch-run.yml
with:
job-name: "{1}"
hatch-run: "lint-js"

View file

@ -9,10 +9,11 @@ on:
jobs:
publish:
uses: ./.github/workflows/.nox-session.yml
uses: ./.github/workflows/.hatch-run.yml
with:
job-name: "publish"
nox-args: "-s publish"
hatch-run: "publish"
node-registry-url: "https://registry.npmjs.org"
secrets:
node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
pypi-username: ${{ secrets.PYPI_USERNAME }}

View file

@ -1,37 +0,0 @@
name: test
on:
push:
branches:
- main
pull_request:
branches:
- main
schedule:
- cron: "0 0 * * 0"
jobs:
python-exhaustive:
uses: ./.github/workflows/.nox-session.yml
with:
job-name: "python-{0}"
nox-args: "-t check-python"
nox-session-args: "--pytest --maxfail=3 --reruns 3"
python-environments:
uses: ./.github/workflows/.nox-session.yml
with:
job-name: "python-{0} {1}"
nox-args: "-s check-python-tests"
nox-session-args: "--no-cov --pytest --maxfail=3 --reruns 3"
runs-on-array: '["ubuntu-latest", "macos-latest", "windows-latest"]'
python-version-array: '["3.7", "3.8", "3.9", "3.10", "3.11"]'
docs:
uses: ./.github/workflows/.nox-session.yml
with:
job-name: "python-{0}"
nox-args: "-s check-docs"
javascript:
uses: ./.github/workflows/.nox-session.yml
with:
job-name: "{1}"
nox-args: "-t check-javascript"

3
.gitignore vendored
View file

@ -1,6 +1,3 @@
# --- Build Artifacts ---
src/reactpy/_client
# --- Jupyter ---
*.ipynb_checkpoints
*Untitled*.ipynb

View file

@ -1,14 +0,0 @@
repos:
- repo: https://github.com/ambv/black
rev: 23.1.0
hooks:
- id: black
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
name: isort
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.0.0-alpha.6
hooks:
- id: prettier

View file

@ -8,42 +8,26 @@ RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
RUN apt-get install -yq nodejs build-essential
RUN npm install -g npm@8.5.0
# Create Python Venv
# ------------------
ENV VIRTUAL_ENV=/opt/venv
RUN python3 -m venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
RUN pip install --upgrade pip
# Install ReactPy
# Install Pipx
# ------------
COPY requirements ./requirements
RUN pip install -r requirements/build-docs.txt
RUN pip install pipx
# Copy Files
# ----------
COPY LICENSE ./
COPY src ./src
COPY scripts ./scripts
COPY setup.py ./
COPY pyproject.toml ./
COPY MANIFEST.in ./
COPY README.md ./
RUN pip install .[all]
# COPY License
# -----------
COPY LICENSE /app/
# Build the Docs
# --------------
COPY docs/__init__.py ./docs/
COPY docs/app.py ./docs/
COPY docs/examples.py ./docs/
COPY docs/source ./docs/source
COPY docs ./docs
COPY branding ./branding
RUN sphinx-build -v -W -b html docs/source docs/build
# Install and Build Docs
# ----------------------
WORKDIR /app/docs
RUN pipx run poetry install
RUN pipx run poetry run sphinx-build -v -W -b html source build
# Define Entrypoint
# -----------------
ENV PORT 5000
ENV REACTPY_DEBUG_MODE=1
ENV REACTPY_CHECK_VDOM_SPEC=0
CMD python scripts/run_docs.py
CMD pipx run poetry run python main.py

View file

@ -1,19 +0,0 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View file

@ -3,14 +3,15 @@ from pathlib import Path
from sanic import Sanic, response
from docs_app.examples import get_normalized_example_name, load_examples
from reactpy import component
from reactpy.backend.sanic import Options, configure, use_request
from reactpy.core.types import ComponentConstructor
from .examples import get_normalized_example_name, load_examples
THIS_DIR = Path(__file__).parent
DOCS_DIR = THIS_DIR.parent
DOCS_BUILD_DIR = DOCS_DIR / "build"
HERE = Path(__file__).parent
REACTPY_MODEL_SERVER_URL_PREFIX = "/_reactpy"
logger = getLogger(__name__)
@ -40,13 +41,13 @@ def reload_examples():
_EXAMPLES: dict[str, ComponentConstructor] = {}
def make_app():
app = Sanic("docs_app")
def make_app(name: str):
app = Sanic(name)
app.static("/docs", str(HERE / "build"))
app.static("/docs", str(DOCS_BUILD_DIR))
@app.route("/")
async def forward_to_index(request):
async def forward_to_index(_):
return response.redirect("/docs/index.html")
configure(

View file

@ -13,22 +13,19 @@ from sphinx_autobuild.cli import (
get_parser,
)
from docs.app import make_app, reload_examples
from docs_app.app import make_app, reload_examples
from reactpy.backend.sanic import serve_development_app
from reactpy.testing import clear_reactpy_web_modules_dir
# these environment variable are used in custom Sphinx extensions
os.environ["REACTPY_DOC_EXAMPLE_SERVER_HOST"] = "127.0.0.1:5555"
os.environ["REACTPY_DOC_STATIC_SERVER_HOST"] = ""
_running_reactpy_servers = []
def wrap_builder(old_builder):
# This is the bit that we're injecting to get the example components to reload too
app = make_app()
app = make_app("docs_dev_app")
thread_started = threading.Event()
@ -87,8 +84,8 @@ def main():
ignore_handler = _get_ignore_handler(args)
server.watch(srcdir, builder, ignore=ignore_handler)
for dirpath in args.additional_watched_dirs:
dirpath = os.path.realpath(dirpath)
server.watch(dirpath, builder, ignore=ignore_handler)
real_dirpath = os.path.realpath(dirpath)
server.watch(real_dirpath, builder, ignore=ignore_handler)
server.watch(outdir, ignore=ignore_handler)
if not args.no_initial_build:
@ -100,12 +97,8 @@ def main():
def opener():
time.sleep(args.delay)
webbrowser.open("http://%s:%s/index.html" % (args.host, args.port))
webbrowser.open(f"http://{args.host}:{args.port}/index.html")
threading.Thread(target=opener, daemon=True).start()
server.serve(port=portn, host=args.host, root=outdir)
if __name__ == "__main__":
main()

View file

@ -1,16 +1,16 @@
from __future__ import annotations
from collections.abc import Iterator
from io import StringIO
from pathlib import Path
from traceback import format_exc
from typing import Callable, Iterator
from typing import Callable
import reactpy
from reactpy.types import ComponentType
HERE = Path(__file__)
SOURCE_DIR = HERE.parent / "source"
SOURCE_DIR = HERE.parent.parent / "source"
CONF_FILE = SOURCE_DIR / "conf.py"
RUN_ReactPy = reactpy.run
@ -148,7 +148,6 @@ class _PrintBuffer:
def set_callback(self, function: Callable[[str], None]) -> None:
self._callback = function
return None
def getvalue(self) -> str:
return "".join(self._lines)

14
docs/docs_app/prod.py Normal file
View file

@ -0,0 +1,14 @@
import os
from docs_app.app import make_app
app = make_app("docs_prod_app")
def main() -> None:
app.run(
host="0.0.0.0", # noqa: S104
port=int(os.environ.get("PORT", 5000)),
workers=int(os.environ.get("WEB_CONCURRENCY", 1)),
debug=bool(int(os.environ.get("DEBUG", "0"))),
)

9
docs/main.py Normal file
View file

@ -0,0 +1,9 @@
import sys
from docs_app import dev, prod
if __name__ == "__main__":
if len(sys.argv) == 1:
prod.main()
else:
dev.main()

View file

@ -1,35 +0,0 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd

2269
docs/poetry.lock generated Normal file

File diff suppressed because it is too large Load diff

23
docs/pyproject.toml Normal file
View file

@ -0,0 +1,23 @@
[tool.poetry]
name = "docs"
version = "0.0.0"
description = "docs"
authors = ["rmorshea <ryan.morshead@gmail.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.9"
reactpy = { path = "../src/py/reactpy", extras = ["starlette", "sanic", "fastapi", "flask", "tornado", "testing"], develop = false }
furo = "2022.04.07"
sphinx = "*"
sphinx-autodoc-typehints = "*"
sphinx-copybutton = "*"
sphinx-autobuild = "*"
sphinx-reredirects = "*"
sphinx-design = "*"
sphinx-resolve-py-references = "*"
sphinxext-opengraph = "*"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View file

@ -8,7 +8,7 @@
"name": "reactpy-docs-example-loader",
"version": "1.0.0",
"dependencies": {
"@reactpy/client": "file:../../../src/client/packages/@reactpy/client"
"@reactpy/client": "file:../../../src/js/packages/@reactpy/client"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^21.0.1",
@ -21,6 +21,7 @@
"../../../src/client/packages/@reactpy/client": {
"version": "0.3.1",
"integrity": "sha512-pIK5eNwFSHKXg7ClpASWFVKyZDYxz59MSFpVaX/OqJFkrJaAxBuhKGXNTMXmuyWOL5Iyvb/ErwwDRxQRzMNkfQ==",
"extraneous": true,
"license": "MIT",
"dependencies": {
"event-to-object": "^0.1.2",
@ -58,8 +59,26 @@
"react-dom": ">=16 <18"
}
},
"../../../src/js/packages/@reactpy/client": {
"version": "0.3.1",
"license": "MIT",
"dependencies": {
"event-to-object": "^0.1.2",
"json-pointer": "^0.6.2"
},
"devDependencies": {
"@types/json-pointer": "^1.0.31",
"@types/react": "^17.0",
"@types/react-dom": "^17.0",
"typescript": "^4.9.5"
},
"peerDependencies": {
"react": ">=16 <18",
"react-dom": ">=16 <18"
}
},
"node_modules/@reactpy/client": {
"resolved": "../../../src/client/packages/@reactpy/client",
"resolved": "../../../src/js/packages/@reactpy/client",
"link": true
},
"node_modules/@rollup/plugin-commonjs": {
@ -434,7 +453,7 @@
},
"dependencies": {
"@reactpy/client": {
"version": "file:../../../src/client/packages/@reactpy/client",
"version": "file:../../../src/js/packages/@reactpy/client",
"requires": {
"@types/json-pointer": "^1.0.31",
"@types/react": "^17.0",

View file

@ -15,6 +15,6 @@
"rollup": "^2.35.1"
},
"dependencies": {
"@reactpy/client": "file:../../../src/client/packages/@reactpy/client"
"@reactpy/client": "file:../../../src/js/packages/@reactpy/client"
}
}

View file

@ -6,7 +6,6 @@ from sphinx.application import Sphinx
from sphinx.ext.doctest import DocTestBuilder
from sphinx.ext.doctest import setup as doctest_setup
test_template = """
import asyncio as __test_template_asyncio
@ -41,10 +40,8 @@ class AsyncDoctestBuilder(DocTestBuilder):
@test_runner.setter
def test_runner(self, value: DocTestRunner) -> None:
self._test_runner = TestRunnerWrapper(value)
return None
def setup(app: Sphinx) -> None:
doctest_setup(app)
app.add_builder(AsyncDoctestBuilder, override=True)
return None

View file

@ -1,15 +1,14 @@
from __future__ import annotations
import sys
from collections.abc import Collection, Iterator
from pathlib import Path
from typing import Collection, Iterator
from sphinx.application import Sphinx
HERE = Path(__file__).parent
SRC = HERE.parent.parent.parent / "src"
PYTHON_PACKAGE = SRC / "reactpy"
PYTHON_PACKAGE = SRC / "py" / "reactpy" / "reactpy"
AUTO_DIR = HERE.parent / "_auto"
AUTO_DIR.mkdir(exist_ok=True)
@ -82,9 +81,12 @@ def get_module_name(path: Path) -> str:
def get_section_symbol(path: Path) -> str:
rel_path_parts = path.relative_to(PYTHON_PACKAGE).parts
assert len(rel_path_parts) < len(SECTION_SYMBOLS), "package structure is too deep"
return SECTION_SYMBOLS[len(rel_path_parts)]
rel_path = path.relative_to(PYTHON_PACKAGE)
rel_path_parts = rel_path.parts
if len(rel_path_parts) > len(SECTION_SYMBOLS):
msg = f"package structure is too deep - ran out of section symbols: {rel_path}"
raise RuntimeError(msg)
return SECTION_SYMBOLS[len(rel_path_parts) - 1]
def walk_python_files(root: Path, ignore_dirs: Collection[str]) -> Iterator[Path]:

View file

@ -3,11 +3,10 @@ from pathlib import Path
from sphinx.application import Sphinx
SOURCE_DIR = Path(__file__).parent.parent
CUSTOM_JS_DIR = SOURCE_DIR / "_custom_js"
def setup(app: Sphinx) -> None:
subprocess.run("npm install", cwd=CUSTOM_JS_DIR, shell=True)
subprocess.run("npm run build", cwd=CUSTOM_JS_DIR, shell=True)
subprocess.run("npm install", cwd=CUSTOM_JS_DIR, shell=True) # noqa S607
subprocess.run("npm run build", cwd=CUSTOM_JS_DIR, shell=True) # noqa S607

View file

@ -17,7 +17,6 @@ from sphinx.locale import __
from sphinx.util import logging
from sphinx.util.nodes import clean_astext
logger = logging.getLogger(__name__)

View file

@ -4,18 +4,17 @@ import re
from pathlib import Path
from typing import Any
from docs_app.examples import (
SOURCE_DIR,
get_example_files_by_name,
get_normalized_example_name,
)
from docutils.parsers.rst import directives
from docutils.statemachine import StringList
from sphinx.application import Sphinx
from sphinx.util.docutils import SphinxDirective
from sphinx_design.tabs import TabSetDirective
from docs.examples import (
SOURCE_DIR,
get_example_files_by_name,
get_normalized_example_name,
)
class WidgetExample(SphinxDirective):
has_content = False
@ -41,10 +40,8 @@ class WidgetExample(SphinxDirective):
ex_files = get_example_files_by_name(example_name)
if not ex_files:
src_file, line_num = self.get_source_info()
raise ValueError(
f"Missing example named {example_name!r} "
f"referenced by document {src_file}:{line_num}"
)
msg = f"Missing example named {example_name!r} referenced by document {src_file}:{line_num}"
raise ValueError(msg)
labeled_tab_items: list[tuple[str, Any]] = []
if len(ex_files) == 1:
@ -114,7 +111,8 @@ def _literal_include(path: Path, linenos: bool):
".json": "json",
}[path.suffix]
except KeyError:
raise ValueError(f"Unknown extension type {path.suffix!r}")
msg = f"Unknown extension type {path.suffix!r}"
raise ValueError(msg) from None
return _literal_include_template.format(
name=str(path.relative_to(SOURCE_DIR)),

View file

@ -1,13 +1,14 @@
import os
import sys
print(sys.path)
from docs_app.examples import get_normalized_example_name
from docutils.nodes import raw
from docutils.parsers.rst import directives
from sphinx.application import Sphinx
from sphinx.util.docutils import SphinxDirective
from docs.examples import get_normalized_example_name
_REACTPY_EXAMPLE_HOST = os.environ.get("REACTPY_DOC_EXAMPLE_SERVER_HOST", "")
_REACTPY_STATIC_HOST = os.environ.get("REACTPY_DOC_STATIC_SERVER_HOST", "/docs").rstrip(
"/"

View file

@ -80,12 +80,18 @@ In order to develop ReactPy locally you'll first need to install the following:
* - What to Install
- How to Install
* - Python >= 3.9
- https://realpython.com/installing-python/
* - Hatch
- https://hatch.pypa.io/latest/install/
* - Poetry
- https://python-poetry.org/docs/#installation
* - Git
- https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
* - Python >= 3.7
- https://realpython.com/installing-python/
* - NodeJS >= 14
- https://nodejs.org/en/download/package-manager/
@ -106,57 +112,38 @@ Once done, you can clone a local copy of this repository:
git clone https://github.com/reactive-python/reactpy.git
cd reactpy
Then, you should be able to run the command below to:
- Install an editable version of the Python code
- Download, build, and install Javascript dependencies
- Install some pre-commit_ hooks for Git
Then, you should be able to activate your development environment with:
.. code-block:: bash
pip install -e . -r requirements.txt && pre-commit install
If you modify any Javascript, you'll need to re-install ReactPy:
.. code-block:: bash
pip install -e .
However you may also ``cd`` to the ``src/client`` directory which contains a
``package.json`` that you can use to run standard ``npm`` commands from.
hatch shell
Running The Tests
-----------------
The test suite for ReactPy is executed with Nox_, which should already be installed if you
followed the `earlier instructions <Development Environment>`_. The suite covers:
1. Server-side Python code with PyTest_
2. The end-to-end application using Playwright_ in Python
3. Client-side Javascript code with UVU_
Once you've installed them you'll be able to run:
Tests exist for both Python and Javascript. These can be run with the following:
.. code-block:: bash
nox -s check-python-tests
hatch run test-py
hatch run test-js
You can observe the browser as the tests are running by passing an extra flag:
If you want to run tests for individual packages you'll need to ``cd`` into the
package directory and run the tests from there. For example, to run the tests just for
the ``reactpy`` package you'd do:
.. code-block:: bash
nox -s check-python-tests -- --headed
cd src/py/reactpy
hatch run test --headed # run the tests in a browser window
To see a full list of available commands (e.g. ``nox -s <your-command>``) run:
For Javascript, you'd do:
.. code-block:: bash
nox -l
cd src/js/packages/event-to-object
npm run check:tests
Code Quality Checks
@ -172,8 +159,9 @@ The following are currently being used:
- MyPy_ - a static type checker
- Black_ - an opinionated code formatter
- Flake8_ - a style guide enforcement tool
- ISort_ - a utility for alphabetically sorting imports
- Ruff_ - An extremely fast Python linter, written in Rust.
- Prettier_ - a tool for automatically formatting various file types
- EsLint_ - A Javascript linter
The most strict measure of quality enforced on the codebase is 100% test coverage in
Python files. This means that every line of coded added to ReactPy requires a test case
@ -186,10 +174,10 @@ your :ref:`Pull Request <Making a Pull Request>`.
.. note::
You can manually run ``nox -s format`` to auto format your code without having to
do so via ``pre-commit``. However, many IDEs have ways to automatically format upon
saving a file
(e.g.`VSCode <https://code.visualstudio.com/docs/python/editing#_formatting>`__)
You can manually run ``hatch run lint --fix`` to auto format your code without
having to do so via ``pre-commit``. However, many IDEs have ways to automatically
format upon saving a file (e.g.
`VSCode <https://code.visualstudio.com/docs/python/editing#_formatting>`__)
Building The Documentation
@ -199,7 +187,7 @@ To build and display the documentation locally run:
.. code-block:: bash
nox -s docs
hatch run docs
This will compile the documentation from its source files into HTML, start a web server,
and open a browser to display the now generated documentation. Whenever you change any
@ -211,14 +199,14 @@ To run some of the examples in the documentation as if they were tests run:
.. code-block:: bash
nox -s test_docs
hatch run test-docs
Building the documentation as it's deployed in production requires Docker_. Once you've
installed Docker, you can run:
.. code-block:: bash
nox -s docs_in_docker
hatch run docs --docker
Where you can then navigate to http://localhost:5000..
@ -329,7 +317,6 @@ you should refer to their respective documentation in the links below:
.. _pip: https://pypi.org/project/pip/
.. _PyTest: pytest <https://docs.pytest.org
.. _Playwright: https://playwright.dev/python/
.. _Nox: https://nox.thea.codes/en/stable/#
.. _React: https://reactjs.org/
.. _Heroku: https://www.heroku.com/what
.. _GitHub Actions: https://github.com/features/actions
@ -338,6 +325,7 @@ you should refer to their respective documentation in the links below:
.. _MyPy: http://mypy-lang.org/
.. _Black: https://github.com/psf/black
.. _Flake8: https://flake8.pycqa.org/en/latest/
.. _ISort: https://pycqa.github.io/isort/
.. _Ruff: https://github.com/charliermarsh/ruff
.. _UVU: https://github.com/lukeed/uvu
.. _Prettier: https://prettier.io/
.. _ESLint: https://eslint.org/

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Configuration file for the Sphinx documentation builder.
#
@ -10,16 +9,14 @@ import sys
from doctest import DONT_ACCEPT_TRUE_FOR_1, ELLIPSIS, NORMALIZE_WHITESPACE
from pathlib import Path
# -- Path Setup --------------------------------------------------------------
THIS_DIR = Path(__file__).parent
ROOT_DIR = THIS_DIR.parent.parent
# project path
sys.path.insert(0, str(ROOT_DIR))
DOCS_DIR = THIS_DIR.parent
# extension path
sys.path.insert(0, str(DOCS_DIR))
sys.path.insert(0, str(THIS_DIR / "_exts"))
@ -32,7 +29,7 @@ description = (
"a single line of Javascript. It can be run standalone, in a Jupyter Notebook, or "
"as part of an existing application."
)
copyright = "2023, Ryan Morshead"
copyright = "2023, Ryan Morshead" # noqa: A001
author = "Ryan Morshead"
# -- Common External Links ---------------------------------------------------
@ -40,23 +37,23 @@ author = "Ryan Morshead"
extlinks = {
"issue": (
"https://github.com/reactive-python/reactpy/issues/%s",
"#",
"#%s",
),
"pull": (
"https://github.com/reactive-python/reactpy/pull/%s",
"#",
"#%s",
),
"discussion": (
"https://github.com/reactive-python/reactpy/discussions/%s",
"#",
"#%s",
),
"discussion-type": (
"https://github.com/reactive-python/reactpy/discussions/categories/%s",
"",
"%s",
),
"commit": (
"https://github.com/reactive-python/reactpy/commit/%s",
"",
"%s",
),
}
extlinks_detect_hardcoded_links = True
@ -320,7 +317,7 @@ epub_exclude_files = ["search.html"]
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
"https://docs.python.org/": None,
"python": ("https://docs.python.org/3", None),
"pyalect": ("https://pyalect.readthedocs.io/en/latest", None),
"sanic": ("https://sanic.readthedocs.io/en/latest/", None),
"tornado": ("https://www.tornadoweb.org/en/stable/", None),

View file

@ -3,7 +3,6 @@ from pathlib import Path
from reactpy import component, hooks, html, run
HERE = Path(__file__)
DATA_PATH = HERE.parent / "data.json"
sculpture_data = json.loads(DATA_PATH.read_text())

View file

@ -3,7 +3,6 @@ from pathlib import Path
from reactpy import component, hooks, html, run
HERE = Path(__file__)
DATA_PATH = HERE.parent / "data.json"
sculpture_data = json.loads(DATA_PATH.read_text())

View file

@ -3,7 +3,6 @@ from pathlib import Path
from reactpy import component, hooks, html, run
HERE = Path(__file__)
DATA_PATH = HERE.parent / "data.json"
sculpture_data = json.loads(DATA_PATH.read_text())

View file

@ -8,7 +8,7 @@ def ArtistList():
)
def handle_sort_click(event):
set_artists(list(sorted(artists)))
set_artists(sorted(artists))
def handle_reverse_click(event):
set_artists(list(reversed(artists)))

View file

@ -6,7 +6,7 @@ def DataList(items, filter_by_priority=None, sort_by_priority=False):
if filter_by_priority is not None:
items = [i for i in items if i["priority"] <= filter_by_priority]
if sort_by_priority:
items = list(sorted(items, key=lambda i: i["priority"]))
items = sorted(items, key=lambda i: i["priority"])
list_item_elements = [html.li(i["text"]) for i in items]
return html.ul(list_item_elements)

View file

@ -6,7 +6,7 @@ def DataList(items, filter_by_priority=None, sort_by_priority=False):
if filter_by_priority is not None:
items = [i for i in items if i["priority"] <= filter_by_priority]
if sort_by_priority:
items = list(sorted(items, key=lambda i: i["priority"]))
items = sorted(items, key=lambda i: i["priority"])
list_item_elements = [html.li({"key": i["id"]}, i["text"]) for i in items]
return html.ul(list_item_elements)

View file

@ -1,6 +1,5 @@
from reactpy import component, run, web
mui = web.module_from_template(
"react@^17.0.0",
"@material-ui/core@4.12.4",

View file

@ -2,7 +2,6 @@ import json
import reactpy
mui = reactpy.web.module_from_template(
"react@^17.0.0",
"@material-ui/core@4.12.4",

View file

@ -2,7 +2,6 @@ from pathlib import Path
from reactpy import component, run, web
file = Path(__file__).parent / "super-simple-chart.js"
ssc = web.module_from_file("super-simple-chart", file, fallback="")
SuperSimpleChart = web.export(ssc, "SuperSimpleChart")

View file

@ -13,7 +13,8 @@ def GoodComponent():
@component
def BadComponent():
raise RuntimeError("This component raised an error")
msg = "This component raised an error"
raise RuntimeError(msg)
run(App)

View file

@ -3,7 +3,6 @@
from reactpy import run
from reactpy.backend import fastapi as fastapi_server
# the run() function is the entry point for examples
fastapi_server.configure = lambda _, cmpt: run(cmpt)

View file

@ -3,7 +3,6 @@
from reactpy import run
from reactpy.backend import flask as flask_server
# the run() function is the entry point for examples
flask_server.configure = lambda _, cmpt: run(cmpt)

View file

@ -3,7 +3,6 @@
from reactpy import run
from reactpy.backend import sanic as sanic_server
# the run() function is the entry point for examples
sanic_server.configure = lambda _, cmpt: run(cmpt)

View file

@ -3,7 +3,6 @@
from reactpy import run
from reactpy.backend import starlette as starlette_server
# the run() function is the entry point for examples
starlette_server.configure = lambda _, cmpt: run(cmpt)

View file

@ -3,7 +3,6 @@
from reactpy import run
from reactpy.backend import tornado as tornado_server
# the run() function is the entry point for examples
tornado_server.configure = lambda _, cmpt: run(cmpt)

View file

@ -1,4 +1,3 @@
import reactpy
reactpy.run(reactpy.sample.SampleApp)

View file

@ -4,7 +4,6 @@ from sanic.response import file
from reactpy import component, html
from reactpy.backend.sanic import Options, configure
app = Sanic("MyApp")

View file

@ -3,7 +3,6 @@ from pathlib import Path
from reactpy import component, hooks, html, run
HERE = Path(__file__)
DATA_PATH = HERE.parent / "data.json"
food_data = json.loads(DATA_PATH.read_text())
@ -33,11 +32,10 @@ def Table(value, set_value):
name = html.td(row["name"])
descr = html.td(row["description"])
tr = html.tr(name, descr, value)
if value == "":
if not value:
rows.append(tr)
elif value.lower() in row["name"].lower():
rows.append(tr)
else:
if value.lower() in row["name"].lower():
rows.append(tr)
headers = html.tr(html.td(html.b("name")), html.td(html.b("description")))
table = html.table(html.thead(headers), html.tbody(rows))
return table

View file

@ -4,7 +4,6 @@ from typing import NamedTuple
from reactpy import component, html, run, use_state
from reactpy.widgets import image
HERE = Path(__file__)
CHARACTER_IMAGE = (HERE.parent / "static" / "bunny.png").read_bytes()

View file

@ -1,6 +1,5 @@
import reactpy
mui = reactpy.web.module_from_template("react", "@material-ui/core@^5.0", fallback="")
Switch = reactpy.web.export(mui, "Switch")

View file

@ -10,7 +10,7 @@ from reactpy.widgets import image
def PolynomialPlot():
coefficients, set_coefficients = reactpy.hooks.use_state([0])
x = [n for n in linspace(-1, 1, 50)]
x = list(linspace(-1, 1, 50))
y = [polynomial(value, coefficients) for value in x]
return reactpy.html.div(
@ -31,7 +31,7 @@ def ExpandableNumberInputs(values, set_values):
inputs.append(poly_coef_input(i + 1, set_value_at_index))
def add_input():
set_values(values + [0])
set_values([*values, 0])
def del_input():
set_values(values[:-1])
@ -62,7 +62,7 @@ def poly_coef_input(index, callback):
reactpy.html.label(
"C",
reactpy.html.sub(index),
" × X",
" x X",
reactpy.html.sup(index),
),
reactpy.html.input({"type": "number", "on_change": callback}),

View file

@ -2,7 +2,6 @@ import random
import reactpy
react_cytoscapejs = reactpy.web.module_from_template(
"react",
"react-cytoscapejs",

View file

@ -1,6 +1,5 @@
import reactpy
pigeon_maps = reactpy.web.module_from_template("react", "pigeon-maps", fallback="")
Map, Marker = reactpy.web.export(pigeon_maps, ["Map", "Marker"])
@ -9,18 +8,16 @@ Map, Marker = reactpy.web.export(pigeon_maps, ["Map", "Marker"])
def MapWithMarkers():
marker_anchor, add_marker_anchor, remove_marker_anchor = use_set()
markers = list(
map(
lambda anchor: Marker(
{
"anchor": anchor,
"onClick": lambda: remove_marker_anchor(anchor),
},
key=str(anchor),
),
marker_anchor,
markers = [
Marker(
{
"anchor": anchor,
"onClick": lambda event, a=anchor: remove_marker_anchor(a),
},
key=str(anchor),
)
)
for anchor in marker_anchor
]
return Map(
{

View file

@ -5,7 +5,6 @@ import time
import reactpy
from reactpy.widgets import Input
victory = reactpy.web.module_from_template(
"react",
"victory-line",

View file

@ -107,7 +107,7 @@ def GameLoop(grid_size, block_scale, set_game_state):
if snake[-1] == food:
set_food()
new_snake = snake + [new_snake_head]
new_snake = [*snake, new_snake_head]
else:
new_snake = snake[1:] + [new_snake_head]

View file

@ -7,7 +7,7 @@ def Todo():
async def add_new_task(event):
if event["key"] == "Enter":
set_items(items + [event["target"]["value"]])
set_items([*items, event["target"]["value"]])
tasks = []

View file

@ -9,7 +9,8 @@ def reducer(count, action):
elif action == "reset":
return 0
else:
raise ValueError(f"Unknown action '{action}'")
msg = f"Unknown action '{action}'"
raise ValueError(msg)
@reactpy.component

View file

@ -1,6 +1,5 @@
import reactpy
victory = reactpy.web.module_from_template("react", "victory-bar", fallback="")
VictoryBar = reactpy.web.export(victory, "VictoryBar")

View file

@ -1,553 +0,0 @@
from __future__ import annotations
import json
import os
import re
from argparse import REMAINDER
from dataclasses import replace
from pathlib import Path
from shutil import rmtree
from typing import TYPE_CHECKING, Callable, NamedTuple, Sequence, cast
from noxopt import Annotated, NoxOpt, Option, Session
# --- Typing Preamble ------------------------------------------------------------------
if TYPE_CHECKING:
# not available in typing module until Python 3.8
# not available in typing module until Python 3.10
from typing import Literal, Protocol, TypeAlias
class ReleasePrepFunc(Protocol):
def __call__(
self, session: Session, package: PackageInfo
) -> Callable[[bool], None]:
...
LanguageName: TypeAlias = "Literal['py', 'js']"
# --- Constants ------------------------------------------------------------------------
ROOT_DIR = Path(__file__).parent.resolve()
SRC_DIR = ROOT_DIR / "src"
CLIENT_DIR = SRC_DIR / "client"
REACTPY_DIR = SRC_DIR / "reactpy"
TAG_PATTERN = re.compile(
# start
r"^"
# package name
r"(?P<name>[0-9a-zA-Z-@/]+)-"
# package version
r"v(?P<version>[0-9][0-9a-zA-Z-\.\+]*)"
# end
r"$"
)
REMAINING_ARGS = Option(nargs=REMAINDER, type=str)
# --- Session Setup --------------------------------------------------------------------
group = NoxOpt(auto_tag=True)
@group.setup
def setup_checks(session: Session) -> None:
session.install("--upgrade", "pip")
session.run("pip", "--version")
session.run("npm", "--version", external=True)
@group.setup("check-javascript")
def setup_javascript_checks(session: Session) -> None:
session.chdir(CLIENT_DIR)
session.run("npm", "ci", external=True)
# --- Session Definitions --------------------------------------------------------------
@group.session
def format(session: Session) -> None:
"""Auto format Python and Javascript code"""
# format Python
install_requirements_file(session, "check-style")
session.run("black", ".")
session.run("isort", ".")
# format client Javascript
session.chdir(CLIENT_DIR)
session.run("npm", "run", "format", external=True)
# format docs Javascript
session.chdir(ROOT_DIR / "docs" / "source" / "_custom_js")
session.run("npm", "run", "format", external=True)
@group.session
def tsc(session: Session) -> None:
session.chdir(CLIENT_DIR)
session.run("npx", "tsc", "-b", "-w", "packages/app", external=True)
@group.session
def example(session: Session) -> None:
"""Run an example"""
session.install("matplotlib")
install_reactpy_dev(session)
session.run(
"python",
"scripts/one_example.py",
*session.posargs,
env=get_reactpy_script_env(),
)
@group.session
def docs(session: Session) -> None:
"""Build and display documentation in the browser (automatically reloads on change)"""
install_requirements_file(session, "build-docs")
install_reactpy_dev(session)
session.run(
"python",
"scripts/live_docs.py",
"--open-browser",
# watch python source too
"--watch=src/reactpy",
# for some reason this matches absolute paths
"--ignore=**/_auto/*",
"--ignore=**/_static/custom.js",
"--ignore=**/node_modules/*",
"--ignore=**/package-lock.json",
"-a",
"-E",
"-b",
"html",
"docs/source",
"docs/build",
env={**os.environ, **get_reactpy_script_env()},
)
@group.session
def docs_in_docker(session: Session) -> None:
"""Build a docker image for the documentation and run it to mimic production"""
session.run(
"docker",
"build",
".",
"--file",
"docs/Dockerfile",
"--tag",
"reactpy-docs:latest",
external=True,
)
session.run(
"docker",
"run",
"-it",
"-p",
"5000:5000",
"-e",
"DEBUG=1",
"--rm",
"reactpy-docs:latest",
external=True,
)
@group.session
def check_python_tests(
session: Session,
no_cov: Annotated[bool, Option(help="turn off coverage checks")] = False,
headed: Annotated[bool, Option(help="run tests with a headed browser")] = False,
pytest: Annotated[Sequence[str], replace(REMAINING_ARGS, help="pytest args")] = (),
) -> None:
"""Run the Python-based test suite"""
session.env["REACTPY_DEBUG_MODE"] = "1"
install_requirements_file(session, "test-env")
session.run("playwright", "install", "chromium")
args = ["pytest", *pytest]
if headed:
args.append("--headed")
if no_cov:
session.log("Coverage won't be checked")
session.install(".[all]")
else:
args = ["coverage", "run", "--source=src/reactpy", "--module", *args]
install_reactpy_dev(session)
session.run(*args)
if not no_cov:
session.run("coverage", "report")
@group.session
def check_python_types(session: Session) -> None:
"""Perform a static type analysis of the Python codebase"""
install_requirements_file(session, "check-types")
install_requirements_file(session, "pkg-deps")
install_requirements_file(session, "pkg-extras")
session.run("mypy", "--version")
session.run("mypy", "--show-error-codes", "--strict", "src/reactpy")
session.run("mypy", "--show-error-codes", "noxfile.py")
@group.session
def check_python_format(session: Session) -> None:
"""Check that Python style guidelines are being followed"""
install_requirements_file(session, "check-style")
session.run("flake8", "src/reactpy", "tests", "docs")
session.run("black", ".", "--check")
session.run("isort", ".", "--check-only")
@group.session
def check_python_build(session: Session) -> None:
"""Test whether the Python package can be build for distribution"""
install_requirements_file(session, "build-pkg")
session.run("python", "-m", "build", "--sdist", "--wheel", "--outdir", "dist", ".")
@group.session
def check_docs(session: Session) -> None:
"""Verify that the docs build and that doctests pass"""
install_requirements_file(session, "build-docs")
install_reactpy_dev(session)
session.run(
"sphinx-build",
"-a", # re-write all output files
"-T", # show full tracebacks
"-W", # turn warnings into errors
"--keep-going", # complete the build, but still report warnings as errors
"-b",
"html",
"docs/source",
"docs/build",
)
session.run("sphinx-build", "-b", "doctest", "docs/source", "docs/build")
# ensure docker image build works too
session.run("docker", "build", ".", "--file", "docs/Dockerfile", external=True)
@group.session
def check_javascript_tests(session: Session) -> None:
session.run("npm", "run", "check:tests", external=True)
@group.session
def check_javascript_format(session: Session) -> None:
session.run("npm", "run", "check:format", external=True)
@group.session
def check_javascript_types(session: Session) -> None:
session.run("npm", "run", "build", external=True)
session.run("npm", "run", "check:types", external=True)
@group.session
def check_javascript_build(session: Session) -> None:
session.run("npm", "run", "build", external=True)
@group.session
def build_javascript(session: Session) -> None:
"""Build javascript client code"""
session.chdir(CLIENT_DIR)
session.run("npm", "run", "build", external=True)
@group.session
def build_python(session: Session) -> None:
"""Build python package dist"""
rmtree(str(ROOT_DIR / "build"))
rmtree(str(ROOT_DIR / "dist"))
install_requirements_file(session, "build-pkg")
session.run("python", "-m", "build", "--sdist", "--wheel", "--outdir", "dist", ".")
@group.session
def publish(
session: Session,
publish_dry_run: Annotated[
bool,
Option(help="whether to test the release process"),
] = False,
publish_fake_tags: Annotated[
Sequence[str],
Option(nargs="*", type=str, help="fake tags to use for a dry run release"),
] = (),
) -> None:
packages = get_packages(session)
release_prep: dict[LanguageName, ReleasePrepFunc] = {
"js": prepare_javascript_release,
"py": prepare_python_release,
}
if publish_fake_tags and not publish_dry_run:
session.error("Cannot specify --publish-fake-tags without --publish-dry-run")
parsed_tags: list[TagInfo] = []
for tag in publish_fake_tags or get_current_tags(session):
tag_info = parse_tag(tag)
if tag_info is None:
session.error(
f"Invalid tag {tag} - must be of the form <package>-<language>-<version>"
)
parsed_tags.append(tag_info) # type: ignore
publishers: list[tuple[Path, Callable[[bool], None]]] = []
for tag, tag_pkg, tag_ver in parsed_tags:
if tag_pkg not in packages:
session.error(f"Tag {tag} references package {tag_pkg} that does not exist")
pkg_name, pkg_path, pkg_lang, pkg_ver = pkg_info = packages[tag_pkg]
if pkg_ver != tag_ver:
session.error(
f"Tag {tag} references version {tag_ver} of package {tag_pkg}, "
f"but the current version is {pkg_ver}"
)
session.chdir(pkg_path)
session.log(f"Preparing {tag_pkg} for release...")
publishers.append((pkg_path, release_prep[pkg_lang](session, pkg_info)))
for pkg_path, publish in publishers:
session.log(f"Publishing {pkg_path}...")
session.chdir(pkg_path)
publish(publish_dry_run)
# --- Utilities ------------------------------------------------------------------------
def install_requirements_file(session: Session, name: str) -> None:
file_path = ROOT_DIR / "requirements" / (name + ".txt")
assert file_path.exists(), f"requirements file {file_path} does not exist"
session.install("-r", str(file_path))
def install_reactpy_dev(session: Session, extras: str = "all") -> None:
if "--no-install" not in session.posargs:
session.install("-e", f".[{extras}]")
else:
session.posargs.remove("--no-install")
def get_reactpy_script_env() -> dict[str, str]:
return {
"PYTHONPATH": os.getcwd(),
"REACTPY_DEBUG_MODE": os.environ.get("REACTPY_DEBUG_MODE", "1"),
"REACTPY_TESTING_DEFAULT_TIMEOUT": os.environ.get(
"REACTPY_TESTING_DEFAULT_TIMEOUT", "6.0"
),
"REACTPY_CHECK_VDOM_SPEC": os.environ.get("REACTPY_CHECK_VDOM_SPEC", "0"),
}
def prepare_javascript_release(
session: Session, package: PackageInfo
) -> Callable[[bool], None]:
node_auth_token = session.env.get("NODE_AUTH_TOKEN")
if node_auth_token is None:
session.error("NODE_AUTH_TOKEN environment variable must be set")
session.run("npm", "ci", external=True)
session.run("npm", "run", "build", external=True)
def publish(dry_run: bool) -> None:
if dry_run:
session.run(
"npm",
"--workspace",
package.name,
"pack",
"--dry-run",
external=True,
)
return
session.run(
"npm",
"--workspace",
package.name,
"publish",
"--access",
"public",
external=True,
env={"NODE_AUTH_TOKEN": node_auth_token},
)
return publish
def prepare_python_release(
session: Session, package: PackageInfo
) -> Callable[[bool], None]:
twine_username = session.env.get("PYPI_USERNAME")
twine_password = session.env.get("PYPI_PASSWORD")
if not (twine_password and twine_username):
session.error(
"PYPI_USERNAME and PYPI_PASSWORD environment variables must be set"
)
for build_dir_name in ["build", "dist"]:
build_dir_path = Path.cwd() / build_dir_name
if build_dir_path.exists():
rmtree(str(build_dir_path))
install_requirements_file(session, "build-pkg")
session.run("python", "-m", "build", "--sdist", "--wheel", "--outdir", "dist", ".")
def publish(dry_run: bool):
if dry_run:
session.run("twine", "check", "dist/*")
return
session.run(
"twine",
"upload",
"dist/*",
env={"TWINE_USERNAME": twine_username, "TWINE_PASSWORD": twine_password},
)
return publish
def get_packages(session: Session) -> dict[str, PackageInfo]:
packages: dict[str, PackageInfo] = {
"reactpy": PackageInfo(
"reactpy", ROOT_DIR, "py", get_reactpy_package_version(session)
)
}
# collect javascript packages
js_package_paths: list[Path] = []
for maybed_pkg in (CLIENT_DIR / "packages").glob("*"):
if not (maybed_pkg / "package.json").exists():
for nmaybe_namespaced_pkg in maybed_pkg.glob("*"):
if (nmaybe_namespaced_pkg / "package.json").exists():
js_package_paths.append(nmaybe_namespaced_pkg)
else:
js_package_paths.append(maybed_pkg)
# get javascript package info
for pkg in js_package_paths:
pkg_json_file = pkg / "package.json" # we already know this exists
pkg_json = json.loads(pkg_json_file.read_text())
pkg_name = pkg_json.get("name")
pkg_version = pkg_json.get("version")
if pkg_version is None or pkg_name is None:
session.log(f"Skipping - {pkg_name} has no name/version in package.json")
continue
if pkg_name in packages:
session.error(f"Duplicate package name {pkg_name}")
packages[pkg_name] = PackageInfo(pkg_name, CLIENT_DIR, "js", pkg_version)
return packages
class PackageInfo(NamedTuple):
name: str
path: Path
language: LanguageName
version: str
def get_current_tags(session: Session) -> list[str]:
"""Get tags for the current commit"""
# check if unstaged changes
try:
session.run(
"git",
"diff",
"--cached",
"--exit-code",
silent=True,
external=True,
)
session.run(
"git",
"diff",
"--exit-code",
silent=True,
external=True,
)
except Exception:
session.error("Cannot create a tag - there are uncommited changes")
tags_per_commit: dict[str, list[str]] = {}
for commit, tag in map(
str.split,
cast(
str,
session.run(
"git",
"for-each-ref",
"--format",
r"%(objectname) %(refname:short)",
"refs/tags",
silent=True,
external=True,
),
).splitlines(),
):
tags_per_commit.setdefault(commit, []).append(tag)
current_commit = cast(
str, session.run("git", "rev-parse", "HEAD", silent=True, external=True)
).strip()
tags = tags_per_commit.get(current_commit, [])
if not tags:
session.error("No tags found for current commit")
session.log(f"Found tags: {tags}")
return tags
def parse_tag(tag: str) -> TagInfo | None:
match = TAG_PATTERN.match(tag)
if not match:
return None
return TagInfo(tag, match["name"], match["version"])
class TagInfo(NamedTuple):
tag: str
package: str
version: str
def get_reactpy_package_version(session: Session) -> str: # type: ignore[return]
pkg_root_init_file = REACTPY_DIR / "__init__.py"
for line in pkg_root_init_file.read_text().split("\n"):
if line.startswith('__version__ = "') and line.endswith('" # DO NOT MODIFY'):
return (
line
# get assignment value
.split("=", 1)[1]
# remove "DO NOT MODIFY" comment
.split("#", 1)[0]
# clean up leading/trailing space
.strip()
# remove the quotes
[1:-1]
)
session.error(f"No version found in {pkg_root_init_file}")

View file

@ -1,61 +1,132 @@
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
# --- Project ----------------------------------------------------------------------------
[tool.isort]
multi_line_output = 3
force_grid_wrap = 0
use_parentheses = "True"
ensure_newline_before_comments = "True"
include_trailing_comma = "True"
line_length = 88
lines_after_imports = 2
[project]
name = "scripts"
version = "0.0.0"
description = "Scripts for managing the ReactPy respository"
[tool.mypy]
incremental = false
ignore_missing_imports = true
warn_unused_configs = true
warn_redundant_casts = true
warn_unused_ignores = true
# --- Hatch ----------------------------------------------------------------------------
[tool.pytest.ini_options]
testpaths = "tests"
xfail_strict = true
markers = ["slow: marks tests as slow (deselect with '-m \"not slow\"')"]
python_files = "*asserts.py test_*.py"
asyncio_mode = "auto"
[tool.coverage.report]
fail_under = 100
show_missing = true
skip_covered = true
sort = "Name"
exclude_lines = [
# These are regex patterns
'pragma: no cover',
'\.\.\.',
'raise NotImplementedError',
'if TYPE_CHECKING[\s:]',
]
omit = [
"src/reactpy/__main__.py",
[tool.hatch.envs.default]
detached = true
dependencies = [
"invoke",
# lint
"black",
"ruff",
"toml",
"flake8",
"flake8-pyproject",
"reactpy-flake8 >=0.7",
# publish
"semver >=2, <3",
"twine",
]
[tool.pydocstyle]
inherit = false
match = '.*\.py'
convention = "google"
add_ignore = ["D100", "D101", "D102", "D103", "D104", "D105", "D107", "D412", "D415"]
[tool.hatch.envs.default.scripts]
publish = "invoke publish {args}"
docs = "invoke docs {args}"
lint-py = "invoke lint-py {args}"
lint-js = "invoke lint-js {args}"
test-py = "invoke test-py {args}"
test-js = "invoke test-js"
test-docs = "invoke test-docs"
# --- Black ----------------------------------------------------------------------------
[tool.black]
target-version = ["py39"]
line-length = 88
# --- Flake8 ----------------------------------------------------------------------------
[tool.flake8]
ignore = ["E203", "E266", "E501", "W503", "F811", "N802", "N806"]
per-file-ignores = [
# sometimes this is required in order to hide setup for an example
"docs/*/_examples/*.py:E402",
select = ["RPY"] # only need to check with reactpy-flake8
exclude = ["**/node_modules/*", ".eggs/*", ".tox/*", "**/venv/*"]
# --- Ruff -----------------------------------------------------------------------------
[tool.ruff]
target-version = "py39"
line-length = 88
select = [
"A",
"ARG",
"B",
"C",
"DTZ",
"E",
# error message linting is overkill
# "EM",
"F",
# TODO: turn this on later
# "FBT",
"I",
"ICN",
"ISC",
"N",
"PLC",
"PLE",
"PLR",
"PLW",
"Q",
"RUF",
"S",
"T",
"TID",
"UP",
"W",
"YTT",
]
ignore = [
# TODO: turn this on later
"N802", "N806", # allow TitleCase functions/variables
# We're not any cryptography
"S311",
# For loop variable re-assignment seems like an uncommon mistake
"PLW2901",
# Let Black deal with line-length
"E501",
# Allow args/attrs to shadow built-ins
"A002", "A003",
# Allow unused args (useful for documenting what the parameter is for later)
"ARG001", "ARG002", "ARG005",
# Allow non-abstract empty methods in abstract base classes
"B027",
# Allow boolean positional values in function calls, like `dict.get(... True)`
"FBT003",
# If we're making an explicit comparison to a falsy value it was probably intentional
"PLC1901",
# Ignore checks for possible passwords
"S105", "S106", "S107",
# Ignore complexity
"C901", "PLR0911", "PLR0912", "PLR0913", "PLR0915",
]
unfixable = [
# Don't touch unused imports
"F401",
]
[tool.ruff.isort]
known-first-party = ["reactpy"]
[tool.ruff.flake8-tidy-imports]
ban-relative-imports = "all"
[tool.ruff.per-file-ignores]
# Tests can use magic values, assertions, and relative imports
"**/tests/**/*" = ["PLR2004", "S101", "TID252"]
"docs/**/*.py" = [
# Examples require some extra setup before import
"E402",
# Allow exec
"S102",
# Allow print
"T201",
]
"scripts/**/*.py" = [
# Allow print
"T201",
]
max-line-length = 88
max-complexity = 20
select = ["B", "C", "E", "F", "W", "T4", "B9", "N", "RPY"]
exclude = ["**/node_modules/*", ".eggs/*", ".tox/*"]
# -- flake8-tidy-imports --
ban-relative-imports = "true"

View file

@ -1,9 +0,0 @@
sphinx
sphinx-autodoc-typehints
furo ==2022.04.07
sphinx-copybutton
sphinx-autobuild
sphinx-reredirects
sphinx-design
sphinx-resolve-py-references
sphinxext-opengraph

View file

@ -1,3 +0,0 @@
twine
wheel
build

View file

@ -1,10 +0,0 @@
black[jupyter]
flake8
flake8-pyproject
reactpy-flake8 >=0.7
flake8-print
flake8-tidy-imports
isort >=5.7.0
pep8-naming
# pydocstyle
git+https://github.com/PyCQA/pydocstyle.git@bd4993345a241bd0d5128f21de56394b1cde3714#egg=pydocstyle

View file

@ -1,6 +0,0 @@
mypy
types-click
types-tornado
types-pkg-resources
types-flask
types-requests

View file

@ -1 +0,0 @@
semver >=2, <3

View file

@ -1,2 +0,0 @@
nox
noxopt

View file

@ -1,9 +0,0 @@
typing-extensions >=3.10
mypy-extensions >=0.4.3
anyio >=3
jsonpatch >=1.32
fastjsonschema >=2.14.5
requests >=2
colorlog >=6
asgiref >=3
lxml >=4

View file

@ -1,24 +0,0 @@
# extra=starlette
starlette >=0.13.6
uvicorn[standard] >=0.19.0
# extra=sanic
sanic >=21
sanic-cors
uvicorn[standard] >=0.19.0
# extra=fastapi
fastapi >=0.63.0
uvicorn[standard] >=0.19.0
# extra=flask
flask
markupsafe>=1.1.1,<2.1
flask-cors
flask-sock
# extra=tornado
tornado
# extra=testing
playwright

View file

@ -1,15 +0,0 @@
pytest
pytest-asyncio>=0.17
pytest-mock
pytest-rerunfailures
pytest-timeout
coverage
responses
playwright
# I'm not quite sure why this needs to be installed for tests with Sanic to pass
sanic-testing
# Used to generate model changes from layout update messages
jsonpointer

View file

@ -1,5 +0,0 @@
# Scripts
All scripts should be run from the repository root (typically using
[`nox`](https://nox.thea.codes/en/stable/)). Use `nox --list` to see the list of
available scripts.

View file

@ -1,103 +0,0 @@
import sys
import time
from os.path import getmtime
from threading import Event, Thread
import reactpy
from docs.examples import all_example_names, get_example_files_by_name, load_one_example
from reactpy.widgets import _hotswap
EXAMPLE_NAME_SET = all_example_names()
EXAMPLE_NAME_LIST = tuple(sorted(EXAMPLE_NAME_SET))
def on_file_change(path, callback):
did_call_back_once = Event()
def watch_for_change():
last_modified = 0
while True:
modified_at = getmtime(path)
if modified_at != last_modified:
callback()
did_call_back_once.set()
last_modified = modified_at
time.sleep(1)
Thread(target=watch_for_change, daemon=True).start()
did_call_back_once.wait()
def main():
ex_name = _example_name_input()
mount, component = _hotswap()
def update_component():
print(f"Loading example: {ex_name!r}")
mount(load_one_example(ex_name))
for file in get_example_files_by_name(ex_name):
on_file_change(file, update_component)
reactpy.run(component)
def _example_name_input() -> str:
if len(sys.argv) == 1:
_print_error(
"No example argument given. Provide an example's number from above."
)
sys.exit(1)
ex_num = sys.argv[1]
try:
ex_num = int(ex_num)
except ValueError:
_print_error(
f"No example {ex_num!r} exists. Provide an example's number as an integer."
)
sys.exit(1)
ex_index = ex_num - 1
try:
return EXAMPLE_NAME_LIST[ex_index]
except IndexError:
_print_error(f"No example #{ex_num} exists. Choose from an option above.")
sys.exit(1)
def _print_error(*args) -> None:
_print_available_options()
print(*args)
def _print_available_options():
examples_by_path = {}
for i, name in enumerate(EXAMPLE_NAME_LIST):
if "/" not in name:
path = ""
else:
path, name = name.rsplit("/", 1)
examples_by_path.setdefault(path, []).append(name)
number = 1
print()
for path, names in examples_by_path.items():
title = " > ".join(
section.replace("-", " ").replace("_", " ").title()
for section in path.split("/")
if not section.startswith("_")
)
print(title)
print("-" * len(title))
for name in names:
print(f"{number}. ", name)
number += 1
print()
if __name__ == "__main__":
main()

View file

@ -1,20 +0,0 @@
import os
import sys
# all scripts should be run from the repository root so we need to insert cwd to path
# to import docs
sys.path.insert(0, os.getcwd())
from docs.app import make_app
app = make_app()
if __name__ == "__main__":
app.run(
host="0.0.0.0",
port=int(os.environ.get("PORT", 5000)),
workers=int(os.environ.get("WEB_CONCURRENCY", 1)),
debug=bool(int(os.environ.get("DEBUG", "0"))),
)

203
setup.py
View file

@ -1,203 +0,0 @@
from __future__ import print_function
import pipes
import shutil
import subprocess
import sys
import traceback
from logging import StreamHandler, getLogger
from pathlib import Path
from setuptools import find_packages, setup
from setuptools.command.develop import develop
from setuptools.command.sdist import sdist
if sys.platform == "win32":
from subprocess import list2cmdline
else:
def list2cmdline(cmd_list):
return " ".join(map(pipes.quote, cmd_list))
log = getLogger()
log.addHandler(StreamHandler(sys.stdout))
# --------------------------------------------------------------------------------------
# Basic Constants
# --------------------------------------------------------------------------------------
# the name of the project
NAME = "reactpy"
# basic paths used to gather files
ROOT_DIR = Path(__file__).parent
SRC_DIR = ROOT_DIR / "src"
PKG_DIR = SRC_DIR / NAME
JS_DIR = SRC_DIR / "client"
# --------------------------------------------------------------------------------------
# Package Definition
# --------------------------------------------------------------------------------------
package = {
"name": NAME,
"python_requires": ">=3.7",
"packages": find_packages(str(SRC_DIR)),
"package_dir": {"": "src"},
"description": "It's React, but in Python",
"author": "Ryan Morshead",
"author_email": "ryan.morshead@gmail.com",
"url": "https://github.com/reactive-python/reactpy",
"license": "MIT",
"platforms": "Linux, Mac OS X, Windows",
"keywords": ["interactive", "widgets", "DOM", "React"],
"include_package_data": True,
"zip_safe": False,
"classifiers": [
"Environment :: Web Environment",
"Framework :: AsyncIO",
"Framework :: Flask",
"Intended Audience :: Developers",
"Intended Audience :: Science/Research",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Topic :: Software Development :: User Interfaces",
"Topic :: Software Development :: Widget Sets",
"Typing :: Typed",
],
}
# --------------------------------------------------------------------------------------
# Library Version
# --------------------------------------------------------------------------------------
pkg_root_init_file = PKG_DIR / "__init__.py"
for line in pkg_root_init_file.read_text().split("\n"):
if line.startswith('__version__ = "') and line.endswith('" # DO NOT MODIFY'):
package["version"] = (
line
# get assignment value
.split("=", 1)[1]
# remove "DO NOT MODIFY" comment
.split("#", 1)[0]
# clean up leading/trailing space
.strip()
# remove the quotes
[1:-1]
)
break
else:
print(f"No version found in {pkg_root_init_file}")
sys.exit(1)
# --------------------------------------------------------------------------------------
# Requirements
# --------------------------------------------------------------------------------------
requirements = []
with (ROOT_DIR / "requirements" / "pkg-deps.txt").open() as f:
for line in map(str.strip, f):
if not line.startswith("#"):
requirements.append(line)
package["install_requires"] = requirements
_current_extras = []
extra_requirements = {"all": []} # type: ignore
extra_requirements_path = ROOT_DIR / "requirements" / "pkg-extras.txt"
with extra_requirements_path.open() as f:
for line in map(str.strip, f):
if line.startswith("#") and line[1:].strip().startswith("extra="):
_current_extras = [e.strip() for e in line.split("=", 1)[1].split(",")]
if "all" in _current_extras:
raise ValueError("%r uses the reserved extra name 'all'")
for e in _current_extras:
extra_requirements[e] = []
elif _current_extras:
for e in _current_extras:
extra_requirements[e].append(line)
extra_requirements["all"].append(line)
elif line:
raise ValueError(
f"No '# extra=<name>' header before requirements in {extra_requirements_path}"
)
package["extras_require"] = extra_requirements
# --------------------------------------------------------------------------------------
# Library Description
# --------------------------------------------------------------------------------------
with (ROOT_DIR / "README.md").open() as f:
long_description = f.read()
package["long_description"] = long_description
package["long_description_content_type"] = "text/markdown"
# --------------------------------------------------------------------------------------
# Command Line Interface
# --------------------------------------------------------------------------------------
package["entry_points"] = {"console_scripts": ["reactpy=reactpy.__main__:app"]}
# --------------------------------------------------------------------------------------
# Build Javascript
# --------------------------------------------------------------------------------------
def build_javascript_first(cls):
class Command(cls):
def run(self):
log.info("Installing Javascript...")
try:
npm = shutil.which("npm") # this is required on windows
if npm is None:
raise RuntimeError("NPM is not installed.")
for args in (f"{npm} ci", f"{npm} run build"):
args_list = args.split()
log.info(f"> {list2cmdline(args_list)}")
subprocess.run(args_list, cwd=str(JS_DIR), check=True)
except Exception:
log.error("Failed to install Javascript")
log.error(traceback.format_exc())
raise
else:
log.info("Successfully installed Javascript")
super().run()
return Command
package["cmdclass"] = {
"sdist": build_javascript_first(sdist),
"develop": build_javascript_first(develop),
}
if sys.version_info < (3, 10, 6):
from distutils.command.build import build
package["cmdclass"]["build"] = build_javascript_first(build)
else:
from setuptools.command.build_py import build_py
package["cmdclass"]["build_py"] = build_javascript_first(build_py)
# --------------------------------------------------------------------------------------
# Install It
# --------------------------------------------------------------------------------------
if __name__ == "__main__":
setup(**package)

View file

@ -1,2 +1,3 @@
tsconfig.tsbuildinfo
packages/**/package-lock.json
dist

View file

@ -22,7 +22,6 @@
"build": "vite build",
"format": "prettier --write . && eslint --fix .",
"test": "npm run check:tests",
"check:format": "prettier --check .",
"check:tests": "echo 'no tests'",
"check:types": "tsc --noEmit"
}

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -1,7 +1,7 @@
import { defineConfig } from "vite";
export default defineConfig({
build: { outDir: "../../reactpy/_client", emptyOutDir: true },
build: { emptyOutDir: true },
resolve: {
alias: {
react: "preact/compat",

View file

@ -1,5 +1,5 @@
{
"name": "client",
"name": "js",
"lockfileVersion": 2,
"requires": true,
"packages": {
@ -8,7 +8,7 @@
"workspaces": [
"packages/event-to-object",
"packages/@reactpy/client",
"ui"
"app"
],
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.58.0",
@ -18,6 +18,45 @@
"prettier": "^3.0.0-alpha.6"
}
},
"app": {
"license": "MIT",
"dependencies": {
"@reactpy/client": "^0.2.0",
"preact": "^10.7.0"
},
"devDependencies": {
"@types/react": "^17.0",
"@types/react-dom": "^17.0",
"typescript": "^4.9.5",
"vite": "^3.1.8"
}
},
"app/node_modules/@reactpy/client": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@reactpy/client/-/client-0.2.1.tgz",
"integrity": "sha512-9sgGH+pJ2BpLT+QSVe7FQLS2VQ9acHgPlO8X3qiTumGw43O0X82sm8pzya8H8dAew463SeGza/pZc0mpUBHmqA==",
"dependencies": {
"event-to-object": "^0.1.2",
"json-pointer": "^0.6.2"
},
"peerDependencies": {
"react": ">=16 <18",
"react-dom": ">=16 <18"
}
},
"app/node_modules/typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"apps/ui": {
"extraneous": true,
"license": "MIT",
@ -514,6 +553,10 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/app": {
"resolved": "app",
"link": true
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@ -2386,10 +2429,16 @@
"dev": true
},
"node_modules/nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@ -2663,9 +2712,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.21",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
"integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
"version": "8.4.24",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
"integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
"dev": true,
"funding": [
{
@ -2675,10 +2724,14 @@
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.4",
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
@ -2687,9 +2740,9 @@
}
},
"node_modules/preact": {
"version": "10.13.1",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.13.1.tgz",
"integrity": "sha512-KyoXVDU5OqTpG9LXlB3+y639JAGzl8JSBXLn1J9HTSB3gbKcuInga7bZnXLlxmK94ntTs1EFeZp0lrja2AuBYQ==",
"version": "10.15.1",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.15.1.tgz",
"integrity": "sha512-qs2ansoQEwzNiV5eAcRT1p1EC/dmEzaATVDJNiB3g2sRDWdA7b7MurXdJjB2+/WQktGWZwxvDrnuRFbWuIr64g==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
@ -2810,12 +2863,12 @@
}
},
"node_modules/resolve": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
"version": "1.22.2",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
"integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
"dev": true,
"dependencies": {
"is-core-module": "^2.9.0",
"is-core-module": "^2.11.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
@ -3232,10 +3285,6 @@
"node": ">=12.20"
}
},
"node_modules/ui": {
"resolved": "ui",
"link": true
},
"node_modules/unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
@ -3279,9 +3328,9 @@
}
},
"node_modules/vite": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz",
"integrity": "sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==",
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz",
"integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==",
"dev": true,
"dependencies": {
"esbuild": "^0.15.9",
@ -3458,7 +3507,7 @@
}
},
"packages/@reactpy/client": {
"version": "0.3.0",
"version": "0.3.1",
"license": "MIT",
"dependencies": {
"event-to-object": "^0.1.2",
@ -3556,6 +3605,7 @@
"extraneous": true
},
"ui": {
"extraneous": true,
"license": "MIT",
"dependencies": {
"@reactpy/client": "^0.2.0",
@ -3567,32 +3617,6 @@
"typescript": "^4.9.5",
"vite": "^3.1.8"
}
},
"ui/node_modules/@reactpy/client": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@reactpy/client/-/client-0.2.1.tgz",
"integrity": "sha512-9sgGH+pJ2BpLT+QSVe7FQLS2VQ9acHgPlO8X3qiTumGw43O0X82sm8pzya8H8dAew463SeGza/pZc0mpUBHmqA==",
"dependencies": {
"event-to-object": "^0.1.2",
"json-pointer": "^0.6.2"
},
"peerDependencies": {
"react": ">=16 <18",
"react-dom": ">=16 <18"
}
},
"ui/node_modules/typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
}
},
"dependencies": {
@ -3923,6 +3947,34 @@
"color-convert": "^2.0.1"
}
},
"app": {
"version": "file:app",
"requires": {
"@reactpy/client": "^0.2.0",
"@types/react": "^17.0",
"@types/react-dom": "^17.0",
"preact": "^10.7.0",
"typescript": "^4.9.5",
"vite": "^3.1.8"
},
"dependencies": {
"@reactpy/client": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@reactpy/client/-/client-0.2.1.tgz",
"integrity": "sha512-9sgGH+pJ2BpLT+QSVe7FQLS2VQ9acHgPlO8X3qiTumGw43O0X82sm8pzya8H8dAew463SeGza/pZc0mpUBHmqA==",
"requires": {
"event-to-object": "^0.1.2",
"json-pointer": "^0.6.2"
}
},
"typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true
}
}
},
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@ -5233,9 +5285,9 @@
"dev": true
},
"nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"dev": true
},
"natural-compare": {
@ -5424,20 +5476,20 @@
"dev": true
},
"postcss": {
"version": "8.4.21",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
"integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
"version": "8.4.24",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
"integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
"dev": true,
"requires": {
"nanoid": "^3.3.4",
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
},
"preact": {
"version": "10.13.1",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.13.1.tgz",
"integrity": "sha512-KyoXVDU5OqTpG9LXlB3+y639JAGzl8JSBXLn1J9HTSB3gbKcuInga7bZnXLlxmK94ntTs1EFeZp0lrja2AuBYQ=="
"version": "10.15.1",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.15.1.tgz",
"integrity": "sha512-qs2ansoQEwzNiV5eAcRT1p1EC/dmEzaATVDJNiB3g2sRDWdA7b7MurXdJjB2+/WQktGWZwxvDrnuRFbWuIr64g=="
},
"prelude-ls": {
"version": "1.2.1",
@ -5513,12 +5565,12 @@
}
},
"resolve": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
"version": "1.22.2",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
"integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
"dev": true,
"requires": {
"is-core-module": "^2.9.0",
"is-core-module": "^2.11.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
}
@ -5802,34 +5854,6 @@
"dev": true,
"peer": true
},
"ui": {
"version": "file:ui",
"requires": {
"@reactpy/client": "^0.2.0",
"@types/react": "^17.0",
"@types/react-dom": "^17.0",
"preact": "^10.7.0",
"typescript": "^4.9.5",
"vite": "^3.1.8"
},
"dependencies": {
"@reactpy/client": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@reactpy/client/-/client-0.2.1.tgz",
"integrity": "sha512-9sgGH+pJ2BpLT+QSVe7FQLS2VQ9acHgPlO8X3qiTumGw43O0X82sm8pzya8H8dAew463SeGza/pZc0mpUBHmqA==",
"requires": {
"event-to-object": "^0.1.2",
"json-pointer": "^0.6.2"
}
},
"typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true
}
}
},
"unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
@ -5864,9 +5888,9 @@
}
},
"vite": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz",
"integrity": "sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==",
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz",
"integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==",
"dev": true,
"requires": {
"esbuild": "^0.15.9",

View file

@ -4,15 +4,17 @@
"publish": "npm --workspaces publish",
"test": "npm --workspaces test",
"build": "npm --workspaces run build",
"format": "prettier --write . && eslint --fix .",
"check:format": "prettier --check . && eslint .",
"format": "npm run prettier -- --write && npm run eslint -- --fix",
"check:format": "npm run prettier -- --check && npm run eslint",
"check:tests": "npm --workspaces run check:tests",
"check:types": "npm --workspaces run check:types"
"check:types": "npm --workspaces run check:types",
"prettier": "prettier --ignore-path .gitignore .",
"eslint": "eslint --ignore-path .gitignore ."
},
"workspaces": [
"packages/event-to-object",
"packages/@reactpy/client",
"ui"
"app"
],
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.58.0",

Some files were not shown because too many files have changed in this diff Show more