From 135f2d11fbb751db277daf9d58618c983b615924 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Mon, 29 May 2023 11:53:27 -0600 Subject: [PATCH 01/26] re-organize dirs + start using hatch --- .gitignore | 3 - src/{client => js}/.eslintrc.json | 0 src/{client => js}/.gitignore | 0 src/{client => js}/README.md | 0 src/{client/ui => js/app}/.eslintrc.json | 0 src/{client/ui => js/app}/index.html | 0 src/{client/ui => js/app}/package-lock.json | 0 src/{client/ui => js/app}/package.json | 0 .../app}/public/assets/reactpy-logo.ico | Bin src/{client/ui => js/app}/src/index.ts | 0 src/{client/ui => js/app}/tsconfig.json | 0 src/{client/ui => js/app}/vite.config.js | 2 +- src/{client => js}/package-lock.json | 214 ++++++++++-------- src/{client => js}/package.json | 2 +- .../packages/@reactpy/client/.gitignore | 0 .../packages/@reactpy/client/README.md | 0 .../packages/@reactpy/client/package.json | 0 .../@reactpy/client/src/components.tsx | 0 .../packages/@reactpy/client/src/index.ts | 0 .../packages/@reactpy/client/src/logger.ts | 0 .../packages/@reactpy/client/src/messages.ts | 0 .../packages/@reactpy/client/src/mount.tsx | 0 .../@reactpy/client/src/reactpy-client.ts | 0 .../@reactpy/client/src/reactpy-vdom.tsx | 0 .../packages/@reactpy/client/tsconfig.json | 0 .../packages/event-to-object/package.json | 0 .../packages/event-to-object/src/events.ts | 0 .../packages/event-to-object/src/index.ts | 0 .../tests/event-to-object.test.ts | 0 .../event-to-object/tests/tooling/check.ts | 0 .../event-to-object/tests/tooling/mock.ts | 0 .../event-to-object/tests/tooling/setup.js | 0 .../packages/event-to-object/tsconfig.json | 0 .../event-to-object/tsconfig.tests.json | 0 src/{client => js}/tsconfig.package.json | 0 src/py/reactpy/.gitignore | 2 + MANIFEST.in => src/py/reactpy/MANIFEST.in | 0 .../py/reactpy/pyproject-temp.toml | 0 src/py/reactpy/pyproject.toml | 211 +++++++++++++++++ src/{ => py/reactpy}/reactpy/__init__.py | 1 - src/{ => py/reactpy}/reactpy/__main__.py | 0 .../reactpy}/reactpy/_console/__init__.py | 0 .../reactpy}/reactpy/_console/ast_utils.py | 11 +- .../_console/rewrite_camel_case_props.py | 4 +- .../reactpy}/reactpy/_console/rewrite_keys.py | 3 +- src/{ => py/reactpy}/reactpy/_option.py | 11 +- src/{ => py/reactpy}/reactpy/_warnings.py | 0 .../reactpy}/reactpy/backend/__init__.py | 0 .../reactpy}/reactpy/backend/_common.py | 9 +- .../reactpy}/reactpy/backend/default.py | 15 +- .../reactpy}/reactpy/backend/fastapi.py | 3 +- src/{ => py/reactpy}/reactpy/backend/flask.py | 16 +- src/{ => py/reactpy}/reactpy/backend/hooks.py | 7 +- src/{ => py/reactpy}/reactpy/backend/sanic.py | 22 +- .../reactpy}/reactpy/backend/starlette.py | 25 +- .../reactpy}/reactpy/backend/tornado.py | 26 +-- src/{ => py/reactpy}/reactpy/backend/types.py | 1 - src/{ => py/reactpy}/reactpy/backend/utils.py | 10 +- src/{ => py/reactpy}/reactpy/config.py | 3 +- src/{ => py/reactpy}/reactpy/core/__init__.py | 0 src/{ => py/reactpy}/reactpy/core/_f_back.py | 0 .../reactpy}/reactpy/core/_thread_local.py | 1 - .../reactpy}/reactpy/core/component.py | 13 +- src/{ => py/reactpy}/reactpy/core/events.py | 12 +- src/{ => py/reactpy}/reactpy/core/hooks.py | 8 +- src/{ => py/reactpy}/reactpy/core/layout.py | 40 ++-- src/{ => py/reactpy}/reactpy/core/serve.py | 1 - src/{ => py/reactpy}/reactpy/core/types.py | 23 +- src/{ => py/reactpy}/reactpy/core/vdom.py | 12 +- src/{ => py/reactpy}/reactpy/future.py | 0 src/{ => py/reactpy}/reactpy/html.py | 13 +- src/{ => py/reactpy}/reactpy/logging.py | 3 +- src/{ => py/reactpy}/reactpy/py.typed | 0 src/{ => py/reactpy}/reactpy/sample.py | 6 +- src/{ => py/reactpy}/reactpy/svg.py | 1 - .../reactpy}/reactpy/testing/__init__.py | 14 +- .../reactpy}/reactpy/testing/backend.py | 31 +-- .../reactpy}/reactpy/testing/common.py | 6 +- .../reactpy}/reactpy/testing/display.py | 6 +- src/{ => py/reactpy}/reactpy/testing/logs.py | 0 src/{ => py/reactpy}/reactpy/types.py | 9 +- src/{ => py/reactpy}/reactpy/utils.py | 17 +- src/{ => py/reactpy}/reactpy/web/__init__.py | 3 +- src/{ => py/reactpy}/reactpy/web/module.py | 20 +- .../reactpy}/reactpy/web/templates/react.js | 2 +- src/{ => py/reactpy}/reactpy/web/utils.py | 1 - src/{ => py/reactpy}/reactpy/widgets.py | 15 +- setup.py => src/py/reactpy/setup.py | 11 +- {tests => src/py/reactpy/tests}/__init__.py | 0 {tests => src/py/reactpy/tests}/conftest.py | 0 .../reactpy/tests}/test__console/__init__.py | 0 .../test_rewrite_camel_case_props.py | 1 - .../tests}/test__console/test_rewrite_keys.py | 1 - .../py/reactpy/tests}/test__option.py | 0 .../reactpy/tests}/test_backend/__init__.py | 0 .../tests}/test_backend/test__common.py | 0 .../reactpy/tests}/test_backend/test_all.py | 2 +- .../reactpy/tests}/test_backend/test_utils.py | 2 +- .../py/reactpy/tests}/test_client.py | 3 +- .../py/reactpy/tests}/test_config.py | 0 .../py/reactpy/tests}/test_core/__init__.py | 0 .../tests}/test_core/test_component.py | 0 .../reactpy/tests}/test_core/test_events.py | 2 +- .../py/reactpy/tests}/test_core/test_hooks.py | 24 +- .../reactpy/tests}/test_core/test_layout.py | 26 ++- .../py/reactpy/tests}/test_core/test_serve.py | 1 - .../py/reactpy/tests}/test_core/test_vdom.py | 3 +- {tests => src/py/reactpy/tests}/test_html.py | 0 .../py/reactpy/tests}/test_sample.py | 0 .../py/reactpy/tests}/test_testing.py | 23 +- {tests => src/py/reactpy/tests}/test_utils.py | 0 .../py/reactpy/tests}/test_web/__init__.py | 0 .../js_fixtures/component-can-have-child.js | 6 +- .../js_fixtures/export-resolution/index.js | 2 + .../js_fixtures/export-resolution/one.js | 2 +- .../js_fixtures/export-resolution/two.js | 2 +- .../test_web/js_fixtures/exports-syntax.js | 0 .../js_fixtures/exports-two-components.js | 6 +- .../set-flag-when-unmount-is-called.js | 2 +- .../test_web/js_fixtures/simple-button.js | 4 +- .../py/reactpy/tests}/test_web/test_module.py | 1 - .../py/reactpy/tests}/test_web/test_utils.py | 1 - .../py/reactpy/tests}/test_widgets.py | 1 - .../py/reactpy/tests}/tooling/__init__.py | 0 .../py/reactpy/tests}/tooling/asserts.py | 4 +- .../py/reactpy/tests}/tooling/common.py | 1 - .../py/reactpy/tests}/tooling/hooks.py | 0 .../py/reactpy/tests}/tooling/loop.py | 7 +- .../js_fixtures/export-resolution/index.js | 2 - 129 files changed, 615 insertions(+), 383 deletions(-) rename src/{client => js}/.eslintrc.json (100%) rename src/{client => js}/.gitignore (100%) rename src/{client => js}/README.md (100%) rename src/{client/ui => js/app}/.eslintrc.json (100%) rename src/{client/ui => js/app}/index.html (100%) rename src/{client/ui => js/app}/package-lock.json (100%) rename src/{client/ui => js/app}/package.json (100%) rename src/{client/ui => js/app}/public/assets/reactpy-logo.ico (100%) rename src/{client/ui => js/app}/src/index.ts (100%) rename src/{client/ui => js/app}/tsconfig.json (100%) rename src/{client/ui => js/app}/vite.config.js (75%) rename src/{client => js}/package-lock.json (98%) rename src/{client => js}/package.json (98%) rename src/{client => js}/packages/@reactpy/client/.gitignore (100%) rename src/{client => js}/packages/@reactpy/client/README.md (100%) rename src/{client => js}/packages/@reactpy/client/package.json (100%) rename src/{client => js}/packages/@reactpy/client/src/components.tsx (100%) rename src/{client => js}/packages/@reactpy/client/src/index.ts (100%) rename src/{client => js}/packages/@reactpy/client/src/logger.ts (100%) rename src/{client => js}/packages/@reactpy/client/src/messages.ts (100%) rename src/{client => js}/packages/@reactpy/client/src/mount.tsx (100%) rename src/{client => js}/packages/@reactpy/client/src/reactpy-client.ts (100%) rename src/{client => js}/packages/@reactpy/client/src/reactpy-vdom.tsx (100%) rename src/{client => js}/packages/@reactpy/client/tsconfig.json (100%) rename src/{client => js}/packages/event-to-object/package.json (100%) rename src/{client => js}/packages/event-to-object/src/events.ts (100%) rename src/{client => js}/packages/event-to-object/src/index.ts (100%) rename src/{client => js}/packages/event-to-object/tests/event-to-object.test.ts (100%) rename src/{client => js}/packages/event-to-object/tests/tooling/check.ts (100%) rename src/{client => js}/packages/event-to-object/tests/tooling/mock.ts (100%) rename src/{client => js}/packages/event-to-object/tests/tooling/setup.js (100%) rename src/{client => js}/packages/event-to-object/tsconfig.json (100%) rename src/{client => js}/packages/event-to-object/tsconfig.tests.json (100%) rename src/{client => js}/tsconfig.package.json (100%) create mode 100644 src/py/reactpy/.gitignore rename MANIFEST.in => src/py/reactpy/MANIFEST.in (100%) rename pyproject.toml => src/py/reactpy/pyproject-temp.toml (100%) create mode 100644 src/py/reactpy/pyproject.toml rename src/{ => py/reactpy}/reactpy/__init__.py (99%) rename src/{ => py/reactpy}/reactpy/__main__.py (100%) rename src/{ => py/reactpy}/reactpy/_console/__init__.py (100%) rename src/{ => py/reactpy}/reactpy/_console/ast_utils.py (95%) rename src/{ => py/reactpy}/reactpy/_console/rewrite_camel_case_props.py (97%) rename src/{ => py/reactpy}/reactpy/_console/rewrite_keys.py (97%) rename src/{ => py/reactpy}/reactpy/_option.py (92%) rename src/{ => py/reactpy}/reactpy/_warnings.py (100%) rename src/{ => py/reactpy}/reactpy/backend/__init__.py (100%) rename src/{ => py/reactpy}/reactpy/backend/_common.py (94%) rename src/{ => py/reactpy}/reactpy/backend/default.py (80%) rename src/{ => py/reactpy}/reactpy/backend/fastapi.py (95%) rename src/{ => py/reactpy}/reactpy/backend/flask.py (95%) rename src/{ => py/reactpy}/reactpy/backend/hooks.py (83%) rename src/{ => py/reactpy}/reactpy/backend/sanic.py (95%) rename src/{ => py/reactpy}/reactpy/backend/starlette.py (95%) rename src/{ => py/reactpy}/reactpy/backend/tornado.py (94%) rename src/{ => py/reactpy}/reactpy/backend/types.py (99%) rename src/{ => py/reactpy}/reactpy/backend/utils.py (92%) rename src/{ => py/reactpy}/reactpy/config.py (97%) rename src/{ => py/reactpy}/reactpy/core/__init__.py (100%) rename src/{ => py/reactpy}/reactpy/core/_f_back.py (100%) rename src/{ => py/reactpy}/reactpy/core/_thread_local.py (99%) rename src/{ => py/reactpy}/reactpy/core/component.py (84%) rename src/{ => py/reactpy}/reactpy/core/events.py (95%) rename src/{ => py/reactpy}/reactpy/core/hooks.py (99%) rename src/{ => py/reactpy}/reactpy/core/layout.py (96%) rename src/{ => py/reactpy}/reactpy/core/serve.py (99%) rename src/{ => py/reactpy}/reactpy/core/types.py (93%) rename src/{ => py/reactpy}/reactpy/core/vdom.py (97%) rename src/{ => py/reactpy}/reactpy/future.py (100%) rename src/{ => py/reactpy}/reactpy/html.py (96%) rename src/{ => py/reactpy}/reactpy/logging.py (95%) rename src/{ => py/reactpy}/reactpy/py.typed (100%) rename src/{ => py/reactpy}/reactpy/sample.py (81%) rename src/{ => py/reactpy}/reactpy/svg.py (99%) rename src/{ => py/reactpy}/reactpy/testing/__init__.py (59%) rename src/{ => py/reactpy}/reactpy/testing/backend.py (90%) rename src/{ => py/reactpy}/reactpy/testing/common.py (97%) rename src/{ => py/reactpy}/reactpy/testing/display.py (94%) rename src/{ => py/reactpy}/reactpy/testing/logs.py (100%) rename src/{ => py/reactpy}/reactpy/types.py (81%) rename src/{ => py/reactpy}/reactpy/utils.py (94%) rename src/{ => py/reactpy}/reactpy/web/__init__.py (87%) rename src/{ => py/reactpy}/reactpy/web/module.py (95%) rename src/{ => py/reactpy}/reactpy/web/templates/react.js (99%) rename src/{ => py/reactpy}/reactpy/web/utils.py (99%) rename src/{ => py/reactpy}/reactpy/widgets.py (89%) rename setup.py => src/py/reactpy/setup.py (95%) rename {tests => src/py/reactpy/tests}/__init__.py (100%) rename {tests => src/py/reactpy/tests}/conftest.py (100%) rename {tests => src/py/reactpy/tests}/test__console/__init__.py (100%) rename {tests => src/py/reactpy/tests}/test__console/test_rewrite_camel_case_props.py (99%) rename {tests => src/py/reactpy/tests}/test__console/test_rewrite_keys.py (99%) rename {tests => src/py/reactpy/tests}/test__option.py (100%) rename {tests => src/py/reactpy/tests}/test_backend/__init__.py (100%) rename {tests => src/py/reactpy/tests}/test_backend/test__common.py (100%) rename {tests => src/py/reactpy/tests}/test_backend/test_all.py (98%) rename {tests => src/py/reactpy/tests}/test_backend/test_utils.py (95%) rename {tests => src/py/reactpy/tests}/test_client.py (98%) rename {tests => src/py/reactpy/tests}/test_config.py (100%) rename {tests => src/py/reactpy/tests}/test_core/__init__.py (100%) rename {tests => src/py/reactpy/tests}/test_core/test_component.py (100%) rename {tests => src/py/reactpy/tests}/test_core/test_events.py (99%) rename {tests => src/py/reactpy/tests}/test_core/test_hooks.py (98%) rename {tests => src/py/reactpy/tests}/test_core/test_layout.py (98%) rename {tests => src/py/reactpy/tests}/test_core/test_serve.py (99%) rename {tests => src/py/reactpy/tests}/test_core/test_vdom.py (99%) rename {tests => src/py/reactpy/tests}/test_html.py (100%) rename {tests => src/py/reactpy/tests}/test_sample.py (100%) rename {tests => src/py/reactpy/tests}/test_testing.py (91%) rename {tests => src/py/reactpy/tests}/test_utils.py (100%) rename {tests => src/py/reactpy/tests}/test_web/__init__.py (100%) rename {tests => src/py/reactpy/tests}/test_web/js_fixtures/component-can-have-child.js (90%) create mode 100644 src/py/reactpy/tests/test_web/js_fixtures/export-resolution/index.js rename {tests => src/py/reactpy/tests}/test_web/js_fixtures/export-resolution/one.js (88%) rename {tests => src/py/reactpy/tests}/test_web/js_fixtures/export-resolution/two.js (65%) rename {tests => src/py/reactpy/tests}/test_web/js_fixtures/exports-syntax.js (100%) rename {tests => src/py/reactpy/tests}/test_web/js_fixtures/exports-two-components.js (80%) rename {tests => src/py/reactpy/tests}/test_web/js_fixtures/set-flag-when-unmount-is-called.js (99%) rename {tests => src/py/reactpy/tests}/test_web/js_fixtures/simple-button.js (95%) rename {tests => src/py/reactpy/tests}/test_web/test_module.py (99%) rename {tests => src/py/reactpy/tests}/test_web/test_utils.py (99%) rename {tests => src/py/reactpy/tests}/test_widgets.py (99%) rename {tests => src/py/reactpy/tests}/tooling/__init__.py (100%) rename {tests => src/py/reactpy/tests}/tooling/asserts.py (62%) rename {tests => src/py/reactpy/tests}/tooling/common.py (99%) rename {tests => src/py/reactpy/tests}/tooling/hooks.py (100%) rename {tests => src/py/reactpy/tests}/tooling/loop.py (92%) delete mode 100644 tests/test_web/js_fixtures/export-resolution/index.js diff --git a/.gitignore b/.gitignore index 652e5015b..20c041e11 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ -# --- Build Artifacts --- -src/reactpy/_client - # --- Jupyter --- *.ipynb_checkpoints *Untitled*.ipynb diff --git a/src/client/.eslintrc.json b/src/js/.eslintrc.json similarity index 100% rename from src/client/.eslintrc.json rename to src/js/.eslintrc.json diff --git a/src/client/.gitignore b/src/js/.gitignore similarity index 100% rename from src/client/.gitignore rename to src/js/.gitignore diff --git a/src/client/README.md b/src/js/README.md similarity index 100% rename from src/client/README.md rename to src/js/README.md diff --git a/src/client/ui/.eslintrc.json b/src/js/app/.eslintrc.json similarity index 100% rename from src/client/ui/.eslintrc.json rename to src/js/app/.eslintrc.json diff --git a/src/client/ui/index.html b/src/js/app/index.html similarity index 100% rename from src/client/ui/index.html rename to src/js/app/index.html diff --git a/src/client/ui/package-lock.json b/src/js/app/package-lock.json similarity index 100% rename from src/client/ui/package-lock.json rename to src/js/app/package-lock.json diff --git a/src/client/ui/package.json b/src/js/app/package.json similarity index 100% rename from src/client/ui/package.json rename to src/js/app/package.json diff --git a/src/client/ui/public/assets/reactpy-logo.ico b/src/js/app/public/assets/reactpy-logo.ico similarity index 100% rename from src/client/ui/public/assets/reactpy-logo.ico rename to src/js/app/public/assets/reactpy-logo.ico diff --git a/src/client/ui/src/index.ts b/src/js/app/src/index.ts similarity index 100% rename from src/client/ui/src/index.ts rename to src/js/app/src/index.ts diff --git a/src/client/ui/tsconfig.json b/src/js/app/tsconfig.json similarity index 100% rename from src/client/ui/tsconfig.json rename to src/js/app/tsconfig.json diff --git a/src/client/ui/vite.config.js b/src/js/app/vite.config.js similarity index 75% rename from src/client/ui/vite.config.js rename to src/js/app/vite.config.js index 6919d8e1c..c97fb6dac 100644 --- a/src/client/ui/vite.config.js +++ b/src/js/app/vite.config.js @@ -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", diff --git a/src/client/package-lock.json b/src/js/package-lock.json similarity index 98% rename from src/client/package-lock.json rename to src/js/package-lock.json index 109324f5a..2edfdd260 100644 --- a/src/client/package-lock.json +++ b/src/js/package-lock.json @@ -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", diff --git a/src/client/package.json b/src/js/package.json similarity index 98% rename from src/client/package.json rename to src/js/package.json index 3992d59ed..b34abd67a 100644 --- a/src/client/package.json +++ b/src/js/package.json @@ -12,7 +12,7 @@ "workspaces": [ "packages/event-to-object", "packages/@reactpy/client", - "ui" + "app" ], "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.58.0", diff --git a/src/client/packages/@reactpy/client/.gitignore b/src/js/packages/@reactpy/client/.gitignore similarity index 100% rename from src/client/packages/@reactpy/client/.gitignore rename to src/js/packages/@reactpy/client/.gitignore diff --git a/src/client/packages/@reactpy/client/README.md b/src/js/packages/@reactpy/client/README.md similarity index 100% rename from src/client/packages/@reactpy/client/README.md rename to src/js/packages/@reactpy/client/README.md diff --git a/src/client/packages/@reactpy/client/package.json b/src/js/packages/@reactpy/client/package.json similarity index 100% rename from src/client/packages/@reactpy/client/package.json rename to src/js/packages/@reactpy/client/package.json diff --git a/src/client/packages/@reactpy/client/src/components.tsx b/src/js/packages/@reactpy/client/src/components.tsx similarity index 100% rename from src/client/packages/@reactpy/client/src/components.tsx rename to src/js/packages/@reactpy/client/src/components.tsx diff --git a/src/client/packages/@reactpy/client/src/index.ts b/src/js/packages/@reactpy/client/src/index.ts similarity index 100% rename from src/client/packages/@reactpy/client/src/index.ts rename to src/js/packages/@reactpy/client/src/index.ts diff --git a/src/client/packages/@reactpy/client/src/logger.ts b/src/js/packages/@reactpy/client/src/logger.ts similarity index 100% rename from src/client/packages/@reactpy/client/src/logger.ts rename to src/js/packages/@reactpy/client/src/logger.ts diff --git a/src/client/packages/@reactpy/client/src/messages.ts b/src/js/packages/@reactpy/client/src/messages.ts similarity index 100% rename from src/client/packages/@reactpy/client/src/messages.ts rename to src/js/packages/@reactpy/client/src/messages.ts diff --git a/src/client/packages/@reactpy/client/src/mount.tsx b/src/js/packages/@reactpy/client/src/mount.tsx similarity index 100% rename from src/client/packages/@reactpy/client/src/mount.tsx rename to src/js/packages/@reactpy/client/src/mount.tsx diff --git a/src/client/packages/@reactpy/client/src/reactpy-client.ts b/src/js/packages/@reactpy/client/src/reactpy-client.ts similarity index 100% rename from src/client/packages/@reactpy/client/src/reactpy-client.ts rename to src/js/packages/@reactpy/client/src/reactpy-client.ts diff --git a/src/client/packages/@reactpy/client/src/reactpy-vdom.tsx b/src/js/packages/@reactpy/client/src/reactpy-vdom.tsx similarity index 100% rename from src/client/packages/@reactpy/client/src/reactpy-vdom.tsx rename to src/js/packages/@reactpy/client/src/reactpy-vdom.tsx diff --git a/src/client/packages/@reactpy/client/tsconfig.json b/src/js/packages/@reactpy/client/tsconfig.json similarity index 100% rename from src/client/packages/@reactpy/client/tsconfig.json rename to src/js/packages/@reactpy/client/tsconfig.json diff --git a/src/client/packages/event-to-object/package.json b/src/js/packages/event-to-object/package.json similarity index 100% rename from src/client/packages/event-to-object/package.json rename to src/js/packages/event-to-object/package.json diff --git a/src/client/packages/event-to-object/src/events.ts b/src/js/packages/event-to-object/src/events.ts similarity index 100% rename from src/client/packages/event-to-object/src/events.ts rename to src/js/packages/event-to-object/src/events.ts diff --git a/src/client/packages/event-to-object/src/index.ts b/src/js/packages/event-to-object/src/index.ts similarity index 100% rename from src/client/packages/event-to-object/src/index.ts rename to src/js/packages/event-to-object/src/index.ts diff --git a/src/client/packages/event-to-object/tests/event-to-object.test.ts b/src/js/packages/event-to-object/tests/event-to-object.test.ts similarity index 100% rename from src/client/packages/event-to-object/tests/event-to-object.test.ts rename to src/js/packages/event-to-object/tests/event-to-object.test.ts diff --git a/src/client/packages/event-to-object/tests/tooling/check.ts b/src/js/packages/event-to-object/tests/tooling/check.ts similarity index 100% rename from src/client/packages/event-to-object/tests/tooling/check.ts rename to src/js/packages/event-to-object/tests/tooling/check.ts diff --git a/src/client/packages/event-to-object/tests/tooling/mock.ts b/src/js/packages/event-to-object/tests/tooling/mock.ts similarity index 100% rename from src/client/packages/event-to-object/tests/tooling/mock.ts rename to src/js/packages/event-to-object/tests/tooling/mock.ts diff --git a/src/client/packages/event-to-object/tests/tooling/setup.js b/src/js/packages/event-to-object/tests/tooling/setup.js similarity index 100% rename from src/client/packages/event-to-object/tests/tooling/setup.js rename to src/js/packages/event-to-object/tests/tooling/setup.js diff --git a/src/client/packages/event-to-object/tsconfig.json b/src/js/packages/event-to-object/tsconfig.json similarity index 100% rename from src/client/packages/event-to-object/tsconfig.json rename to src/js/packages/event-to-object/tsconfig.json diff --git a/src/client/packages/event-to-object/tsconfig.tests.json b/src/js/packages/event-to-object/tsconfig.tests.json similarity index 100% rename from src/client/packages/event-to-object/tsconfig.tests.json rename to src/js/packages/event-to-object/tsconfig.tests.json diff --git a/src/client/tsconfig.package.json b/src/js/tsconfig.package.json similarity index 100% rename from src/client/tsconfig.package.json rename to src/js/tsconfig.package.json diff --git a/src/py/reactpy/.gitignore b/src/py/reactpy/.gitignore new file mode 100644 index 000000000..29eb7f90f --- /dev/null +++ b/src/py/reactpy/.gitignore @@ -0,0 +1,2 @@ +# --- Build Artifacts --- +reactpy/_static diff --git a/MANIFEST.in b/src/py/reactpy/MANIFEST.in similarity index 100% rename from MANIFEST.in rename to src/py/reactpy/MANIFEST.in diff --git a/pyproject.toml b/src/py/reactpy/pyproject-temp.toml similarity index 100% rename from pyproject.toml rename to src/py/reactpy/pyproject-temp.toml diff --git a/src/py/reactpy/pyproject.toml b/src/py/reactpy/pyproject.toml new file mode 100644 index 000000000..dc32344e9 --- /dev/null +++ b/src/py/reactpy/pyproject.toml @@ -0,0 +1,211 @@ +[build-system] +requires = ["hatchling", "hatch-build-scripts>=0.0.2"] +build-backend = "hatchling.build" + +# --- Project -------------------------------------------------------------------------- + +[project] +name = "reactpy" +dynamic = ["version"] +description = 'Reactive user interfaces with pure Python' +readme = "../../../README.md" +requires-python = ">=3.9" +license = "MIT" +keywords = ["react", "javascript", "reactpy", "component"] +authors = [ + { name = "Ryan Morshead", email = "ryan.morshead@gmail.com" }, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Python", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dependencies = [ + "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", +] +[project.optional-dependencies] +starlette = [ + "starlette >=0.13.6", + "uvicorn[standard] >=0.19.0", +] +sanic = [ + "sanic >=21", + "sanic-cors", + "uvicorn[standard] >=0.19.0", +] +fastapi = [ + "fastapi >=0.63.0", + "uvicorn[standard] >=0.19.0", +] +flask = [ + "flask", + "markupsafe>=1.1.1,<2.1", + "flask-cors", + "flask-sock", +] +tornado = [ + "tornado", +] +testing = [ + "playwright", +] + +[project.urls] +Documentation = "https://github.com/reactive-python/reactpy#readme" +Issues = "https://github.com/reactive-python/reactpy/discussions" +Source = "https://github.com/reactive-python/reactpy" + +# --- Hatch ---------------------------------------------------------------------------- + +[tool.hatch.version] +path = "reactpy/__init__.py" + +[tool.hatch.envs.default] +pre-install-command = "hatch run build --hooks-only" +dependencies = [ + "coverage[toml]>=6.5", + "pytest", +] +[tool.hatch.envs.default.scripts] +test = "pytest {args:tests}" +test-cov = "coverage run -m pytest {args:tests}" +cov-report = [ + "- coverage combine", + "coverage report", +] +cov = [ + "test-cov", + "cov-report", +] + +[tool.hatch.envs.lint] +detached = true +dependencies = [ + "black>=23.1.0", + "mypy>=1.0.0", + "ruff>=0.0.243", +] + +[tool.hatch.envs.lint.scripts] +typing = "mypy --install-types --non-interactive {args:reactpy tests}" +style = [ + "ruff {args:.}", + "black --check --diff {args:.}", +] +fmt = [ + "black {args:.}", + "ruff --fix {args:.}", + "style", +] +all = [ + "style", + "typing", +] + +[[tool.hatch.build.hooks.build-scripts.scripts]] +work_dir = "../../js" +commands = [ + "npm install", + "npm run build" +] +artifacts = [ + "app/dist/" +] +out_dir = "reactpy/_static" + +# --- Black ---------------------------------------------------------------------------- + +[tool.black] +target-version = ["py37"] +line-length = 88 +skip-string-normalization = true + +# --- Ruff ----------------------------------------------------------------------------- + +[tool.ruff] +target-version = "py37" +line-length = 88 +select = [ + "A", + "ARG", + "B", + "C", + "DTZ", + "E", + "EM", + "F", + "FBT", + "I", + "ICN", + "ISC", + "N", + "PLC", + "PLE", + "PLR", + "PLW", + "Q", + "RUF", + "S", + "T", + "TID", + "UP", + "W", + "YTT", +] +ignore = [ + # Allow non-abstract empty methods in abstract base classes + "B027", + # Allow boolean positional values in function calls, like `dict.get(... True)` + "FBT003", + # 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"] + +# --- Coverage ------------------------------------------------------------------------- + +[tool.coverage.run] +source_pkgs = ["reactpy", "tests"] +branch = true +parallel = true +omit = [ + "reactpy/__init__.py", +] + +[tool.coverage.paths] +reactpy = ["reactpy", "*/reactpy/reactpy"] +tests = ["tests", "*/reactpy/tests"] + +[tool.coverage.report] +exclude_lines = [ + "no cov", + "if __name__ == .__main__.:", + "if TYPE_CHECKING:", +] diff --git a/src/reactpy/__init__.py b/src/py/reactpy/reactpy/__init__.py similarity index 99% rename from src/reactpy/__init__.py rename to src/py/reactpy/reactpy/__init__.py index c9adf6cb2..996a984b2 100644 --- a/src/reactpy/__init__.py +++ b/src/py/reactpy/reactpy/__init__.py @@ -20,7 +20,6 @@ from reactpy.core.vdom import vdom from reactpy.utils import Ref, html_to_vdom, vdom_to_html - __author__ = "The Reactive Python Team" __version__ = "1.0.0" # DO NOT MODIFY diff --git a/src/reactpy/__main__.py b/src/py/reactpy/reactpy/__main__.py similarity index 100% rename from src/reactpy/__main__.py rename to src/py/reactpy/reactpy/__main__.py diff --git a/src/reactpy/_console/__init__.py b/src/py/reactpy/reactpy/_console/__init__.py similarity index 100% rename from src/reactpy/_console/__init__.py rename to src/py/reactpy/reactpy/_console/__init__.py diff --git a/src/reactpy/_console/ast_utils.py b/src/py/reactpy/reactpy/_console/ast_utils.py similarity index 95% rename from src/reactpy/_console/ast_utils.py rename to src/py/reactpy/reactpy/_console/ast_utils.py index aeaf8f512..96c62610b 100644 --- a/src/reactpy/_console/ast_utils.py +++ b/src/py/reactpy/reactpy/_console/ast_utils.py @@ -38,12 +38,11 @@ def rewrite_changed_nodes( nodes_to_unparse.append(current_node) break else: # pragma: no cover - raise RuntimeError("Failed to change code") + msg = "Failed to change code" + raise RuntimeError(msg) # check if an nodes to rewrite contain eachother, pick outermost nodes - current_outermost_node, *sorted_nodes_to_unparse = list( - sorted(nodes_to_unparse, key=lambda n: n.lineno) - ) + current_outermost_node, *sorted_nodes_to_unparse = sorted(nodes_to_unparse, key=lambda n: n.lineno) outermost_nodes_to_unparse = [current_outermost_node] for node in sorted_nodes_to_unparse: if ( @@ -80,7 +79,7 @@ def rewrite_changed_nodes( if comments: moved_comment_lines_from_end.append(len(lines) - node.lineno) - for lineno_from_end in sorted(list(set(moved_comment_lines_from_end))): + for lineno_from_end in sorted(set(moved_comment_lines_from_end)): click.echo(f"Moved comments to {file}:{len(lines) - lineno_from_end}") return "\n".join(lines) @@ -179,7 +178,7 @@ def _find_comments(lines: list[str]) -> list[str]: def _walk_with_parent( node: ast.AST, parents: tuple[ast.AST, ...] = () ) -> Iterator[tuple[tuple[ast.AST, ...], ast.AST]]: - parents = (node,) + parents + parents = (node, *parents) for child in ast.iter_child_nodes(node): yield parents, child yield from _walk_with_parent(child, parents) diff --git a/src/reactpy/_console/rewrite_camel_case_props.py b/src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py similarity index 97% rename from src/reactpy/_console/rewrite_camel_case_props.py rename to src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py index b67c210ab..edf5b6960 100644 --- a/src/reactpy/_console/rewrite_camel_case_props.py +++ b/src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py @@ -16,7 +16,6 @@ rewrite_changed_nodes, ) - CAMEL_CASE_SUB_PATTERN = re.compile(r"(? None: """Rewrite camelCase props to snake_case""" if sys.version_info < (3, 9): # pragma: no cover - raise RuntimeError("This command requires Python>=3.9") + msg = "This command requires Python>=3.9" + raise RuntimeError(msg) for p in map(Path, paths): for f in [p] if p.is_file() else p.rglob("*.py"): diff --git a/src/reactpy/_console/rewrite_keys.py b/src/py/reactpy/reactpy/_console/rewrite_keys.py similarity index 97% rename from src/reactpy/_console/rewrite_keys.py rename to src/py/reactpy/reactpy/_console/rewrite_keys.py index 7ff672e1e..4d6dd7338 100644 --- a/src/reactpy/_console/rewrite_keys.py +++ b/src/py/reactpy/reactpy/_console/rewrite_keys.py @@ -46,7 +46,8 @@ def rewrite_keys(paths: list[str]) -> None: comments back in their original location. """ if sys.version_info < (3, 9): # pragma: no cover - raise RuntimeError("This command requires Python>=3.9") + msg = "This command requires Python>=3.9" + raise RuntimeError(msg) for p in map(Path, paths): for f in [p] if p.is_file() else p.rglob("*.py"): diff --git a/src/reactpy/_option.py b/src/py/reactpy/reactpy/_option.py similarity index 92% rename from src/reactpy/_option.py rename to src/py/reactpy/reactpy/_option.py index b86b7c0cc..633eb34fe 100644 --- a/src/reactpy/_option.py +++ b/src/py/reactpy/reactpy/_option.py @@ -6,7 +6,6 @@ from reactpy._warnings import warn - _O = TypeVar("_O") logger = getLogger(__name__) @@ -63,12 +62,12 @@ def current(self) -> _O: @current.setter def current(self, new: _O) -> None: self.set_current(new) - return None def subscribe(self, handler: Callable[[_O], None]) -> Callable[[_O], None]: """Register a callback that will be triggered when this option changes""" if not self.mutable: - raise TypeError("Immutable options cannot be subscribed to.") + msg = "Immutable options cannot be subscribed to." + raise TypeError(msg) self._subscribers.append(handler) handler(self.current) return handler @@ -83,7 +82,8 @@ def set_current(self, new: Any) -> None: Raises a ``TypeError`` if this option is not :attr:`Option.mutable`. """ if not self._mutable: - raise TypeError(f"{self} cannot be modified after initial load") + msg = f"{self} cannot be modified after initial load" + raise TypeError(msg) old = self.current new = self._current = self._validator(new) logger.debug(f"{self._name}={self._current}") @@ -107,7 +107,8 @@ def reload(self) -> None: def unset(self) -> None: """Remove the current value, the default will be used until it is set again.""" if not self._mutable: - raise TypeError(f"{self} cannot be modified after initial load") + msg = f"{self} cannot be modified after initial load" + raise TypeError(msg) old = self.current delattr(self, "_current") if self.current != old: diff --git a/src/reactpy/_warnings.py b/src/py/reactpy/reactpy/_warnings.py similarity index 100% rename from src/reactpy/_warnings.py rename to src/py/reactpy/reactpy/_warnings.py diff --git a/src/reactpy/backend/__init__.py b/src/py/reactpy/reactpy/backend/__init__.py similarity index 100% rename from src/reactpy/backend/__init__.py rename to src/py/reactpy/reactpy/backend/__init__.py diff --git a/src/reactpy/backend/_common.py b/src/py/reactpy/reactpy/backend/_common.py similarity index 94% rename from src/reactpy/backend/_common.py rename to src/py/reactpy/reactpy/backend/_common.py index 41bff8512..486f3aaec 100644 --- a/src/reactpy/backend/_common.py +++ b/src/py/reactpy/reactpy/backend/_common.py @@ -15,13 +15,12 @@ from reactpy.core.types import VdomDict from reactpy.utils import vdom_to_html - PATH_PREFIX = PurePosixPath("/_reactpy") MODULES_PATH = PATH_PREFIX / "modules" ASSETS_PATH = PATH_PREFIX / "assets" STREAM_PATH = PATH_PREFIX / "stream" -CLIENT_BUILD_DIR = Path(_reactpy_file_path).parent / "_client" +CLIENT_BUILD_DIR = Path(_reactpy_file_path).parent / "_static" / "app" / "dist" async def serve_development_asgi( @@ -87,7 +86,8 @@ def traversal_safe_path(root: str | Path, *unsafe: str | Path) -> Path: if os.path.commonprefix([root, path]) != root: # If the common prefix is not root directory we resolved outside the root dir - raise ValueError("Unsafe path") + msg = "Unsafe path" + raise ValueError(msg) return Path(path) @@ -136,4 +136,5 @@ class CommonOptions: def __post_init__(self) -> None: if self.url_prefix and not self.url_prefix.startswith("/"): - raise ValueError("Expected 'url_prefix' to start with '/'") + msg = "Expected 'url_prefix' to start with '/'" + raise ValueError(msg) diff --git a/src/reactpy/backend/default.py b/src/py/reactpy/reactpy/backend/default.py similarity index 80% rename from src/reactpy/backend/default.py rename to src/py/reactpy/reactpy/backend/default.py index b24bfc314..6177f5b64 100644 --- a/src/reactpy/backend/default.py +++ b/src/py/reactpy/reactpy/backend/default.py @@ -5,12 +5,10 @@ from sys import exc_info from typing import Any, NoReturn +from reactpy.backend.types import BackendImplementation +from reactpy.backend.utils import all_implementations from reactpy.types import RootComponentConstructor -from .types import BackendImplementation -from .utils import all_implementations - - logger = getLogger(__name__) @@ -19,7 +17,8 @@ def configure( ) -> None: """Configure the given app instance to display the given component""" if options is not None: # pragma: no cover - raise ValueError("Default implementation cannot be configured with options") + msg = "Default implementation cannot be configured with options" + raise ValueError(msg) return _default_implementation().configure(app, component) @@ -30,7 +29,8 @@ def create_development_app() -> Any: def Options(*args: Any, **kwargs: Any) -> NoReturn: """Create configuration options""" - raise ValueError("Default implementation has no options.") # pragma: no cover + msg = "Default implementation has no options." + raise ValueError(msg) # pragma: no cover async def serve_development_app( @@ -59,7 +59,8 @@ def _default_implementation() -> BackendImplementation[Any]: implementation = next(all_implementations()) except StopIteration: # pragma: no cover logger.debug("Backend implementation import failed", exc_info=exc_info()) - raise RuntimeError("No built-in server implementation installed.") + msg = "No built-in server implementation installed." + raise RuntimeError(msg) else: _DEFAULT_IMPLEMENTATION = implementation return implementation diff --git a/src/reactpy/backend/fastapi.py b/src/py/reactpy/reactpy/backend/fastapi.py similarity index 95% rename from src/reactpy/backend/fastapi.py rename to src/py/reactpy/reactpy/backend/fastapi.py index c3cd038d8..575fce1fe 100644 --- a/src/reactpy/backend/fastapi.py +++ b/src/py/reactpy/reactpy/backend/fastapi.py @@ -2,8 +2,7 @@ from fastapi import FastAPI -from . import starlette - +from reactpy.backend import starlette serve_development_app = starlette.serve_development_app """Alias for :func:`reactpy.backend.starlette.serve_development_app`""" diff --git a/src/reactpy/backend/flask.py b/src/py/reactpy/reactpy/backend/flask.py similarity index 95% rename from src/reactpy/backend/flask.py rename to src/py/reactpy/reactpy/backend/flask.py index 19dfbe1c3..5aecb05e9 100644 --- a/src/reactpy/backend/flask.py +++ b/src/py/reactpy/reactpy/backend/flask.py @@ -42,7 +42,6 @@ from reactpy.core.types import ComponentType, RootComponentConstructor from reactpy.utils import Ref - logger = logging.getLogger(__name__) @@ -113,7 +112,8 @@ def run_server() -> None: thread.join(timeout=3) # just double check it happened if thread.is_alive(): # pragma: no cover - raise RuntimeError("Failed to shutdown server.") + msg = "Failed to shutdown server." + raise RuntimeError(msg) def use_websocket() -> WebSocket: @@ -130,9 +130,9 @@ def use_connection() -> Connection[_FlaskCarrier]: """Get the current :class:`Connection`""" conn = _use_connection() if not isinstance(conn.carrier, _FlaskCarrier): # pragma: no cover + msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Flask server?" raise TypeError( - f"Connection has unexpected carrier {conn.carrier}. " - "Are you running with a Flask server?" + msg ) return conn @@ -204,11 +204,11 @@ def _dispatch_in_thread( path: str, component: ComponentType, send: Callable[[Any], None], - recv: Callable[[], Optional[Any]], + recv: Callable[[], Any | None], ) -> NoReturn: dispatch_thread_info_created = ThreadEvent() dispatch_thread_info_ref: reactpy.Ref[ - Optional[_DispatcherThreadInfo] + _DispatcherThreadInfo | None ] = reactpy.Ref(None) @copy_current_request_context @@ -216,8 +216,8 @@ def run_dispatcher() -> None: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - thread_send_queue: "ThreadQueue[Any]" = ThreadQueue() - async_recv_queue: "AsyncQueue[Any]" = AsyncQueue() + thread_send_queue: ThreadQueue[Any] = ThreadQueue() + async_recv_queue: AsyncQueue[Any] = AsyncQueue() async def send_coro(value: Any) -> None: thread_send_queue.put(value) diff --git a/src/reactpy/backend/hooks.py b/src/py/reactpy/reactpy/backend/hooks.py similarity index 83% rename from src/reactpy/backend/hooks.py rename to src/py/reactpy/reactpy/backend/hooks.py index 981461316..69a57501b 100644 --- a/src/reactpy/backend/hooks.py +++ b/src/py/reactpy/reactpy/backend/hooks.py @@ -2,11 +2,9 @@ from typing import Any, MutableMapping +from reactpy.backend.types import Connection, Location from reactpy.core.hooks import Context, create_context, use_context -from .types import Connection, Location - - # backend implementations should establish this context at the root of an app ConnectionContext: Context[Connection[Any] | None] = create_context(None) @@ -15,7 +13,8 @@ def use_connection() -> Connection[Any]: """Get the current :class:`~reactpy.backend.types.Connection`.""" conn = use_context(ConnectionContext) if conn is None: - raise RuntimeError("No backend established a connection.") # pragma: no cover + msg = "No backend established a connection." + raise RuntimeError(msg) # pragma: no cover return conn diff --git a/src/reactpy/backend/sanic.py b/src/py/reactpy/reactpy/backend/sanic.py similarity index 95% rename from src/reactpy/backend/sanic.py rename to src/py/reactpy/reactpy/backend/sanic.py index 91d20b838..475942223 100644 --- a/src/reactpy/backend/sanic.py +++ b/src/py/reactpy/reactpy/backend/sanic.py @@ -13,12 +13,7 @@ from sanic.server.websockets.connection import WebSocketConnection from sanic_cors import CORS -from reactpy.backend.types import Connection, Location -from reactpy.core.layout import Layout -from reactpy.core.serve import RecvCoroutine, SendCoroutine, Stop, serve_layout -from reactpy.core.types import RootComponentConstructor - -from ._common import ( +from reactpy.backend._common import ( ASSETS_PATH, MODULES_PATH, PATH_PREFIX, @@ -29,9 +24,12 @@ safe_web_modules_dir_path, serve_development_asgi, ) -from .hooks import ConnectionContext -from .hooks import use_connection as _use_connection - +from reactpy.backend.hooks import ConnectionContext +from reactpy.backend.hooks import use_connection as _use_connection +from reactpy.backend.types import Connection, Location +from reactpy.core.layout import Layout +from reactpy.core.serve import RecvCoroutine, SendCoroutine, Stop, serve_layout +from reactpy.core.types import RootComponentConstructor logger = logging.getLogger(__name__) @@ -83,9 +81,9 @@ def use_connection() -> Connection[_SanicCarrier]: """Get the current :class:`Connection`""" conn = _use_connection() if not isinstance(conn.carrier, _SanicCarrier): # pragma: no cover + msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Sanic server?" raise TypeError( - f"Connection has unexpected carrier {conn.carrier}. " - "Are you running with a Sanic server?" + msg ) return conn @@ -203,7 +201,7 @@ async def model_stream( def _make_send_recv_callbacks( socket: WebSocketConnection, -) -> Tuple[SendCoroutine, RecvCoroutine]: +) -> tuple[SendCoroutine, RecvCoroutine]: async def sock_send(value: Any) -> None: await socket.send(json.dumps(value)) diff --git a/src/reactpy/backend/starlette.py b/src/py/reactpy/reactpy/backend/starlette.py similarity index 95% rename from src/reactpy/backend/starlette.py rename to src/py/reactpy/reactpy/backend/starlette.py index 4ab16e91b..0d8203fc7 100644 --- a/src/reactpy/backend/starlette.py +++ b/src/py/reactpy/reactpy/backend/starlette.py @@ -13,14 +13,7 @@ from starlette.staticfiles import StaticFiles from starlette.websockets import WebSocket, WebSocketDisconnect -from reactpy.backend.hooks import ConnectionContext -from reactpy.backend.types import Connection, Location -from reactpy.config import REACTPY_WEB_MODULES_DIR -from reactpy.core.layout import Layout -from reactpy.core.serve import RecvCoroutine, SendCoroutine, serve_layout -from reactpy.core.types import RootComponentConstructor - -from ._common import ( +from reactpy.backend._common import ( ASSETS_PATH, CLIENT_BUILD_DIR, MODULES_PATH, @@ -29,9 +22,13 @@ read_client_index_html, serve_development_asgi, ) -from .hooks import ConnectionContext -from .hooks import use_connection as _use_connection - +from reactpy.backend.hooks import ConnectionContext +from reactpy.backend.hooks import use_connection as _use_connection +from reactpy.backend.types import Connection, Location +from reactpy.config import REACTPY_WEB_MODULES_DIR +from reactpy.core.layout import Layout +from reactpy.core.serve import RecvCoroutine, SendCoroutine, serve_layout +from reactpy.core.types import RootComponentConstructor logger = logging.getLogger(__name__) @@ -79,9 +76,9 @@ def use_websocket() -> WebSocket: def use_connection() -> Connection[WebSocket]: conn = _use_connection() if not isinstance(conn.carrier, WebSocket): # pragma: no cover + msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Flask server?" raise TypeError( - f"Connection has unexpected carrier {conn.carrier}. " - "Are you running with a Flask server?" + msg ) return conn @@ -166,7 +163,7 @@ async def model_stream(socket: WebSocket) -> None: def _make_send_recv_callbacks( socket: WebSocket, -) -> Tuple[SendCoroutine, RecvCoroutine]: +) -> tuple[SendCoroutine, RecvCoroutine]: async def sock_send(value: Any) -> None: await socket.send_text(json.dumps(value)) diff --git a/src/reactpy/backend/tornado.py b/src/py/reactpy/reactpy/backend/tornado.py similarity index 94% rename from src/reactpy/backend/tornado.py rename to src/py/reactpy/reactpy/backend/tornado.py index b79f5fa0e..74e7a1343 100644 --- a/src/reactpy/backend/tornado.py +++ b/src/py/reactpy/reactpy/backend/tornado.py @@ -16,13 +16,7 @@ from tornado.wsgi import WSGIContainer from typing_extensions import TypeAlias -from reactpy.backend.types import Connection, Location -from reactpy.config import REACTPY_WEB_MODULES_DIR -from reactpy.core.layout import Layout -from reactpy.core.serve import serve_layout -from reactpy.core.types import ComponentConstructor - -from ._common import ( +from reactpy.backend._common import ( ASSETS_PATH, CLIENT_BUILD_DIR, MODULES_PATH, @@ -30,9 +24,13 @@ CommonOptions, read_client_index_html, ) -from .hooks import ConnectionContext -from .hooks import use_connection as _use_connection - +from reactpy.backend.hooks import ConnectionContext +from reactpy.backend.hooks import use_connection as _use_connection +from reactpy.backend.types import Connection, Location +from reactpy.config import REACTPY_WEB_MODULES_DIR +from reactpy.core.layout import Layout +from reactpy.core.serve import serve_layout +from reactpy.core.types import ComponentConstructor Options = CommonOptions """Render server config for :func:`reactpy.backend.tornado.configure`""" @@ -101,9 +99,9 @@ def use_request() -> HTTPServerRequest: def use_connection() -> Connection[HTTPServerRequest]: conn = _use_connection() if not isinstance(conn.carrier, HTTPServerRequest): # pragma: no cover + msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Flask server?" raise TypeError( - f"Connection has unexpected carrier {conn.carrier}. " - "Are you running with a Flask server?" + msg ) return conn @@ -135,7 +133,7 @@ def _add_handler( app: Application, options: Options, handlers: _RouteHandlerSpecs ) -> None: prefixed_handlers: list[Any] = [ - (urljoin(options.url_prefix, route_pattern),) + tuple(handler_info) + (urljoin(options.url_prefix, route_pattern), *tuple(handler_info)) for route_pattern, *handler_info in handlers ] app.add_handlers(r".*", prefixed_handlers) @@ -181,7 +179,7 @@ def initialize( self._url_prefix = url_prefix async def open(self, path: str = "", *args: Any, **kwargs: Any) -> None: - message_queue: "AsyncQueue[str]" = AsyncQueue() + message_queue: AsyncQueue[str] = AsyncQueue() async def send(value: Any) -> None: await self.write_message(json.dumps(value)) diff --git a/src/reactpy/backend/types.py b/src/py/reactpy/reactpy/backend/types.py similarity index 99% rename from src/reactpy/backend/types.py rename to src/py/reactpy/reactpy/backend/types.py index fb19aa5d0..fa02c85f8 100644 --- a/src/reactpy/backend/types.py +++ b/src/py/reactpy/reactpy/backend/types.py @@ -8,7 +8,6 @@ from reactpy.core.types import RootComponentConstructor - _App = TypeVar("_App") diff --git a/src/reactpy/backend/utils.py b/src/py/reactpy/reactpy/backend/utils.py similarity index 92% rename from src/reactpy/backend/utils.py rename to src/py/reactpy/reactpy/backend/utils.py index b7576cfb0..0dc08d703 100644 --- a/src/reactpy/backend/utils.py +++ b/src/py/reactpy/reactpy/backend/utils.py @@ -7,11 +7,9 @@ from importlib import import_module from typing import Any, Iterator +from reactpy.backend.types import BackendImplementation from reactpy.types import RootComponentConstructor -from .types import BackendImplementation - - logger = logging.getLogger(__name__) SUPPORTED_PACKAGES = ( @@ -70,8 +68,9 @@ def find_available_port( pass else: return port + msg = f"Host {host!r} has no available port in range {port_max}-{port_max}" raise RuntimeError( - f"Host {host!r} has no available port in range {port_max}-{port_max}" + msg ) @@ -86,7 +85,8 @@ def all_implementations() -> Iterator[BackendImplementation[Any]]: continue if not isinstance(module, BackendImplementation): # pragma: no cover - raise TypeError(f"{module.__name__!r} is an invalid implementation") + msg = f"{module.__name__!r} is an invalid implementation" + raise TypeError(msg) yield module diff --git a/src/reactpy/config.py b/src/py/reactpy/reactpy/config.py similarity index 97% rename from src/reactpy/config.py rename to src/py/reactpy/reactpy/config.py index 8f26271c4..ace93b6d2 100644 --- a/src/reactpy/config.py +++ b/src/py/reactpy/reactpy/config.py @@ -6,8 +6,7 @@ from pathlib import Path from tempfile import TemporaryDirectory -from ._option import Option as _Option - +from reactpy._option import Option as _Option REACTPY_DEBUG_MODE = _Option( "REACTPY_DEBUG_MODE", diff --git a/src/reactpy/core/__init__.py b/src/py/reactpy/reactpy/core/__init__.py similarity index 100% rename from src/reactpy/core/__init__.py rename to src/py/reactpy/reactpy/core/__init__.py diff --git a/src/reactpy/core/_f_back.py b/src/py/reactpy/reactpy/core/_f_back.py similarity index 100% rename from src/reactpy/core/_f_back.py rename to src/py/reactpy/reactpy/core/_f_back.py diff --git a/src/reactpy/core/_thread_local.py b/src/py/reactpy/reactpy/core/_thread_local.py similarity index 99% rename from src/reactpy/core/_thread_local.py rename to src/py/reactpy/reactpy/core/_thread_local.py index 80a42d069..b3d6a14b0 100644 --- a/src/reactpy/core/_thread_local.py +++ b/src/py/reactpy/reactpy/core/_thread_local.py @@ -2,7 +2,6 @@ from typing import Callable, Generic, TypeVar from weakref import WeakKeyDictionary - _StateType = TypeVar("_StateType") diff --git a/src/reactpy/core/component.py b/src/py/reactpy/reactpy/core/component.py similarity index 84% rename from src/reactpy/core/component.py rename to src/py/reactpy/reactpy/core/component.py index 79731ad4e..2e702a539 100644 --- a/src/reactpy/core/component.py +++ b/src/py/reactpy/reactpy/core/component.py @@ -4,7 +4,7 @@ from functools import wraps from typing import Any, Callable, Dict, Optional, Tuple -from .types import ComponentType, VdomDict +from reactpy.core.types import ComponentType, VdomDict def component( @@ -21,12 +21,13 @@ def component( inspect.Parameter.KEYWORD_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD, ): + msg = f"Component render function {function} uses reserved parameter 'key'" raise TypeError( - f"Component render function {function} uses reserved parameter 'key'" + msg ) @wraps(function) - def constructor(*args: Any, key: Optional[Any] = None, **kwargs: Any) -> Component: + def constructor(*args: Any, key: Any | None = None, **kwargs: Any) -> Component: return Component(function, key, args, kwargs, sig) return constructor @@ -40,9 +41,9 @@ class Component: def __init__( self, function: Callable[..., ComponentType | VdomDict | str | None], - key: Optional[Any], - args: Tuple[Any, ...], - kwargs: Dict[str, Any], + key: Any | None, + args: tuple[Any, ...], + kwargs: dict[str, Any], sig: inspect.Signature, ) -> None: self.key = key diff --git a/src/reactpy/core/events.py b/src/py/reactpy/reactpy/core/events.py similarity index 95% rename from src/reactpy/core/events.py rename to src/py/reactpy/reactpy/core/events.py index 4cad9fd7f..a9a63a54b 100644 --- a/src/reactpy/core/events.py +++ b/src/py/reactpy/reactpy/core/events.py @@ -104,7 +104,7 @@ def __init__( function: EventHandlerFunc, stop_propagation: bool = False, prevent_default: bool = False, - target: Optional[str] = None, + target: str | None = None, ) -> None: self.function = to_event_handler_function(function, positional_args=False) self.prevent_default = prevent_default @@ -174,7 +174,8 @@ def merge_event_handlers( :attr:`~reactpy.core.proto.EventHandlerType.prevent_default` attributes. """ if not event_handlers: - raise ValueError("No event handlers to merge") + msg = "No event handlers to merge" + raise ValueError(msg) elif len(event_handlers) == 1: return event_handlers[0] @@ -190,9 +191,9 @@ def merge_event_handlers( or handler.prevent_default != prevent_default or handler.target != target ): + msg = "Cannot merge handlers - 'stop_propagation', 'prevent_default' or 'target' mistmatch." raise ValueError( - "Cannot merge handlers - " - "'stop_propagation', 'prevent_default' or 'target' mistmatch." + msg ) return EventHandler( @@ -208,7 +209,8 @@ def merge_event_handler_funcs( ) -> EventHandlerFunc: """Make one event handler function from many""" if not functions: - raise ValueError("No event handler functions to merge") + msg = "No event handler functions to merge" + raise ValueError(msg) elif len(functions) == 1: return functions[0] diff --git a/src/reactpy/core/hooks.py b/src/py/reactpy/reactpy/core/hooks.py similarity index 99% rename from src/reactpy/core/hooks.py rename to src/py/reactpy/reactpy/core/hooks.py index c40a6869f..ba8166a70 100644 --- a/src/reactpy/core/hooks.py +++ b/src/py/reactpy/reactpy/core/hooks.py @@ -19,12 +19,10 @@ from typing_extensions import Protocol, TypeAlias from reactpy.config import REACTPY_DEBUG_MODE +from reactpy.core._thread_local import ThreadLocal +from reactpy.core.types import ComponentType, Key, State, VdomDict from reactpy.utils import Ref -from ._thread_local import ThreadLocal -from .types import ComponentType, Key, State, VdomDict - - if not TYPE_CHECKING: # make flake8 think that this variable exists ellipsis = type(...) @@ -167,7 +165,6 @@ def effect() -> None: if clean is not None: hook.add_effect(COMPONENT_WILL_UNMOUNT_EFFECT, clean) - return None return memoize(lambda: hook.add_effect(LAYOUT_DID_RENDER_EFFECT, effect)) @@ -627,7 +624,6 @@ def schedule_render(self) -> None: self._schedule_render_later = True else: self._schedule_render() - return None def use_state(self, function: Callable[[], _Type]) -> _Type: if not self._rendered_atleast_once: diff --git a/src/reactpy/core/layout.py b/src/py/reactpy/reactpy/core/layout.py similarity index 96% rename from src/reactpy/core/layout.py rename to src/py/reactpy/reactpy/core/layout.py index 2b785815e..d7b17c1da 100644 --- a/src/reactpy/core/layout.py +++ b/src/py/reactpy/reactpy/core/layout.py @@ -20,10 +20,8 @@ from weakref import ref as weakref from reactpy.config import REACTPY_CHECK_VDOM_SPEC, REACTPY_DEBUG_MODE -from reactpy.utils import Ref - -from .hooks import LifeCycleHook -from .types import ( +from reactpy.core.hooks import LifeCycleHook +from reactpy.core.types import ( ComponentType, EventHandlerDict, LayoutEventMessage, @@ -31,8 +29,8 @@ VdomDict, VdomJson, ) -from .vdom import validate_vdom_json - +from reactpy.core.vdom import validate_vdom_json +from reactpy.utils import Ref logger = getLogger(__name__) @@ -51,10 +49,11 @@ class Layout: if not hasattr(abc.ABC, "__weakref__"): # pragma: no cover __slots__.append("__weakref__") - def __init__(self, root: "ComponentType") -> None: + def __init__(self, root: ComponentType) -> None: super().__init__() if not isinstance(root, ComponentType): - raise TypeError(f"Expected a ComponentType, not {type(root)!r}.") + msg = f"Expected a ComponentType, not {type(root)!r}." + raise TypeError(msg) self.root = root async def __aenter__(self) -> Layout: @@ -82,7 +81,6 @@ async def __aexit__(self, *exc: Any) -> None: del self._root_life_cycle_state_id del self._model_states_by_life_cycle_state_id - return None async def deliver(self, event: LayoutEventMessage) -> None: """Dispatch an event to the targeted handler""" @@ -138,7 +136,7 @@ def _create_layout_update(self, old_state: _ModelState) -> LayoutUpdateMessage: def _render_component( self, exit_stack: ExitStack, - old_state: Optional[_ModelState], + old_state: _ModelState | None, new_state: _ModelState, component: ComponentType, ) -> None: @@ -195,14 +193,15 @@ def _render_component( def _render_model( self, exit_stack: ExitStack, - old_state: Optional[_ModelState], + old_state: _ModelState | None, new_state: _ModelState, raw_model: Any, ) -> None: try: new_state.model.current = {"tagName": raw_model["tagName"]} except Exception as e: # pragma: no cover - raise ValueError(f"Expected a VDOM element dict, not {raw_model}") from e + msg = f"Expected a VDOM element dict, not {raw_model}" + raise ValueError(msg) from e if "key" in raw_model: new_state.key = new_state.model.current["key"] = raw_model["key"] if "importSource" in raw_model: @@ -214,7 +213,7 @@ def _render_model( def _render_model_attributes( self, - old_state: Optional[_ModelState], + old_state: _ModelState | None, new_state: _ModelState, raw_model: dict[str, Any], ) -> None: @@ -278,7 +277,7 @@ def _render_model_event_handlers_without_old_state( def _render_model_children( self, exit_stack: ExitStack, - old_state: Optional[_ModelState], + old_state: _ModelState | None, new_state: _ModelState, raw_children: Any, ) -> None: @@ -301,8 +300,9 @@ def _render_model_children( if len(new_keys) != len(raw_children): key_counter = Counter(item[2] for item in child_type_key_tuples) duplicate_keys = [key for key, count in key_counter.items() if count > 1] + msg = f"Duplicate keys {duplicate_keys} at {new_state.patch_path or '/'!r}" raise ValueError( - f"Duplicate keys {duplicate_keys} at {new_state.patch_path or '/'!r}" + msg ) old_keys = set(old_state.children_by_key).difference(new_keys) @@ -392,8 +392,9 @@ def _render_model_children_without_old_state( if len(new_keys) != len(raw_children): key_counter = Counter(item[2] for item in child_type_key_tuples) duplicate_keys = [key for key, count in key_counter.items() if count > 1] + msg = f"Duplicate keys {duplicate_keys} at {new_state.patch_path or '/'!r}" raise ValueError( - f"Duplicate keys {duplicate_keys} at {new_state.patch_path or '/'!r}" + msg ) new_state.model.current["children"] = [] @@ -467,7 +468,7 @@ def _make_component_model_state( def _copy_component_model_state(old_model_state: _ModelState) -> _ModelState: # use try/except here because not having a parent is rare (only the root state) try: - parent: Optional[_ModelState] = old_model_state.parent + parent: _ModelState | None = old_model_state.parent except AttributeError: parent = None @@ -555,14 +556,14 @@ class _ModelState: def __init__( self, - parent: Optional[_ModelState], + parent: _ModelState | None, index: int, key: Any, model: Ref[VdomJson], patch_path: str, children_by_key: dict[str, _ModelState], targets_by_event: dict[str, str], - life_cycle_state: Optional[_LifeCycleState] = None, + life_cycle_state: _LifeCycleState | None = None, ): self.index = index """The index of the element amongst its siblings""" @@ -665,7 +666,6 @@ def put(self, value: _Type) -> None: if value not in self._pending: self._pending.add(value) self._loop.call_soon_threadsafe(self._queue.put_nowait, value) - return None async def get(self) -> _Type: while True: diff --git a/src/reactpy/core/serve.py b/src/py/reactpy/reactpy/core/serve.py similarity index 99% rename from src/reactpy/core/serve.py rename to src/py/reactpy/reactpy/core/serve.py index 2802df9e2..3c2cea048 100644 --- a/src/reactpy/core/serve.py +++ b/src/py/reactpy/reactpy/core/serve.py @@ -8,7 +8,6 @@ from reactpy.core.types import LayoutEventMessage, LayoutType, LayoutUpdateMessage - logger = getLogger(__name__) diff --git a/src/reactpy/core/types.py b/src/py/reactpy/reactpy/core/types.py similarity index 93% rename from src/reactpy/core/types.py rename to src/py/reactpy/reactpy/core/types.py index e9ec39c1a..957057d04 100644 --- a/src/reactpy/core/types.py +++ b/src/py/reactpy/reactpy/core/types.py @@ -18,7 +18,6 @@ from typing_extensions import Literal, Protocol, TypeAlias, TypedDict, runtime_checkable - _Type = TypeVar("_Type") @@ -81,7 +80,7 @@ async def __aenter__(self) -> LayoutType[_Render, _Event]: async def __aexit__( self, - exc_type: Type[Exception], + exc_type: type[Exception], exc_value: Exception, traceback: TracebackType, ) -> bool | None: @@ -109,12 +108,12 @@ class _VdomDictOptional(TypedDict, total=False): | Any ] attributes: VdomAttributes - eventHandlers: EventHandlerDict # noqa - importSource: ImportSourceDict # noqa + eventHandlers: EventHandlerDict + importSource: ImportSourceDict class _VdomDictRequired(TypedDict, total=True): - tagName: str # noqa + tagName: str class VdomDict(_VdomDictRequired, _VdomDictOptional): @@ -124,8 +123,8 @@ class VdomDict(_VdomDictRequired, _VdomDictOptional): class ImportSourceDict(TypedDict): source: str fallback: Any - sourceType: str # noqa - unmountBeforeUpdate: bool # noqa + sourceType: str + unmountBeforeUpdate: bool class _OptionalVdomJson(TypedDict, total=False): @@ -133,12 +132,12 @@ class _OptionalVdomJson(TypedDict, total=False): error: str children: list[Any] attributes: dict[str, Any] - eventHandlers: dict[str, _JsonEventTarget] # noqa - importSource: _JsonImportSource # noqa + eventHandlers: dict[str, _JsonEventTarget] + importSource: _JsonImportSource class _RequiredVdomJson(TypedDict, total=True): - tagName: str # noqa + tagName: str class VdomJson(_RequiredVdomJson, _OptionalVdomJson): @@ -147,8 +146,8 @@ class VdomJson(_RequiredVdomJson, _OptionalVdomJson): class _JsonEventTarget(TypedDict): target: str - preventDefault: bool # noqa - stopPropagation: bool # noqa + preventDefault: bool + stopPropagation: bool class _JsonImportSource(TypedDict): diff --git a/src/reactpy/core/vdom.py b/src/py/reactpy/reactpy/core/vdom.py similarity index 97% rename from src/reactpy/core/vdom.py rename to src/py/reactpy/reactpy/core/vdom.py index 9579560b9..8441bff4e 100644 --- a/src/reactpy/core/vdom.py +++ b/src/py/reactpy/reactpy/core/vdom.py @@ -9,6 +9,7 @@ from reactpy._warnings import warn from reactpy.config import REACTPY_DEBUG_MODE +from reactpy.core._f_back import f_module_name from reactpy.core.events import EventHandler, to_event_handler_function from reactpy.core.types import ( ComponentType, @@ -24,9 +25,6 @@ VdomJson, ) -from ._f_back import f_module_name - - logger = logging.getLogger() @@ -188,7 +186,8 @@ def vdom( ) if kwargs: - raise ValueError(f"Extra keyword arguments {kwargs}") + msg = f"Extra keyword arguments {kwargs}" + raise ValueError(msg) model: VdomDict = {"tagName": tag} @@ -226,7 +225,8 @@ def make_vdom_constructor( def constructor(*attributes_and_children: Any, **kwargs: Any) -> VdomDict: model = vdom(tag, *attributes_and_children, **kwargs) if not allow_children and "children" in model: - raise TypeError(f"{tag!r} nodes cannot have children.") + msg = f"{tag!r} nodes cannot have children." + raise TypeError(msg) if import_source: model["importSource"] = import_source return model @@ -308,7 +308,7 @@ def separate_attributes_and_event_handlers( separated_event_handlers[k] = handler - return separated_attributes, {k: h for k, h in separated_event_handlers.items()} + return separated_attributes, dict(separated_event_handlers.items()) def _is_attributes(value: Any) -> bool: diff --git a/src/reactpy/future.py b/src/py/reactpy/reactpy/future.py similarity index 100% rename from src/reactpy/future.py rename to src/py/reactpy/reactpy/future.py diff --git a/src/reactpy/html.py b/src/py/reactpy/reactpy/html.py similarity index 96% rename from src/reactpy/html.py rename to src/py/reactpy/reactpy/html.py index 5c1d8b6ad..1efca8a7b 100644 --- a/src/reactpy/html.py +++ b/src/py/reactpy/reactpy/html.py @@ -169,7 +169,6 @@ ) from reactpy.core.vdom import custom_vdom_constructor, make_vdom_constructor - __all__ = ( "_", "a", @@ -293,7 +292,8 @@ def _fragment( ) -> VdomDict: """An HTML fragment - this element will not appear in the DOM""" if attributes or event_handlers: - raise TypeError("Fragments cannot have attributes besides 'key'") + msg = "Fragments cannot have attributes besides 'key'" + raise TypeError(msg) model: VdomDict = {"tagName": ""} if children: @@ -473,13 +473,16 @@ def _script( model: VdomDict = {"tagName": "script"} if event_handlers: - raise ValueError("'script' elements do not support event handlers") + msg = "'script' elements do not support event handlers" + raise ValueError(msg) if children: if len(children) > 1: - raise ValueError("'script' nodes may have, at most, one child.") + msg = "'script' nodes may have, at most, one child." + raise ValueError(msg) elif not isinstance(children[0], str): - raise ValueError("The child of a 'script' must be a string.") + msg = "The child of a 'script' must be a string." + raise ValueError(msg) else: model["children"] = children if key is None: diff --git a/src/reactpy/logging.py b/src/py/reactpy/reactpy/logging.py similarity index 95% rename from src/reactpy/logging.py rename to src/py/reactpy/reactpy/logging.py index 1a07984a1..f10414cb6 100644 --- a/src/reactpy/logging.py +++ b/src/py/reactpy/reactpy/logging.py @@ -2,8 +2,7 @@ import sys from logging.config import dictConfig -from .config import REACTPY_DEBUG_MODE - +from reactpy.config import REACTPY_DEBUG_MODE dictConfig( { diff --git a/src/reactpy/py.typed b/src/py/reactpy/reactpy/py.typed similarity index 100% rename from src/reactpy/py.typed rename to src/py/reactpy/reactpy/py.typed diff --git a/src/reactpy/sample.py b/src/py/reactpy/reactpy/sample.py similarity index 81% rename from src/reactpy/sample.py rename to src/py/reactpy/reactpy/sample.py index 7905c1586..8509c773d 100644 --- a/src/reactpy/sample.py +++ b/src/py/reactpy/reactpy/sample.py @@ -1,8 +1,8 @@ from __future__ import annotations -from . import html -from .core.component import component -from .core.types import VdomDict +from reactpy import html +from reactpy.core.component import component +from reactpy.core.types import VdomDict @component diff --git a/src/reactpy/svg.py b/src/py/reactpy/reactpy/svg.py similarity index 99% rename from src/reactpy/svg.py rename to src/py/reactpy/reactpy/svg.py index ef9995468..cf3e75577 100644 --- a/src/reactpy/svg.py +++ b/src/py/reactpy/reactpy/svg.py @@ -1,6 +1,5 @@ from reactpy.core.vdom import make_vdom_constructor - __all__ = ( "a", "animate", diff --git a/src/reactpy/testing/__init__.py b/src/py/reactpy/reactpy/testing/__init__.py similarity index 59% rename from src/reactpy/testing/__init__.py rename to src/py/reactpy/reactpy/testing/__init__.py index 1c9fcda1c..9f61cec57 100644 --- a/src/reactpy/testing/__init__.py +++ b/src/py/reactpy/reactpy/testing/__init__.py @@ -1,14 +1,18 @@ -from .backend import BackendFixture -from .common import HookCatcher, StaticEventHandler, clear_reactpy_web_modules_dir, poll -from .display import DisplayFixture -from .logs import ( +from reactpy.testing.backend import BackendFixture +from reactpy.testing.common import ( + HookCatcher, + StaticEventHandler, + clear_reactpy_web_modules_dir, + poll, +) +from reactpy.testing.display import DisplayFixture +from reactpy.testing.logs import ( LogAssertionError, assert_reactpy_did_log, assert_reactpy_did_not_log, capture_reactpy_logs, ) - __all__ = [ "assert_reactpy_did_not_log", "assert_reactpy_did_log", diff --git a/src/reactpy/testing/backend.py b/src/py/reactpy/reactpy/testing/backend.py similarity index 90% rename from src/reactpy/testing/backend.py rename to src/py/reactpy/reactpy/testing/backend.py index 5749e1fef..23d908291 100644 --- a/src/reactpy/testing/backend.py +++ b/src/py/reactpy/reactpy/testing/backend.py @@ -14,10 +14,13 @@ from reactpy.core.component import component from reactpy.core.hooks import use_callback, use_effect, use_state from reactpy.core.types import ComponentConstructor +from reactpy.testing.logs import ( + LogAssertionError, + capture_reactpy_logs, + list_logged_exceptions, +) from reactpy.utils import Ref -from .logs import LogAssertionError, capture_reactpy_logs, list_logged_exceptions - class BackendFixture: """A test fixture for running a server and imperatively displaying views @@ -38,7 +41,7 @@ class BackendFixture: def __init__( self, host: str = "127.0.0.1", - port: Optional[int] = None, + port: int | None = None, app: Any | None = None, implementation: BackendImplementation[Any] | None = None, options: Any | None = None, @@ -53,9 +56,9 @@ def __init__( if app is not None: if implementation is None: + msg = "If an application instance its corresponding server implementation must be provided too." raise ValueError( - "If an application instance its corresponding " - "server implementation must be provided too." + msg ) self._app = app @@ -67,7 +70,7 @@ def log_records(self) -> list[logging.LogRecord]: """A list of captured log records""" return self._records - def url(self, path: str = "", query: Optional[Any] = None) -> str: + def url(self, path: str = "", query: Any | None = None) -> str: """Return a URL string pointing to the host and point of the server Args: @@ -88,7 +91,7 @@ def url(self, path: str = "", query: Optional[Any] = None) -> str: def list_logged_exceptions( self, pattern: str = "", - types: Union[Type[Any], Tuple[Type[Any], ...]] = Exception, + types: type[Any] | tuple[type[Any], ...] = Exception, log_level: int = logging.ERROR, del_log_records: bool = True, ) -> list[BaseException]: @@ -141,9 +144,9 @@ async def stop_server() -> None: async def __aexit__( self, - exc_type: Optional[Type[BaseException]], - exc_value: Optional[BaseException], - traceback: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, ) -> None: await self._exit_stack.aclose() @@ -151,15 +154,15 @@ async def __aexit__( logged_errors = self.list_logged_exceptions(del_log_records=False) if logged_errors: # pragma: no cover - raise LogAssertionError("Unexpected logged exception") from logged_errors[0] + msg = "Unexpected logged exception" + raise LogAssertionError(msg) from logged_errors[0] - return None _MountFunc = Callable[["Callable[[], Any] | None"], None] -def _hotswap(update_on_change: bool = False) -> Tuple[_MountFunc, ComponentConstructor]: +def _hotswap(update_on_change: bool = False) -> tuple[_MountFunc, ComponentConstructor]: """Swap out components from a layout on the fly. Since you can't change the component functions used to create a layout @@ -218,7 +221,6 @@ def swap(constructor: Callable[[], Any] | None) -> None: for set_constructor in set_constructor_callbacks: set_constructor(constructor) - return None else: @@ -228,6 +230,5 @@ def HotSwap() -> Any: def swap(constructor: Callable[[], Any] | None) -> None: constructor_ref.current = constructor or (lambda: None) - return None return swap, HotSwap diff --git a/src/reactpy/testing/common.py b/src/py/reactpy/reactpy/testing/common.py similarity index 97% rename from src/reactpy/testing/common.py rename to src/py/reactpy/reactpy/testing/common.py index 61b4a9a0e..dff7a90c3 100644 --- a/src/reactpy/testing/common.py +++ b/src/py/reactpy/reactpy/testing/common.py @@ -66,9 +66,9 @@ async def until( if condition(result): break elif (time.time() - started_at) > timeout: # pragma: no cover + msg = f"Expected {description} after {timeout} seconds - last value was {result!r}" raise TimeoutError( - f"Expected {description} after {timeout} " - f"seconds - last value was {result!r}" + msg ) async def until_is( @@ -126,7 +126,7 @@ def MyComponent(thing): latest: LifeCycleHook - def __init__(self, index_by_kwarg: Optional[str] = None): + def __init__(self, index_by_kwarg: str | None = None): self.index_by_kwarg = index_by_kwarg self.index: dict[Any, LifeCycleHook] = {} diff --git a/src/reactpy/testing/display.py b/src/py/reactpy/reactpy/testing/display.py similarity index 94% rename from src/reactpy/testing/display.py rename to src/py/reactpy/reactpy/testing/display.py index e97fb5927..1afb79940 100644 --- a/src/reactpy/testing/display.py +++ b/src/py/reactpy/reactpy/testing/display.py @@ -13,10 +13,9 @@ ) from reactpy.config import REACTPY_TESTING_DEFAULT_TIMEOUT +from reactpy.testing.backend import BackendFixture from reactpy.types import RootComponentConstructor -from .backend import BackendFixture - class DisplayFixture: """A fixture for running web-based tests using ``playwright``""" @@ -58,7 +57,8 @@ async def goto( async def root_element(self) -> ElementHandle: element = await self.page.wait_for_selector("#app", state="attached") if element is None: - raise RuntimeError("Root element not attached") # pragma: no cover + msg = "Root element not attached" + raise RuntimeError(msg) # pragma: no cover return element async def __aenter__(self) -> DisplayFixture: diff --git a/src/reactpy/testing/logs.py b/src/py/reactpy/reactpy/testing/logs.py similarity index 100% rename from src/reactpy/testing/logs.py rename to src/py/reactpy/reactpy/testing/logs.py diff --git a/src/reactpy/types.py b/src/py/reactpy/reactpy/types.py similarity index 81% rename from src/reactpy/types.py rename to src/py/reactpy/reactpy/types.py index b8ed1ca2a..715b66fff 100644 --- a/src/reactpy/types.py +++ b/src/py/reactpy/reactpy/types.py @@ -4,10 +4,10 @@ - :mod:`reactpy.backend.types` """ -from .backend.types import BackendImplementation, Connection, Location -from .core.component import Component -from .core.hooks import Context -from .core.types import ( +from reactpy.backend.types import BackendImplementation, Connection, Location +from reactpy.core.component import Component +from reactpy.core.hooks import Context +from reactpy.core.types import ( ComponentConstructor, ComponentType, EventHandlerDict, @@ -26,7 +26,6 @@ VdomJson, ) - __all__ = [ "BackendImplementation", "Component", diff --git a/src/reactpy/utils.py b/src/py/reactpy/reactpy/utils.py similarity index 94% rename from src/reactpy/utils.py rename to src/py/reactpy/reactpy/utils.py index e42097aa0..5f0cac906 100644 --- a/src/reactpy/utils.py +++ b/src/py/reactpy/reactpy/utils.py @@ -10,7 +10,6 @@ from reactpy.core.types import VdomDict from reactpy.core.vdom import vdom - _RefValue = TypeVar("_RefValue") _ModelTransform = Callable[[VdomDict], Any] _UNDEFINED: Any = object() @@ -96,7 +95,8 @@ def html_to_vdom( syntax. """ if not isinstance(html, str): # pragma: no cover - raise TypeError(f"Expected html to be a string, not {type(html).__name__}") + msg = f"Expected html to be a string, not {type(html).__name__}" + raise TypeError(msg) # If the user provided a string, convert it to a list of lxml.etree nodes try: @@ -112,12 +112,9 @@ def html_to_vdom( except etree.XMLSyntaxError as e: if not strict: raise e # pragma: no cover + msg = "An error has occurred while parsing the HTML.\n\nThis HTML may be malformatted, or may not perfectly adhere to HTML5.\nIf you believe the exception above was due to something intentional, you can disable the strict parameter on html_to_vdom().\nOtherwise, repair your broken HTML and try again." raise HTMLParseError( - "An error has occurred while parsing the HTML.\n\n" - "This HTML may be malformatted, or may not perfectly adhere to HTML5.\n" - "If you believe the exception above was due to something intentional, " - "you can disable the strict parameter on html_to_vdom().\n" - "Otherwise, repair your broken HTML and try again." + msg ) from e return _etree_to_vdom(root_node, transforms) @@ -141,8 +138,9 @@ def _etree_to_vdom( transform function to add highlighting to a ```` block. """ if not isinstance(node, etree._Element): # pragma: no cover + msg = f"Expected node to be a etree._Element, not {type(node).__name__}" raise TypeError( - f"Expected node to be a etree._Element, not {type(node).__name__}" + msg ) # Recursively call _etree_to_vdom() on all children @@ -165,7 +163,8 @@ def _add_vdom_to_etree(parent: etree._Element, vdom: VdomDict | dict[str, Any]) try: tag = vdom["tagName"] except KeyError as e: - raise TypeError(f"Expected a VDOM dict, not {vdom}") from e + msg = f"Expected a VDOM dict, not {vdom}" + raise TypeError(msg) from e else: vdom = cast(VdomDict, vdom) diff --git a/src/reactpy/web/__init__.py b/src/py/reactpy/reactpy/web/__init__.py similarity index 87% rename from src/reactpy/web/__init__.py rename to src/py/reactpy/reactpy/web/__init__.py index 6fe239ed9..308429dbb 100644 --- a/src/reactpy/web/__init__.py +++ b/src/py/reactpy/reactpy/web/__init__.py @@ -1,4 +1,4 @@ -from .module import ( +from reactpy.web.module import ( export, module_from_file, module_from_string, @@ -6,7 +6,6 @@ module_from_url, ) - __all__ = [ "module_from_file", "module_from_string", diff --git a/src/reactpy/web/module.py b/src/py/reactpy/reactpy/web/module.py similarity index 95% rename from src/reactpy/web/module.py rename to src/py/reactpy/reactpy/web/module.py index efdd2395f..5e615a981 100644 --- a/src/reactpy/web/module.py +++ b/src/py/reactpy/reactpy/web/module.py @@ -13,14 +13,12 @@ from reactpy.config import REACTPY_DEBUG_MODE, REACTPY_WEB_MODULES_DIR from reactpy.core.types import ImportSourceDict, VdomDictConstructor from reactpy.core.vdom import make_vdom_constructor - -from .utils import ( +from reactpy.web.utils import ( module_name_suffix, resolve_module_exports_from_file, resolve_module_exports_from_url, ) - logger = logging.getLogger(__name__) SourceType = NewType("SourceType", str) @@ -143,7 +141,8 @@ def module_from_template( template_file = Path(__file__).parent / "templates" / template_file_name if not template_file.exists(): - raise ValueError(f"No template for {template_file_name!r} exists") + msg = f"No template for {template_file_name!r} exists" + raise ValueError(msg) variables = {"PACKAGE": package, "CDN": cdn, "VERSION": template_version} content = Template(template_file.read_text()).substitute(variables) @@ -193,7 +192,8 @@ def module_from_file( source_file = Path(file).resolve() target_file = _web_module_path(name) if not source_file.exists(): - raise FileNotFoundError(f"Source file does not exist: {source_file}") + msg = f"Source file does not exist: {source_file}" + raise FileNotFoundError(msg) if not target_file.exists(): _copy_file(target_file, source_file, symlink) @@ -351,15 +351,15 @@ def export( web_module.export_names is not None and export_names not in web_module.export_names ): - raise ValueError(f"{web_module.source!r} does not export {export_names!r}") + msg = f"{web_module.source!r} does not export {export_names!r}" + raise ValueError(msg) return _make_export(web_module, export_names, fallback, allow_children) else: if web_module.export_names is not None: - missing = list( - sorted(set(export_names).difference(web_module.export_names)) - ) + missing = sorted(set(export_names).difference(web_module.export_names)) if missing: - raise ValueError(f"{web_module.source!r} does not export {missing!r}") + msg = f"{web_module.source!r} does not export {missing!r}" + raise ValueError(msg) return [ _make_export(web_module, name, fallback, allow_children) for name in export_names diff --git a/src/reactpy/web/templates/react.js b/src/py/reactpy/reactpy/web/templates/react.js similarity index 99% rename from src/reactpy/web/templates/react.js rename to src/py/reactpy/reactpy/web/templates/react.js index 00924f85a..5c6a45743 100644 --- a/src/reactpy/web/templates/react.js +++ b/src/py/reactpy/reactpy/web/templates/react.js @@ -54,7 +54,7 @@ function makeJsonSafeEventHandler(oldHandler) { } return true; } - }) + }), ); }; } diff --git a/src/reactpy/web/utils.py b/src/py/reactpy/reactpy/web/utils.py similarity index 99% rename from src/reactpy/web/utils.py rename to src/py/reactpy/reactpy/web/utils.py index a1f812187..4bfd21351 100644 --- a/src/reactpy/web/utils.py +++ b/src/py/reactpy/reactpy/web/utils.py @@ -6,7 +6,6 @@ import requests - logger = logging.getLogger(__name__) diff --git a/src/reactpy/widgets.py b/src/py/reactpy/reactpy/widgets.py similarity index 89% rename from src/reactpy/widgets.py rename to src/py/reactpy/reactpy/widgets.py index 584c3b562..593f9de00 100644 --- a/src/reactpy/widgets.py +++ b/src/py/reactpy/reactpy/widgets.py @@ -6,15 +6,14 @@ from typing_extensions import Protocol import reactpy - -from . import html -from ._warnings import warn -from .core.types import ComponentConstructor, VdomDict +from reactpy import html +from reactpy._warnings import warn +from reactpy.core.types import ComponentConstructor, VdomDict def image( format: str, - value: Union[str, bytes] = "", + value: str | bytes = "", attributes: dict[str, Any] | None = None, ) -> VdomDict: """Utility for constructing an image from a string or bytes @@ -89,17 +88,17 @@ def __call__(self, value: str) -> _CastTo: if TYPE_CHECKING: - from .testing.backend import _MountFunc + from reactpy.testing.backend import _MountFunc def hotswap( update_on_change: bool = False, -) -> Tuple[_MountFunc, ComponentConstructor]: # pragma: no cover +) -> tuple[_MountFunc, ComponentConstructor]: # pragma: no cover warn( "The 'hotswap' function is deprecated and will be removed in a future release", DeprecationWarning, stacklevel=2, ) - from .testing.backend import _hotswap + from reactpy.testing.backend import _hotswap return _hotswap(update_on_change) diff --git a/setup.py b/src/py/reactpy/setup.py similarity index 95% rename from setup.py rename to src/py/reactpy/setup.py index 5f256008f..6531fe85e 100644 --- a/setup.py +++ b/src/py/reactpy/setup.py @@ -1,4 +1,3 @@ -from __future__ import print_function import pipes import shutil @@ -12,7 +11,6 @@ from setuptools.command.develop import develop from setuptools.command.sdist import sdist - if sys.platform == "win32": from subprocess import list2cmdline else: @@ -119,7 +117,8 @@ def list2cmdline(cmd_list): 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'") + msg = "%r uses the reserved extra name 'all'" + raise ValueError(msg) for e in _current_extras: extra_requirements[e] = [] elif _current_extras: @@ -127,8 +126,9 @@ def list2cmdline(cmd_list): extra_requirements[e].append(line) extra_requirements["all"].append(line) elif line: + msg = f"No '# extra=' header before requirements in {extra_requirements_path}" raise ValueError( - f"No '# extra=' header before requirements in {extra_requirements_path}" + msg ) package["extras_require"] = extra_requirements @@ -163,7 +163,8 @@ def run(self): try: npm = shutil.which("npm") # this is required on windows if npm is None: - raise RuntimeError("NPM is not installed.") + msg = "NPM is not installed." + raise RuntimeError(msg) for args in (f"{npm} ci", f"{npm} run build"): args_list = args.split() log.info(f"> {list2cmdline(args_list)}") diff --git a/tests/__init__.py b/src/py/reactpy/tests/__init__.py similarity index 100% rename from tests/__init__.py rename to src/py/reactpy/tests/__init__.py diff --git a/tests/conftest.py b/src/py/reactpy/tests/conftest.py similarity index 100% rename from tests/conftest.py rename to src/py/reactpy/tests/conftest.py diff --git a/tests/test__console/__init__.py b/src/py/reactpy/tests/test__console/__init__.py similarity index 100% rename from tests/test__console/__init__.py rename to src/py/reactpy/tests/test__console/__init__.py diff --git a/tests/test__console/test_rewrite_camel_case_props.py b/src/py/reactpy/tests/test__console/test_rewrite_camel_case_props.py similarity index 99% rename from tests/test__console/test_rewrite_camel_case_props.py rename to src/py/reactpy/tests/test__console/test_rewrite_camel_case_props.py index d8746f8c7..47b8baabc 100644 --- a/tests/test__console/test_rewrite_camel_case_props.py +++ b/src/py/reactpy/tests/test__console/test_rewrite_camel_case_props.py @@ -10,7 +10,6 @@ rewrite_camel_case_props, ) - if sys.version_info < (3, 9): pytestmark = pytest.mark.skip(reason="ast.unparse is Python>=3.9") diff --git a/tests/test__console/test_rewrite_keys.py b/src/py/reactpy/tests/test__console/test_rewrite_keys.py similarity index 99% rename from tests/test__console/test_rewrite_keys.py rename to src/py/reactpy/tests/test__console/test_rewrite_keys.py index 6fad036e7..da0b26c4f 100644 --- a/tests/test__console/test_rewrite_keys.py +++ b/src/py/reactpy/tests/test__console/test_rewrite_keys.py @@ -7,7 +7,6 @@ from reactpy._console.rewrite_keys import generate_rewrite, rewrite_keys - if sys.version_info < (3, 9): pytestmark = pytest.mark.skip(reason="ast.unparse is Python>=3.9") diff --git a/tests/test__option.py b/src/py/reactpy/tests/test__option.py similarity index 100% rename from tests/test__option.py rename to src/py/reactpy/tests/test__option.py diff --git a/tests/test_backend/__init__.py b/src/py/reactpy/tests/test_backend/__init__.py similarity index 100% rename from tests/test_backend/__init__.py rename to src/py/reactpy/tests/test_backend/__init__.py diff --git a/tests/test_backend/test__common.py b/src/py/reactpy/tests/test_backend/test__common.py similarity index 100% rename from tests/test_backend/test__common.py rename to src/py/reactpy/tests/test_backend/test__common.py diff --git a/tests/test_backend/test_all.py b/src/py/reactpy/tests/test_backend/test_all.py similarity index 98% rename from tests/test_backend/test_all.py rename to src/py/reactpy/tests/test_backend/test_all.py index fcf6b7286..b9ef10c41 100644 --- a/tests/test_backend/test_all.py +++ b/src/py/reactpy/tests/test_backend/test_all.py @@ -12,7 +12,7 @@ @pytest.fixture( - params=list(all_implementations()) + [default_implementation], + params=[*list(all_implementations()), default_implementation], ids=lambda imp: imp.__name__, scope="module", ) diff --git a/tests/test_backend/test_utils.py b/src/py/reactpy/tests/test_backend/test_utils.py similarity index 95% rename from tests/test_backend/test_utils.py rename to src/py/reactpy/tests/test_backend/test_utils.py index 2b36966f5..2a58dc62a 100644 --- a/tests/test_backend/test_utils.py +++ b/src/py/reactpy/tests/test_backend/test_utils.py @@ -8,7 +8,7 @@ from reactpy.backend import flask as flask_implementation from reactpy.backend.utils import find_available_port from reactpy.backend.utils import run as sync_run -from reactpy.sample import SampleApp as SampleApp +from reactpy.sample import SampleApp @pytest.fixture diff --git a/tests/test_client.py b/src/py/reactpy/tests/test_client.py similarity index 98% rename from tests/test_client.py rename to src/py/reactpy/tests/test_client.py index ea6a3a672..5a51f8b16 100644 --- a/tests/test_client.py +++ b/src/py/reactpy/tests/test_client.py @@ -10,7 +10,6 @@ from tests.tooling.common import DEFAULT_TYPE_DELAY from tests.tooling.hooks import use_counter - JS_DIR = Path(__file__).parent / "js" @@ -27,7 +26,7 @@ def SomeComponent(): return reactpy.html._( reactpy.html.p({"data_count": count, "id": "count"}, "count", count), reactpy.html.button( - dict(on_click=lambda e: incr_count(), id="incr"), "incr" + {"on_click": lambda e: incr_count(), "id": "incr"}, "incr" ), ) diff --git a/tests/test_config.py b/src/py/reactpy/tests/test_config.py similarity index 100% rename from tests/test_config.py rename to src/py/reactpy/tests/test_config.py diff --git a/tests/test_core/__init__.py b/src/py/reactpy/tests/test_core/__init__.py similarity index 100% rename from tests/test_core/__init__.py rename to src/py/reactpy/tests/test_core/__init__.py diff --git a/tests/test_core/test_component.py b/src/py/reactpy/tests/test_core/test_component.py similarity index 100% rename from tests/test_core/test_component.py rename to src/py/reactpy/tests/test_core/test_component.py diff --git a/tests/test_core/test_events.py b/src/py/reactpy/tests/test_core/test_events.py similarity index 99% rename from tests/test_core/test_events.py rename to src/py/reactpy/tests/test_core/test_events.py index f6ab2bdc1..67c7c2ba9 100644 --- a/tests/test_core/test_events.py +++ b/src/py/reactpy/tests/test_core/test_events.py @@ -193,7 +193,7 @@ def inner_click_no_op(event): clicked.current = True def outer_click_is_not_triggered(event): - assert False + raise AssertionError() outer = reactpy.html.div( { diff --git a/tests/test_core/test_hooks.py b/src/py/reactpy/tests/test_core/test_hooks.py similarity index 98% rename from tests/test_core/test_hooks.py rename to src/py/reactpy/tests/test_core/test_hooks.py index 570319677..52e7c68df 100644 --- a/tests/test_core/test_hooks.py +++ b/src/py/reactpy/tests/test_core/test_hooks.py @@ -557,7 +557,8 @@ async def test_error_in_effect_is_gracefully_handled(caplog): def ComponentWithEffect(): @reactpy.hooks.use_effect def bad_effect(): - raise ValueError("Something went wong :(") + msg = "Something went wong :(" + raise ValueError(msg) return reactpy.html.div() @@ -576,7 +577,8 @@ def ComponentWithEffect(): @reactpy.hooks.use_effect(dependencies=None) # force this to run every time def ok_effect(): def bad_cleanup(): - raise ValueError("Something went wong :(") + msg = "Something went wong :(" + raise ValueError(msg) return bad_cleanup @@ -602,7 +604,8 @@ def ComponentWithEffect(): @reactpy.hooks.use_effect def ok_effect(): def bad_cleanup(): - raise ValueError("Something went wong :(") + msg = "Something went wong :(" + raise ValueError(msg) return bad_cleanup @@ -628,7 +631,8 @@ def reducer(count, action): elif action == "decrement": return count - 1 else: - raise ValueError(f"Unknown action '{action}'") + msg = f"Unknown action '{action}'" + raise ValueError(msg) @reactpy.component def Counter(initial_count): @@ -660,7 +664,8 @@ def reducer(count, action): if action == "increment": return count + 1 else: - raise ValueError(f"Unknown action '{action}'") + msg = f"Unknown action '{action}'" + raise ValueError(msg) @reactpy.component def ComponentWithUseReduce(): @@ -792,7 +797,7 @@ async def test_use_memo_with_stored_deps_is_empty_tuple_after_deps_are_none(): def ComponentWithMemo(): value = reactpy.hooks.use_memo( lambda: next(iter_values), - deps_used_in_memo.current, # noqa: ROH202 + deps_used_in_memo.current, ) used_values.append(value) return reactpy.html.div() @@ -853,7 +858,8 @@ def ComponentWithRef(): def test_bad_schedule_render_callback(): def bad_callback(): - raise ValueError("something went wrong") + msg = "something went wrong" + raise ValueError(msg) with assert_reactpy_did_log( match_message=f"Failed to schedule render via {bad_callback}" @@ -1020,7 +1026,8 @@ def ComponentWithEffect(): hook = current_hook() def bad_effect(): - raise ValueError("The error message") + msg = "The error message" + raise ValueError(msg) hook.add_effect(COMPONENT_DID_RENDER_EFFECT, bad_effect) return reactpy.html.div() @@ -1244,7 +1251,6 @@ async def test_use_state_named_tuple(): @reactpy.component def some_component(): state.current = reactpy.use_state(1) - return None async with reactpy.Layout(some_component()) as layout: await layout.render() diff --git a/tests/test_core/test_layout.py b/src/py/reactpy/tests/test_core/test_layout.py similarity index 98% rename from tests/test_core/test_layout.py rename to src/py/reactpy/tests/test_core/test_layout.py index d46913314..0510e1e3c 100644 --- a/tests/test_core/test_layout.py +++ b/src/py/reactpy/tests/test_core/test_layout.py @@ -171,7 +171,8 @@ def OkChild(): @reactpy.component def BadChild(): - raise ValueError("error from bad child") + msg = "error from bad child" + raise ValueError(msg) with assert_reactpy_did_log(match_error="error from bad child"): async with reactpy.Layout(Main()) as layout: @@ -221,7 +222,8 @@ def OkChild(): @reactpy.component def BadChild(): - raise ValueError("error from bad child") + msg = "error from bad child" + raise ValueError(msg) with assert_reactpy_did_log(match_error="error from bad child"): async with reactpy.Layout(Main()) as layout: @@ -504,7 +506,8 @@ def good_trigger(): @bad_handler.use def bad_trigger(): - raise ValueError("Called bad trigger") + msg = "Called bad trigger" + raise ValueError(msg) children = [ reactpy.html.button( @@ -522,7 +525,7 @@ def bad_trigger(): async with reactpy.Layout(MyComponent()) as layout: await layout.render() - for i in range(3): + for _i in range(3): event = event_message(good_handler.target) await layout.deliver(event) @@ -567,7 +570,8 @@ def callback(): @bad_handler.use def callback(): - raise ValueError("Called bad trigger") + msg = "Called bad trigger" + raise ValueError(msg) return reactpy.html.button({"on_click": callback, "id": "good"}, "good") @@ -658,7 +662,7 @@ def HasEventHandlerAtRoot(): async with reactpy.Layout(HasEventHandlerAtRoot()) as layout: await layout.render() - for i in range(3): + for _i in range(3): last_event_handler = event_handler.current # after this render we should have release the reference to the last handler await layout.render() @@ -680,7 +684,7 @@ def HasNestedEventHandler(): async with reactpy.Layout(HasNestedEventHandler()) as layout: await layout.render() - for i in range(3): + for _i in range(3): last_event_handler = event_handler.current # after this render we should have release the reference to the last handler await layout.render() @@ -753,7 +757,8 @@ async def test_log_error_on_bad_event_handler(): def ComponentWithBadEventHandler(): @bad_handler.use def raise_error(): - raise Exception("bad event handler") + msg = "bad event handler" + raise Exception(msg) return reactpy.html.button({"on_click": raise_error}) @@ -893,7 +898,7 @@ def HasState(): async with reactpy.Layout(Root()) as layout: await layout.render() - for i in range(5): + for _i in range(5): last_state = state.current root_hook.latest.schedule_render() await layout.render() @@ -1027,7 +1032,7 @@ async def record_if_state_is_reset(): assert effect_calls_without_state == {"some-key", "key-0"} did_call_effect.clear() - for i in range(1, 5): + for _i in range(1, 5): await layout.deliver(event_message(set_child_key_num.target)) await layout.render() assert effect_calls_without_state == {"some-key", "key-0"} @@ -1167,7 +1172,6 @@ def Parent(): def Child(): nonlocal schedule_removed_child_render schedule_removed_child_render = use_force_render() - return None async with reactpy.Layout(Parent()) as layout: await layout.render() diff --git a/tests/test_core/test_serve.py b/src/py/reactpy/tests/test_core/test_serve.py similarity index 99% rename from tests/test_core/test_serve.py rename to src/py/reactpy/tests/test_core/test_serve.py index b875b3649..02d54875d 100644 --- a/tests/test_core/test_serve.py +++ b/src/py/reactpy/tests/test_core/test_serve.py @@ -10,7 +10,6 @@ from reactpy.testing import StaticEventHandler from tests.tooling.common import event_message - EVENT_NAME = "on_event" STATIC_EVENT_HANDLER = StaticEventHandler() diff --git a/tests/test_core/test_vdom.py b/src/py/reactpy/tests/test_core/test_vdom.py similarity index 99% rename from tests/test_core/test_vdom.py rename to src/py/reactpy/tests/test_core/test_vdom.py index 1b86207de..2aac75bb4 100644 --- a/tests/test_core/test_vdom.py +++ b/src/py/reactpy/tests/test_core/test_vdom.py @@ -9,7 +9,6 @@ from reactpy.core.types import VdomDict from reactpy.core.vdom import is_vdom, make_vdom_constructor, validate_vdom_json - FAKE_EVENT_HANDLER = EventHandler(lambda data: None) FAKE_EVENT_HANDLER_DICT = {"on_event": FAKE_EVENT_HANDLER} @@ -70,7 +69,7 @@ def test_is_vdom(result, value): {"tagName": "div", "children": [0, 1, 2]}, ), ( - reactpy.vdom("div", map(lambda x: x**2, [1, 2, 3])), + reactpy.vdom("div", (x**2 for x in [1, 2, 3])), {"tagName": "div", "children": [1, 4, 9]}, ), ], diff --git a/tests/test_html.py b/src/py/reactpy/tests/test_html.py similarity index 100% rename from tests/test_html.py rename to src/py/reactpy/tests/test_html.py diff --git a/tests/test_sample.py b/src/py/reactpy/tests/test_sample.py similarity index 100% rename from tests/test_sample.py rename to src/py/reactpy/tests/test_sample.py diff --git a/tests/test_testing.py b/src/py/reactpy/tests/test_testing.py similarity index 91% rename from tests/test_testing.py rename to src/py/reactpy/tests/test_testing.py index 5651f93bc..760e68af3 100644 --- a/tests/test_testing.py +++ b/src/py/reactpy/tests/test_testing.py @@ -6,7 +6,7 @@ from reactpy import Ref, component, html, testing from reactpy.backend import starlette as starlette_implementation from reactpy.logging import ROOT_LOGGER -from reactpy.sample import SampleApp as SampleApp +from reactpy.sample import SampleApp from reactpy.testing.backend import _hotswap from reactpy.testing.display import DisplayFixture @@ -14,7 +14,8 @@ def test_assert_reactpy_logged_does_not_supress_errors(): with pytest.raises(RuntimeError, match="expected error"): with testing.assert_reactpy_did_log(): - raise RuntimeError("expected error") + msg = "expected error" + raise RuntimeError(msg) def test_assert_reactpy_logged_message(): @@ -32,7 +33,8 @@ def test_assert_reactpy_logged_error(): match_error="my value error", ): try: - raise ValueError("my value error") + msg = "my value error" + raise ValueError(msg) except ValueError: ROOT_LOGGER.exception("log message") @@ -47,7 +49,8 @@ def test_assert_reactpy_logged_error(): ): try: # change error type - raise RuntimeError("my value error") + msg = "my value error" + raise RuntimeError(msg) except RuntimeError: ROOT_LOGGER.exception("log message") @@ -62,7 +65,8 @@ def test_assert_reactpy_logged_error(): ): try: # change error message - raise ValueError("something else") + msg = "something else" + raise ValueError(msg) except ValueError: ROOT_LOGGER.exception("log message") @@ -77,7 +81,8 @@ def test_assert_reactpy_logged_error(): ): try: # change error message - raise ValueError("my error message") + msg = "my error message" + raise ValueError(msg) except ValueError: ROOT_LOGGER.exception("something else") @@ -125,7 +130,8 @@ def test_assert_reactpy_did_not_log(): match_error=r".*", ): try: - raise Exception("something") + msg = "something" + raise Exception(msg) except Exception: ROOT_LOGGER.exception("something") @@ -157,7 +163,8 @@ def test_list_logged_excptions(): ROOT_LOGGER.info("A non-error log message") try: - raise ValueError("An error for testing") + msg = "An error for testing" + raise ValueError(msg) except Exception as error: ROOT_LOGGER.exception("Log the error") the_error = error diff --git a/tests/test_utils.py b/src/py/reactpy/tests/test_utils.py similarity index 100% rename from tests/test_utils.py rename to src/py/reactpy/tests/test_utils.py diff --git a/tests/test_web/__init__.py b/src/py/reactpy/tests/test_web/__init__.py similarity index 100% rename from tests/test_web/__init__.py rename to src/py/reactpy/tests/test_web/__init__.py diff --git a/tests/test_web/js_fixtures/component-can-have-child.js b/src/py/reactpy/tests/test_web/js_fixtures/component-can-have-child.js similarity index 90% rename from tests/test_web/js_fixtures/component-can-have-child.js rename to src/py/reactpy/tests/test_web/js_fixtures/component-can-have-child.js index e1e892ca0..fd443b164 100644 --- a/tests/test_web/js_fixtures/component-can-have-child.js +++ b/src/py/reactpy/tests/test_web/js_fixtures/component-can-have-child.js @@ -8,7 +8,7 @@ export function bind(node, config) { create: (type, props, children) => h(type, props, ...children), render: (element) => render(element, node), unmount: () => render(null, node), - } + }; } // The intention here is that Child components are passed in here so we check that the @@ -19,9 +19,9 @@ export function Parent(props) {

the parent

    ${props.children} - ` + `; } export function Child({ index }) { - return html`
  • child ${index}
  • ` + return html`
  • child ${index}
  • `; } diff --git a/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/index.js b/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/index.js new file mode 100644 index 000000000..372f108bd --- /dev/null +++ b/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/index.js @@ -0,0 +1,2 @@ +export { index as Index }; +export * from "./one.js"; diff --git a/tests/test_web/js_fixtures/export-resolution/one.js b/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/one.js similarity index 88% rename from tests/test_web/js_fixtures/export-resolution/one.js rename to src/py/reactpy/tests/test_web/js_fixtures/export-resolution/one.js index 1d404b1cc..ddea9d2cb 100644 --- a/tests/test_web/js_fixtures/export-resolution/one.js +++ b/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/one.js @@ -1,4 +1,4 @@ -export {one as One}; +export { one as One }; // use ../ just to check that it works export * from "../export-resolution/two.js"; // this default should not be exported by the * re-export in index.js diff --git a/tests/test_web/js_fixtures/export-resolution/two.js b/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/two.js similarity index 65% rename from tests/test_web/js_fixtures/export-resolution/two.js rename to src/py/reactpy/tests/test_web/js_fixtures/export-resolution/two.js index 4e1d807c2..f01389d2e 100644 --- a/tests/test_web/js_fixtures/export-resolution/two.js +++ b/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/two.js @@ -1,2 +1,2 @@ -export {two as Two}; +export { two as Two }; export * from "https://some.external.url"; diff --git a/tests/test_web/js_fixtures/exports-syntax.js b/src/py/reactpy/tests/test_web/js_fixtures/exports-syntax.js similarity index 100% rename from tests/test_web/js_fixtures/exports-syntax.js rename to src/py/reactpy/tests/test_web/js_fixtures/exports-syntax.js diff --git a/tests/test_web/js_fixtures/exports-two-components.js b/src/py/reactpy/tests/test_web/js_fixtures/exports-two-components.js similarity index 80% rename from tests/test_web/js_fixtures/exports-two-components.js rename to src/py/reactpy/tests/test_web/js_fixtures/exports-two-components.js index e647ac928..10aa7fdbe 100644 --- a/tests/test_web/js_fixtures/exports-two-components.js +++ b/src/py/reactpy/tests/test_web/js_fixtures/exports-two-components.js @@ -8,13 +8,13 @@ export function bind(node, config) { create: (type, props, children) => h(type, props, ...children), render: (element) => render(element, node), unmount: () => render(null, node), - } + }; } export function Header1(props) { - return h("h1", {id: props.id}, props.text); + return h("h1", { id: props.id }, props.text); } export function Header2(props) { - return h("h2", {id: props.id}, props.text); + return h("h2", { id: props.id }, props.text); } diff --git a/tests/test_web/js_fixtures/set-flag-when-unmount-is-called.js b/src/py/reactpy/tests/test_web/js_fixtures/set-flag-when-unmount-is-called.js similarity index 99% rename from tests/test_web/js_fixtures/set-flag-when-unmount-is-called.js rename to src/py/reactpy/tests/test_web/js_fixtures/set-flag-when-unmount-is-called.js index a58d89e9f..dbb1a1c99 100644 --- a/tests/test_web/js_fixtures/set-flag-when-unmount-is-called.js +++ b/src/py/reactpy/tests/test_web/js_fixtures/set-flag-when-unmount-is-called.js @@ -3,7 +3,7 @@ export function bind(node, config) { create: (type, props, children) => type(props), render: (element) => renderElement(element, node), unmount: () => unmountElement(node), - } + }; } export function renderElement(element, container) { diff --git a/tests/test_web/js_fixtures/simple-button.js b/src/py/reactpy/tests/test_web/js_fixtures/simple-button.js similarity index 95% rename from tests/test_web/js_fixtures/simple-button.js rename to src/py/reactpy/tests/test_web/js_fixtures/simple-button.js index 00b094138..2b49f505b 100644 --- a/tests/test_web/js_fixtures/simple-button.js +++ b/src/py/reactpy/tests/test_web/js_fixtures/simple-button.js @@ -8,7 +8,7 @@ export function bind(node, config) { create: (type, props, children) => h(type, props, ...children), render: (element) => render(element, node), unmount: () => render(null, node), - } + }; } export function SimpleButton(props) { @@ -20,6 +20,6 @@ export function SimpleButton(props) { props.onClick({ data: props.eventResponseData }); }, }, - "simple button" + "simple button", ); } diff --git a/tests/test_web/test_module.py b/src/py/reactpy/tests/test_web/test_module.py similarity index 99% rename from tests/test_web/test_module.py rename to src/py/reactpy/tests/test_web/test_module.py index 4b0de2af1..f8783337d 100644 --- a/tests/test_web/test_module.py +++ b/src/py/reactpy/tests/test_web/test_module.py @@ -14,7 +14,6 @@ ) from reactpy.web.module import NAME_SOURCE, WebModule - JS_FIXTURES_DIR = Path(__file__).parent / "js_fixtures" diff --git a/tests/test_web/test_utils.py b/src/py/reactpy/tests/test_web/test_utils.py similarity index 99% rename from tests/test_web/test_utils.py rename to src/py/reactpy/tests/test_web/test_utils.py index 4ed01dd83..14c3e2e13 100644 --- a/tests/test_web/test_utils.py +++ b/src/py/reactpy/tests/test_web/test_utils.py @@ -11,7 +11,6 @@ resolve_module_exports_from_url, ) - JS_FIXTURES_DIR = Path(__file__).parent / "js_fixtures" diff --git a/tests/test_widgets.py b/src/py/reactpy/tests/test_widgets.py similarity index 99% rename from tests/test_widgets.py rename to src/py/reactpy/tests/test_widgets.py index e4ac1a341..d786fded0 100644 --- a/tests/test_widgets.py +++ b/src/py/reactpy/tests/test_widgets.py @@ -5,7 +5,6 @@ from reactpy.testing import DisplayFixture, poll from tests.tooling.common import DEFAULT_TYPE_DELAY - HERE = Path(__file__).parent diff --git a/tests/tooling/__init__.py b/src/py/reactpy/tests/tooling/__init__.py similarity index 100% rename from tests/tooling/__init__.py rename to src/py/reactpy/tests/tooling/__init__.py diff --git a/tests/tooling/asserts.py b/src/py/reactpy/tests/tooling/asserts.py similarity index 62% rename from tests/tooling/asserts.py rename to src/py/reactpy/tests/tooling/asserts.py index 49105fb80..ac84aa0ba 100644 --- a/tests/tooling/asserts.py +++ b/src/py/reactpy/tests/tooling/asserts.py @@ -1,5 +1,5 @@ def assert_same_items(left, right): """Check that two unordered sequences are equal (only works if reprs are equal)""" - sorted_left = list(sorted(left, key=repr)) - sorted_right = list(sorted(right, key=repr)) + sorted_left = sorted(left, key=repr) + sorted_right = sorted(right, key=repr) assert sorted_left == sorted_right diff --git a/tests/tooling/common.py b/src/py/reactpy/tests/tooling/common.py similarity index 99% rename from tests/tooling/common.py rename to src/py/reactpy/tests/tooling/common.py index 8a929edce..b84a3ac96 100644 --- a/tests/tooling/common.py +++ b/src/py/reactpy/tests/tooling/common.py @@ -2,7 +2,6 @@ from reactpy.core.types import LayoutEventMessage, LayoutUpdateMessage - # see: https://github.com/microsoft/playwright-python/issues/1614 DEFAULT_TYPE_DELAY = 100 # miliseconds diff --git a/tests/tooling/hooks.py b/src/py/reactpy/tests/tooling/hooks.py similarity index 100% rename from tests/tooling/hooks.py rename to src/py/reactpy/tests/tooling/hooks.py diff --git a/tests/tooling/loop.py b/src/py/reactpy/tests/tooling/loop.py similarity index 92% rename from tests/tooling/loop.py rename to src/py/reactpy/tests/tooling/loop.py index db6f91b83..f130d080f 100644 --- a/tests/tooling/loop.py +++ b/src/py/reactpy/tests/tooling/loop.py @@ -46,9 +46,9 @@ def open_event_loop(as_current: bool = True) -> Iterator[asyncio.AbstractEventLo start = time.time() while loop.is_running(): if (time.time() - start) > REACTPY_TESTING_DEFAULT_TIMEOUT.current: + msg = f"Failed to stop loop after {REACTPY_TESTING_DEFAULT_TIMEOUT.current} seconds" raise TimeoutError( - "Failed to stop loop after " - f"{REACTPY_TESTING_DEFAULT_TIMEOUT.current} seconds" + msg ) time.sleep(0.1) loop.close() @@ -82,7 +82,8 @@ def one_task_finished(future): else: # user was responsible for cancelling all tasks if not done.wait(timeout=3): - raise TimeoutError("Could not stop event loop in time") + msg = "Could not stop event loop in time" + raise TimeoutError(msg) for task in to_cancel: if task.cancelled(): diff --git a/tests/test_web/js_fixtures/export-resolution/index.js b/tests/test_web/js_fixtures/export-resolution/index.js deleted file mode 100644 index 2f1f46a51..000000000 --- a/tests/test_web/js_fixtures/export-resolution/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export {index as Index}; -export * from "./one.js"; From 37fab4fa4343b75bfb77dd2629d4cacad488278a Mon Sep 17 00:00:00 2001 From: rmorshea Date: Mon, 29 May 2023 15:46:07 -0600 Subject: [PATCH 02/26] setup root pyproject.toml + basic invoke tasks --- .pre-commit-config.yaml | 14 - noxfile.py | 553 ------------------------------ pyproject.toml | 80 +++++ src/py/reactpy/pyproject.toml | 2 +- tasks.py | 612 ++++++++++++++++++++++++++++++++++ 5 files changed, 693 insertions(+), 568 deletions(-) delete mode 100644 .pre-commit-config.yaml delete mode 100644 noxfile.py create mode 100644 pyproject.toml create mode 100644 tasks.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index b28e5d978..000000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -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 diff --git a/noxfile.py b/noxfile.py deleted file mode 100644 index 35973db2f..000000000 --- a/noxfile.py +++ /dev/null @@ -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[0-9a-zA-Z-@/]+)-" - # package version - r"v(?P[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 --" - ) - 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}") diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..1e9787f3a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,80 @@ +# --- Project ---------------------------------------------------------------------------- + +[project] +name = "project" +version = "0.0.0" +description = "Scripts for managing the ReactPy respository" +dependencies = ["invoke"] + +# --- Hatch ---------------------------------------------------------------------------- + +[tool.hatch.envs.default] +post-install-commands = ["invoke env"] + +[tool.hatch.envs.default.scripts] +lint = "invoke lint" +test = "invoke test" + +# --- Black ---------------------------------------------------------------------------- + +[tool.black] +target-version = ["py39"] +line-length = 88 +skip-string-normalization = true + +# --- Ruff ----------------------------------------------------------------------------- + +[tool.ruff] +target-version = "py39" +line-length = 88 +select = [ + "A", + "ARG", + "B", + "C", + "DTZ", + "E", + "EM", + "F", + "FBT", + "I", + "ICN", + "ISC", + "N", + "PLC", + "PLE", + "PLR", + "PLW", + "Q", + "RUF", + "S", + "T", + "TID", + "UP", + "W", + "YTT", +] +ignore = [ + # Allow non-abstract empty methods in abstract base classes + "B027", + # Allow boolean positional values in function calls, like `dict.get(... True)` + "FBT003", + # 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"] diff --git a/src/py/reactpy/pyproject.toml b/src/py/reactpy/pyproject.toml index dc32344e9..335a19546 100644 --- a/src/py/reactpy/pyproject.toml +++ b/src/py/reactpy/pyproject.toml @@ -104,7 +104,7 @@ style = [ "ruff {args:.}", "black --check --diff {args:.}", ] -fmt = [ +fix = [ "black {args:.}", "ruff --fix {args:.}", "style", diff --git a/tasks.py b/tasks.py new file mode 100644 index 000000000..3a08907cd --- /dev/null +++ b/tasks.py @@ -0,0 +1,612 @@ +import functools +from pathlib import Path +from typing import Any, Callable, Sequence, TypeVar +from invoke import task +from invoke.context import Context + +# --- Typing Preamble ------------------------------------------------------------------ + +F = TypeVar("F") + +# --- Constants ------------------------------------------------------------------------ + +ROOT = Path(__file__).parent +SRC_DIR = ROOT / "src" +JS_DIR = SRC_DIR / "js" +PY_DIR = SRC_DIR / "py" +PY_PROJECTS = [p for p in PY_DIR.iterdir() if (p / "pyproject.toml").exists()] + + +# --- Tasks ---------------------------------------------------------------------------- + + +@task +def env(context: Context): + """Install development environment""" + in_py(context, "pip install -e .", hide="out") + in_js(context, "npm ci", hide="out") + + +@task +def lint(context: Context, fix: bool = False): + """Run linters and type checkers""" + in_py(context, f"hatch run lint:{'fix' if fix else 'all'}") + in_js(context, "npm run check:format", "npm run check:types") + + +@task +def test(context: Context, no_cov: bool = False): + """Run test suites""" + in_py(context, f"hatch run test{'' if no_cov else '-cov'}") + in_js(context, "npm run check:tests") + + +# --- Utilities ------------------------------------------------------------------------ + + +def in_py(context: Context, *commands: str, **kwargs: Any) -> None: + for p in PY_PROJECTS: + with context.cd(p): + for c in commands: + context.run(c, **kwargs) + + +def in_js(context: Context, *commands: str, **kwargs: Any) -> None: + with context.cd(JS_DIR): + for c in commands: + context.run(c, **kwargs) + + +# 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[0-9a-zA-Z-@/]+)-" +# # package version +# r"v(?P[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 --" +# ) +# 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}") From ffac74aca050308ca9f48aaaef69460738582035 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Tue, 30 May 2023 01:23:56 -0600 Subject: [PATCH 03/26] add publish task --- pyproject.toml | 22 +- src/py/reactpy/pyproject.toml | 23 +- tasks.py | 826 +++++++++++----------------------- 3 files changed, 290 insertions(+), 581 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1e9787f3a..8332710bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ name = "project" version = "0.0.0" description = "Scripts for managing the ReactPy respository" -dependencies = ["invoke"] +dependencies = ["invoke", "ruff", "black"] # --- Hatch ---------------------------------------------------------------------------- @@ -12,8 +12,17 @@ dependencies = ["invoke"] post-install-commands = ["invoke env"] [tool.hatch.envs.default.scripts] -lint = "invoke lint" -test = "invoke test" +publish = "invoke publish {args}" + +checks = ["lint", "test"] +lint = ["lint-py {args}", "lint-js {args}"] +test = ["test-py", "test-js"] + +lint-py = "invoke lint-py {args}" +lint-js = "invoke lint-js {args}" + +test-py = "invoke test-py {args}" +test-js = "invoke test-js {args}" # --- Black ---------------------------------------------------------------------------- @@ -36,7 +45,8 @@ select = [ "E", "EM", "F", - "FBT", + # TODO: turn this on later + # "FBT", "I", "ICN", "ISC", @@ -55,6 +65,8 @@ select = [ "YTT", ] ignore = [ + # TODO: turn this on later + "N802", "N806", # allow TitleCase functions/variables # Allow non-abstract empty methods in abstract base classes "B027", # Allow boolean positional values in function calls, like `dict.get(... True)` @@ -77,4 +89,4 @@ ban-relative-imports = "all" [tool.ruff.per-file-ignores] # Tests can use magic values, assertions, and relative imports -"tests/**/*" = ["PLR2004", "S101", "TID252"] +"**/tests/**/*" = ["PLR2004", "S101", "TID252"] diff --git a/src/py/reactpy/pyproject.toml b/src/py/reactpy/pyproject.toml index 335a19546..b7cafdc8b 100644 --- a/src/py/reactpy/pyproject.toml +++ b/src/py/reactpy/pyproject.toml @@ -93,29 +93,21 @@ cov = [ [tool.hatch.envs.lint] detached = true dependencies = [ - "black>=23.1.0", "mypy>=1.0.0", - "ruff>=0.0.243", + "types-click", + "types-tornado", + "types-pkg-resources", + "types-flask", + "types-requests", ] [tool.hatch.envs.lint.scripts] typing = "mypy --install-types --non-interactive {args:reactpy tests}" -style = [ - "ruff {args:.}", - "black --check --diff {args:.}", -] -fix = [ - "black {args:.}", - "ruff --fix {args:.}", - "style", -] -all = [ - "style", - "typing", -] +all = ["typing"] [[tool.hatch.build.hooks.build-scripts.scripts]] work_dir = "../../js" +out_dir = "reactpy/_static" commands = [ "npm install", "npm run build" @@ -123,7 +115,6 @@ commands = [ artifacts = [ "app/dist/" ] -out_dir = "reactpy/_static" # --- Black ---------------------------------------------------------------------------- diff --git a/tasks.py b/tasks.py index 3a08907cd..dc0d444c0 100644 --- a/tasks.py +++ b/tasks.py @@ -1,20 +1,54 @@ -import functools +from __future__ import annotations + +import json +import os +import re +from dataclasses import dataclass +from logging import getLogger from pathlib import Path -from typing import Any, Callable, Sequence, TypeVar +from shutil import rmtree +from typing import TYPE_CHECKING, Any, Callable + from invoke import task from invoke.context import Context +from invoke.exceptions import Exit # --- Typing Preamble ------------------------------------------------------------------ -F = TypeVar("F") + +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, context: Context, package: PackageInfo + ) -> Callable[[bool], None]: + ... + + LanguageName: TypeAlias = "Literal['py', 'js']" + # --- Constants ------------------------------------------------------------------------ + +log = getLogger(__name__) ROOT = Path(__file__).parent SRC_DIR = ROOT / "src" JS_DIR = SRC_DIR / "js" PY_DIR = SRC_DIR / "py" PY_PROJECTS = [p for p in PY_DIR.iterdir() if (p / "pyproject.toml").exists()] +TAG_PATTERN = re.compile( + # start + r"^" + # package name + r"(?P[0-9a-zA-Z-@/]+)-" + # package version + r"v(?P[0-9][0-9a-zA-Z-\.\+]*)" + # end + r"$" +) # --- Tasks ---------------------------------------------------------------------------- @@ -28,585 +62,257 @@ def env(context: Context): @task -def lint(context: Context, fix: bool = False): +def lint_py(context: Context, fix: bool = False): """Run linters and type checkers""" - in_py(context, f"hatch run lint:{'fix' if fix else 'all'}") - in_js(context, "npm run check:format", "npm run check:types") + if fix: + context.run("black .") + context.run("ruff --fix .") + else: + context.run("ruff .") + context.run("black --check --diff .") @task -def test(context: Context, no_cov: bool = False): - """Run test suites""" - in_py(context, f"hatch run test{'' if no_cov else '-cov'}") - in_js(context, "npm run check:tests") +def lint_js(context: Context, fix: bool = False): + """Run linters and type checkers""" + if fix: + in_js(context, "npm run fix:format") + else: + in_js(context, "npm run check:format") + in_js(context, "npm run check:types") -# --- Utilities ------------------------------------------------------------------------ +@task +def test_py(context: Context, no_cov: bool = False): + """Run test suites""" + in_py(context, f"hatch run {'test' if no_cov else 'cov'}") -def in_py(context: Context, *commands: str, **kwargs: Any) -> None: - for p in PY_PROJECTS: - with context.cd(p): - for c in commands: - context.run(c, **kwargs) +@task +def test_js(context: Context): + """Run test suites""" + in_js(context, "npm run check:tests") -def in_js(context: Context, *commands: str, **kwargs: Any) -> None: - with context.cd(JS_DIR): - for c in commands: - context.run(c, **kwargs) +@task +def publish(context: Context, dry_run: str = ""): + """Publish packages that have been tagged for release in the current commit + To perform a test run use `--dry-run=-v` to specify a comma-separated + list of tags to simulate a release of. For example, to simulate a release of + `@foo/bar-v1.2.3` and `baz-v4.5.6` use `--dry-run=@foo/bar-v1.2.3,baz-v4.5.6`. + """ + packages = get_packages(context) -# from __future__ import annotations + release_prep: dict[LanguageName, ReleasePrepFunc] = { + "js": prepare_js_release, + "py": prepare_py_release, + } -# 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 + parsed_tags: list[TagInfo] = [ + parse_tag(tag) for tag in dry_run.split(",") or get_current_tags(context) + ] -# from noxopt import Annotated, NoxOpt, Option, Session + publishers: list[Callable[[bool], None]] = [] + for tag_info in parsed_tags: + if tag_info.name not in packages: + msg = f"Tag {tag_info.tag} references package {tag_info.name} that does not exist" + raise Exit(msg) + pkg_info = packages[tag_info.name] + if pkg_info.version != tag_info.version: + msg = f"Tag {tag_info.tag} references version {tag_info.version} of package {tag_info.name}, but the current version is {pkg_info.version}" + raise Exit(msg) -# # --- Typing Preamble ------------------------------------------------------------------ + log.info(f"Preparing {tag_info.name} for release...") + publishers.append(release_prep[pkg_info.language](context, pkg_info)) + for publish in publishers: + publish(bool(dry_run)) -# 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]: -# ... +# --- Utilities ------------------------------------------------------------------------ -# LanguageName: TypeAlias = "Literal['py', 'js']" +def in_py(context: Context, *commands: str, **kwargs: Any) -> None: + for p in PY_PROJECTS: + with context.cd(p): + for c in commands: + context.run(c, **kwargs) -# # --- Constants ------------------------------------------------------------------------ +def in_js(context: Context, *commands: str, **kwargs: Any) -> None: + with context.cd(JS_DIR): + for c in commands: + context.run(c, **kwargs) -# 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[0-9a-zA-Z-@/]+)-" -# # package version -# r"v(?P[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 --" -# ) -# 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}") +def get_packages(context: Context) -> dict[str, PackageInfo]: + packages: list[PackageInfo] = [] + + for maybe_pkg in PY_DIR.glob("*"): + if (maybe_pkg / "pyproject.toml").exists(): + packages.append(make_py_pkg_info(context, maybe_pkg)) + else: + msg = f"unexpected dir or file: {maybe_pkg}" + raise Exit(msg) + + packages_dir = JS_DIR / "packages" + for maybe_pkg in packages_dir.glob("*"): + if (maybe_pkg / "package.json").exists(): + packages.append(make_js_pkg_info(maybe_pkg)) + elif maybe_pkg.is_dir(): + for maybe_ns_pkg in maybe_pkg.glob("*"): + if (maybe_ns_pkg / "package.json").exists(): + packages.append(make_js_pkg_info(maybe_ns_pkg)) + else: + msg = f"unexpected dir or file: {maybe_pkg}" + raise Exit(msg) + + packages_by_name = {p.name: p for p in packages} + assert len(packages_by_name) == len(packages), "duplicate package names" + + return packages_by_name + + +def make_py_pkg_info(context: Context, pkg_dir: Path) -> PackageInfo: + with context.cd(pkg_dir): + proj_metadata = json.loads(context.run("hatch project metadata").stdout) + return PackageInfo( + name=proj_metadata["name"], + path=pkg_dir, + language="py", + version=proj_metadata["version"], + ) + + +def make_js_pkg_info(pkg_dir: Path) -> PackageInfo: + with (pkg_dir / "package.json").open() as f: + pkg_json = json.load(f) + return PackageInfo( + name=pkg_json["name"], + path=pkg_dir, + language="js", + version=pkg_json["version"], + ) + + +@dataclass +class PackageInfo: + name: str + path: Path + language: LanguageName + version: str + + +def get_current_tags(context: Context) -> set[str]: + """Get tags for the current commit""" + # check if unstaged changes + try: + context.run("git diff --cached --exit-code", hide=True) + context.run("git diff --exit-code", hide=True) + except Exception: + log.error("Cannot create a tag - there are uncommited changes") + return set() + + tags_per_commit: dict[str, list[str]] = {} + for commit, tag in map( + str.split, + context.run( + r"git for-each-ref --format '%(objectname) %(refname:short)' refs/tags", + hide=True, + ).stdout.splitlines(), + ): + tags_per_commit.setdefault(commit, []).append(tag) + + current_commit = context.run( + "git rev-parse HEAD", silent=True, external=True + ).stdout.strip() + tags = set(tags_per_commit.get(current_commit, set())) + + if not tags: + log.error("No tags found for current commit") + + for t in tags: + if not TAG_PATTERN.match(t): + msg = f"Invalid tag: {t}" + raise Exit(msg) + + log.info(f"Found tags: {tags}") + + return tags + + +def parse_tag(tag: str) -> TagInfo: + match = TAG_PATTERN.match(tag) + if not match: + msg = f"Invalid tag: {tag}" + raise Exit(msg) + return TagInfo(tag=tag, name=match.group("name"), version=match.group("version")) + + +@dataclass +class TagInfo: + tag: str + name: str + version: str + + +def prepare_js_release( + context: Context, package: PackageInfo +) -> Callable[[bool], None]: + node_auth_token = os.getenv("NODE_AUTH_TOKEN") + if node_auth_token is None: + msg = "NODE_AUTH_TOKEN environment variable must be set" + raise Exit(msg) + + with context.cd(JS_DIR): + context.run("npm ci") + context.run("npm run build") + + def publish(dry_run: bool) -> None: + with context.cd(JS_DIR): + if dry_run: + context.run(f"npm --workspace {package.name} pack --dry-run") + return + context.run( + f"npm --workspace {package.name} publish --access public", + env_dict={"NODE_AUTH_TOKEN": node_auth_token}, + ) + + return publish + + +def prepare_py_release( + context: Context, package: PackageInfo +) -> Callable[[bool], None]: + twine_username = os.getenv("PYPI_USERNAME") + twine_password = os.getenv("PYPI_PASSWORD") + + if not (twine_password and twine_username): + msg = "PYPI_USERNAME and PYPI_PASSWORD environment variables must be set" + raise Exit(msg) + + 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)) + + with context.cd(package.path): + context.run("hatch build") + + def publish(dry_run: bool): + with context.cd(package.path): + if dry_run: + context.run("twine check dist/*") + return + + context.run( + "twine upload dist/*", + env_dict={ + "TWINE_USERNAME": twine_username, + "TWINE_PASSWORD": twine_password, + }, + ) + + return publish From e025f05c7fef47aef662085860a8e51d77f7cf8c Mon Sep 17 00:00:00 2001 From: rmorshea Date: Tue, 30 May 2023 01:39:17 -0600 Subject: [PATCH 04/26] more ruff fixes --- .../{.nox-session.yml => .hatch-run.yml} | 11 ++-- .github/workflows/test.yml | 6 +- docs/app.py | 6 +- docs/examples.py | 5 +- docs/source/_exts/async_doctest.py | 3 - docs/source/_exts/autogen_api_docs.py | 7 +- docs/source/_exts/build_custom_js.py | 1 - docs/source/_exts/custom_autosectionlabel.py | 1 - docs/source/_exts/reactpy_example.py | 9 ++- docs/source/_exts/reactpy_view.py | 1 - docs/source/conf.py | 4 +- .../_examples/adding_state_variable/main.py | 1 - .../_examples/isolated_state/main.py | 1 - .../multiple_state_variables/main.py | 1 - .../_examples/list_re_order.py | 2 +- .../sorted_and_filtered_todo_list.py | 2 +- .../_examples/todo_list_with_keys.py | 2 +- .../_examples/material_ui_button_no_action.py | 1 - .../_examples/material_ui_button_on_click.py | 1 - .../_examples/super_simple_chart/main.py | 1 - .../_examples/debug_error_example.py | 3 +- .../getting-started/_examples/run_fastapi.py | 1 - .../getting-started/_examples/run_flask.py | 1 - .../getting-started/_examples/run_sanic.py | 1 - .../_examples/run_starlette.py | 1 - .../getting-started/_examples/run_tornado.py | 1 - .../getting-started/_examples/sample_app.py | 1 - .../_static/embed-reactpy-view/main.py | 1 - .../_examples/filterable_list/main.py | 3 +- .../_examples/character_movement/main.py | 1 - .../reference/_examples/material_ui_switch.py | 1 - .../reference/_examples/matplotlib_plot.py | 6 +- .../reference/_examples/network_graph.py | 1 - .../source/reference/_examples/pigeon_maps.py | 21 +++--- .../reference/_examples/simple_dashboard.py | 1 - docs/source/reference/_examples/snake_game.py | 2 +- docs/source/reference/_examples/todo.py | 2 +- .../_examples/use_reducer_counter.py | 3 +- .../reference/_examples/victory_chart.py | 1 - pyproject.toml | 16 +++++ scripts/live_docs.py | 3 +- scripts/one_example.py | 3 +- scripts/run_docs.py | 2 - src/py/reactpy/pyproject.toml | 64 ------------------- src/py/reactpy/reactpy/_console/ast_utils.py | 8 ++- src/py/reactpy/reactpy/_warnings.py | 3 +- src/py/reactpy/reactpy/backend/_common.py | 3 +- src/py/reactpy/reactpy/backend/flask.py | 12 ++-- src/py/reactpy/reactpy/backend/hooks.py | 3 +- src/py/reactpy/reactpy/backend/sanic.py | 6 +- src/py/reactpy/reactpy/backend/starlette.py | 7 +- src/py/reactpy/reactpy/backend/tornado.py | 4 +- src/py/reactpy/reactpy/backend/types.py | 5 +- src/py/reactpy/reactpy/backend/utils.py | 7 +- src/py/reactpy/reactpy/core/component.py | 6 +- src/py/reactpy/reactpy/core/events.py | 8 +-- src/py/reactpy/reactpy/core/hooks.py | 7 +- src/py/reactpy/reactpy/core/layout.py | 12 +--- src/py/reactpy/reactpy/core/serve.py | 3 +- src/py/reactpy/reactpy/core/types.py | 9 +-- src/py/reactpy/reactpy/core/vdom.py | 4 +- src/py/reactpy/reactpy/html.py | 8 +-- src/py/reactpy/reactpy/svg.py | 4 +- src/py/reactpy/reactpy/testing/backend.py | 8 +-- src/py/reactpy/reactpy/testing/common.py | 7 +- src/py/reactpy/reactpy/testing/logs.py | 3 +- src/py/reactpy/reactpy/utils.py | 11 ++-- src/py/reactpy/reactpy/web/utils.py | 11 ++-- src/py/reactpy/reactpy/widgets.py | 7 +- src/py/reactpy/setup.py | 5 +- src/py/reactpy/tests/test_backend/test_all.py | 2 +- src/py/reactpy/tests/test_core/test_layout.py | 1 - src/py/reactpy/tests/test_core/test_serve.py | 3 +- src/py/reactpy/tests/tooling/loop.py | 27 +++----- 74 files changed, 146 insertions(+), 264 deletions(-) rename .github/workflows/{.nox-session.yml => .hatch-run.yml} (87%) diff --git a/.github/workflows/.nox-session.yml b/.github/workflows/.hatch-run.yml similarity index 87% rename from .github/workflows/.nox-session.yml rename to .github/workflows/.hatch-run.yml index 827fb4192..9d715908c 100644 --- a/.github/workflows/.nox-session.yml +++ b/.github/workflows/.hatch-run.yml @@ -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 @@ -29,7 +26,7 @@ on: required: false jobs: - nox-session: + hatch: name: ${{ format(inputs.job-name, matrix.python-version, matrix.runs-on) }} strategy: matrix: @@ -55,4 +52,4 @@ jobs: 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 }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0b733f924..33b373f98 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,10 +12,10 @@ on: jobs: python-exhaustive: - uses: ./.github/workflows/.nox-session.yml + uses: ./.github/workflows/.hatch-run.yml with: job-name: "python-{0}" - nox-args: "-t check-python" + hatch-run: "-t check-python" nox-session-args: "--pytest --maxfail=3 --reruns 3" python-environments: uses: ./.github/workflows/.nox-session.yml @@ -24,7 +24,7 @@ jobs: 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"]' + python-version-array: '["3.9", "3.10", "3.11"]' docs: uses: ./.github/workflows/.nox-session.yml with: diff --git a/docs/app.py b/docs/app.py index 0b1ad302a..eb3f5dc93 100644 --- a/docs/app.py +++ b/docs/app.py @@ -3,13 +3,11 @@ from sanic import Sanic, response +from docs.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 - - HERE = Path(__file__).parent REACTPY_MODEL_SERVER_URL_PREFIX = "/_reactpy" @@ -46,7 +44,7 @@ def make_app(): app.static("/docs", str(HERE / "build")) @app.route("/") - async def forward_to_index(request): + async def forward_to_index(_): return response.redirect("/docs/index.html") configure( diff --git a/docs/examples.py b/docs/examples.py index 142d49429..3a29ec691 100644 --- a/docs/examples.py +++ b/docs/examples.py @@ -1,14 +1,14 @@ 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" CONF_FILE = SOURCE_DIR / "conf.py" @@ -148,7 +148,6 @@ def __init__(self, max_lines: int = 10): def set_callback(self, function: Callable[[str], None]) -> None: self._callback = function - return None def getvalue(self) -> str: return "".join(self._lines) diff --git a/docs/source/_exts/async_doctest.py b/docs/source/_exts/async_doctest.py index 905c1d00a..96024d488 100644 --- a/docs/source/_exts/async_doctest.py +++ b/docs/source/_exts/async_doctest.py @@ -6,7 +6,6 @@ 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 @@ def test_runner(self) -> DocTestRunner: @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 diff --git a/docs/source/_exts/autogen_api_docs.py b/docs/source/_exts/autogen_api_docs.py index 9f833ac14..1de1a8493 100644 --- a/docs/source/_exts/autogen_api_docs.py +++ b/docs/source/_exts/autogen_api_docs.py @@ -1,12 +1,11 @@ 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" @@ -83,7 +82,9 @@ 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" + if len(rel_path_parts) < len(SECTION_SYMBOLS): + msg = "package structure is too deep" + raise RuntimeError(msg) return SECTION_SYMBOLS[len(rel_path_parts)] diff --git a/docs/source/_exts/build_custom_js.py b/docs/source/_exts/build_custom_js.py index b84378353..08014a067 100644 --- a/docs/source/_exts/build_custom_js.py +++ b/docs/source/_exts/build_custom_js.py @@ -3,7 +3,6 @@ from sphinx.application import Sphinx - SOURCE_DIR = Path(__file__).parent.parent CUSTOM_JS_DIR = SOURCE_DIR / "_custom_js" diff --git a/docs/source/_exts/custom_autosectionlabel.py b/docs/source/_exts/custom_autosectionlabel.py index 9610ad684..92ff5e2df 100644 --- a/docs/source/_exts/custom_autosectionlabel.py +++ b/docs/source/_exts/custom_autosectionlabel.py @@ -17,7 +17,6 @@ from sphinx.util import logging from sphinx.util.nodes import clean_astext - logger = logging.getLogger(__name__) diff --git a/docs/source/_exts/reactpy_example.py b/docs/source/_exts/reactpy_example.py index 7cad84853..2e3ae489c 100644 --- a/docs/source/_exts/reactpy_example.py +++ b/docs/source/_exts/reactpy_example.py @@ -41,10 +41,8 @@ def run(self): 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 +112,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)), diff --git a/docs/source/_exts/reactpy_view.py b/docs/source/_exts/reactpy_view.py index 477a6caca..478dbbcae 100644 --- a/docs/source/_exts/reactpy_view.py +++ b/docs/source/_exts/reactpy_view.py @@ -7,7 +7,6 @@ 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( "/" diff --git a/docs/source/conf.py b/docs/source/conf.py index 6c8c50927..8d3e4832f 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # @@ -10,7 +9,6 @@ from doctest import DONT_ACCEPT_TRUE_FOR_1, ELLIPSIS, NORMALIZE_WHITESPACE from pathlib import Path - # -- Path Setup -------------------------------------------------------------- THIS_DIR = Path(__file__).parent @@ -32,7 +30,7 @@ "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 --------------------------------------------------- diff --git a/docs/source/guides/adding-interactivity/components-with-state/_examples/adding_state_variable/main.py b/docs/source/guides/adding-interactivity/components-with-state/_examples/adding_state_variable/main.py index 860cd093b..a919a2354 100644 --- a/docs/source/guides/adding-interactivity/components-with-state/_examples/adding_state_variable/main.py +++ b/docs/source/guides/adding-interactivity/components-with-state/_examples/adding_state_variable/main.py @@ -3,7 +3,6 @@ from reactpy import component, hooks, html, run - HERE = Path(__file__) DATA_PATH = HERE.parent / "data.json" sculpture_data = json.loads(DATA_PATH.read_text()) diff --git a/docs/source/guides/adding-interactivity/components-with-state/_examples/isolated_state/main.py b/docs/source/guides/adding-interactivity/components-with-state/_examples/isolated_state/main.py index 3149e6396..d07b87140 100644 --- a/docs/source/guides/adding-interactivity/components-with-state/_examples/isolated_state/main.py +++ b/docs/source/guides/adding-interactivity/components-with-state/_examples/isolated_state/main.py @@ -3,7 +3,6 @@ from reactpy import component, hooks, html, run - HERE = Path(__file__) DATA_PATH = HERE.parent / "data.json" sculpture_data = json.loads(DATA_PATH.read_text()) diff --git a/docs/source/guides/adding-interactivity/components-with-state/_examples/multiple_state_variables/main.py b/docs/source/guides/adding-interactivity/components-with-state/_examples/multiple_state_variables/main.py index 3d31b1bfa..87f9651be 100644 --- a/docs/source/guides/adding-interactivity/components-with-state/_examples/multiple_state_variables/main.py +++ b/docs/source/guides/adding-interactivity/components-with-state/_examples/multiple_state_variables/main.py @@ -3,7 +3,6 @@ from reactpy import component, hooks, html, run - HERE = Path(__file__) DATA_PATH = HERE.parent / "data.json" sculpture_data = json.loads(DATA_PATH.read_text()) diff --git a/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_re_order.py b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_re_order.py index b3bf5a85a..3bd2fd601 100644 --- a/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_re_order.py +++ b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_re_order.py @@ -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))) diff --git a/docs/source/guides/creating-interfaces/rendering-data/_examples/sorted_and_filtered_todo_list.py b/docs/source/guides/creating-interfaces/rendering-data/_examples/sorted_and_filtered_todo_list.py index 111300894..8be2b5f30 100644 --- a/docs/source/guides/creating-interfaces/rendering-data/_examples/sorted_and_filtered_todo_list.py +++ b/docs/source/guides/creating-interfaces/rendering-data/_examples/sorted_and_filtered_todo_list.py @@ -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) diff --git a/docs/source/guides/creating-interfaces/rendering-data/_examples/todo_list_with_keys.py b/docs/source/guides/creating-interfaces/rendering-data/_examples/todo_list_with_keys.py index 26a194d18..8afd2ae55 100644 --- a/docs/source/guides/creating-interfaces/rendering-data/_examples/todo_list_with_keys.py +++ b/docs/source/guides/creating-interfaces/rendering-data/_examples/todo_list_with_keys.py @@ -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) diff --git a/docs/source/guides/escape-hatches/_examples/material_ui_button_no_action.py b/docs/source/guides/escape-hatches/_examples/material_ui_button_no_action.py index f109326e8..3ad4dac5b 100644 --- a/docs/source/guides/escape-hatches/_examples/material_ui_button_no_action.py +++ b/docs/source/guides/escape-hatches/_examples/material_ui_button_no_action.py @@ -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", diff --git a/docs/source/guides/escape-hatches/_examples/material_ui_button_on_click.py b/docs/source/guides/escape-hatches/_examples/material_ui_button_on_click.py index ad3b13cb2..3fc684005 100644 --- a/docs/source/guides/escape-hatches/_examples/material_ui_button_on_click.py +++ b/docs/source/guides/escape-hatches/_examples/material_ui_button_on_click.py @@ -2,7 +2,6 @@ import reactpy - mui = reactpy.web.module_from_template( "react@^17.0.0", "@material-ui/core@4.12.4", diff --git a/docs/source/guides/escape-hatches/_examples/super_simple_chart/main.py b/docs/source/guides/escape-hatches/_examples/super_simple_chart/main.py index b9954b0f2..4640785f8 100644 --- a/docs/source/guides/escape-hatches/_examples/super_simple_chart/main.py +++ b/docs/source/guides/escape-hatches/_examples/super_simple_chart/main.py @@ -2,7 +2,6 @@ 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") diff --git a/docs/source/guides/getting-started/_examples/debug_error_example.py b/docs/source/guides/getting-started/_examples/debug_error_example.py index 36e5b4910..42d0625b1 100644 --- a/docs/source/guides/getting-started/_examples/debug_error_example.py +++ b/docs/source/guides/getting-started/_examples/debug_error_example.py @@ -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) diff --git a/docs/source/guides/getting-started/_examples/run_fastapi.py b/docs/source/guides/getting-started/_examples/run_fastapi.py index ce9974617..bb02e9d6a 100644 --- a/docs/source/guides/getting-started/_examples/run_fastapi.py +++ b/docs/source/guides/getting-started/_examples/run_fastapi.py @@ -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) diff --git a/docs/source/guides/getting-started/_examples/run_flask.py b/docs/source/guides/getting-started/_examples/run_flask.py index 29503fcc1..f98753784 100644 --- a/docs/source/guides/getting-started/_examples/run_flask.py +++ b/docs/source/guides/getting-started/_examples/run_flask.py @@ -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) diff --git a/docs/source/guides/getting-started/_examples/run_sanic.py b/docs/source/guides/getting-started/_examples/run_sanic.py index 9e5f14b45..1dae9f6e0 100644 --- a/docs/source/guides/getting-started/_examples/run_sanic.py +++ b/docs/source/guides/getting-started/_examples/run_sanic.py @@ -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) diff --git a/docs/source/guides/getting-started/_examples/run_starlette.py b/docs/source/guides/getting-started/_examples/run_starlette.py index ffb500f21..966b9ef77 100644 --- a/docs/source/guides/getting-started/_examples/run_starlette.py +++ b/docs/source/guides/getting-started/_examples/run_starlette.py @@ -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) diff --git a/docs/source/guides/getting-started/_examples/run_tornado.py b/docs/source/guides/getting-started/_examples/run_tornado.py index 03109a1d3..b86126e63 100644 --- a/docs/source/guides/getting-started/_examples/run_tornado.py +++ b/docs/source/guides/getting-started/_examples/run_tornado.py @@ -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) diff --git a/docs/source/guides/getting-started/_examples/sample_app.py b/docs/source/guides/getting-started/_examples/sample_app.py index b457ca243..a1cc34e6d 100644 --- a/docs/source/guides/getting-started/_examples/sample_app.py +++ b/docs/source/guides/getting-started/_examples/sample_app.py @@ -1,4 +1,3 @@ import reactpy - reactpy.run(reactpy.sample.SampleApp) diff --git a/docs/source/guides/getting-started/_static/embed-reactpy-view/main.py b/docs/source/guides/getting-started/_static/embed-reactpy-view/main.py index 2ff60ea91..6e3687f27 100644 --- a/docs/source/guides/getting-started/_static/embed-reactpy-view/main.py +++ b/docs/source/guides/getting-started/_static/embed-reactpy-view/main.py @@ -4,7 +4,6 @@ from reactpy import component, html from reactpy.backend.sanic import Options, configure - app = Sanic("MyApp") diff --git a/docs/source/guides/managing-state/sharing-component-state/_examples/filterable_list/main.py b/docs/source/guides/managing-state/sharing-component-state/_examples/filterable_list/main.py index b07424158..4336f4812 100644 --- a/docs/source/guides/managing-state/sharing-component-state/_examples/filterable_list/main.py +++ b/docs/source/guides/managing-state/sharing-component-state/_examples/filterable_list/main.py @@ -3,7 +3,6 @@ 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,7 +32,7 @@ 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) else: if value.lower() in row["name"].lower(): diff --git a/docs/source/reference/_examples/character_movement/main.py b/docs/source/reference/_examples/character_movement/main.py index 6bdb328e0..9545b0c0a 100644 --- a/docs/source/reference/_examples/character_movement/main.py +++ b/docs/source/reference/_examples/character_movement/main.py @@ -4,7 +4,6 @@ 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() diff --git a/docs/source/reference/_examples/material_ui_switch.py b/docs/source/reference/_examples/material_ui_switch.py index ed66d83de..704ae3145 100644 --- a/docs/source/reference/_examples/material_ui_switch.py +++ b/docs/source/reference/_examples/material_ui_switch.py @@ -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") diff --git a/docs/source/reference/_examples/matplotlib_plot.py b/docs/source/reference/_examples/matplotlib_plot.py index af7a95e67..5c4d616fe 100644 --- a/docs/source/reference/_examples/matplotlib_plot.py +++ b/docs/source/reference/_examples/matplotlib_plot.py @@ -10,7 +10,7 @@ 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 set_value_at_index(event, index=i): 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}), diff --git a/docs/source/reference/_examples/network_graph.py b/docs/source/reference/_examples/network_graph.py index f31bd96b0..79b1092f3 100644 --- a/docs/source/reference/_examples/network_graph.py +++ b/docs/source/reference/_examples/network_graph.py @@ -2,7 +2,6 @@ import reactpy - react_cytoscapejs = reactpy.web.module_from_template( "react", "react-cytoscapejs", diff --git a/docs/source/reference/_examples/pigeon_maps.py b/docs/source/reference/_examples/pigeon_maps.py index 5241a0259..84f5ec4d3 100644 --- a/docs/source/reference/_examples/pigeon_maps.py +++ b/docs/source/reference/_examples/pigeon_maps.py @@ -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 @@ 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: remove_marker_anchor(anchor), + }, + key=str(anchor), ) - ) + for anchor in marker_anchor + ] return Map( { diff --git a/docs/source/reference/_examples/simple_dashboard.py b/docs/source/reference/_examples/simple_dashboard.py index 3d592f775..66913fc84 100644 --- a/docs/source/reference/_examples/simple_dashboard.py +++ b/docs/source/reference/_examples/simple_dashboard.py @@ -5,7 +5,6 @@ import reactpy from reactpy.widgets import Input - victory = reactpy.web.module_from_template( "react", "victory-line", diff --git a/docs/source/reference/_examples/snake_game.py b/docs/source/reference/_examples/snake_game.py index eecfea173..36916410e 100644 --- a/docs/source/reference/_examples/snake_game.py +++ b/docs/source/reference/_examples/snake_game.py @@ -107,7 +107,7 @@ async def animate(): 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] diff --git a/docs/source/reference/_examples/todo.py b/docs/source/reference/_examples/todo.py index f688f57be..104ea59a9 100644 --- a/docs/source/reference/_examples/todo.py +++ b/docs/source/reference/_examples/todo.py @@ -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 = [] diff --git a/docs/source/reference/_examples/use_reducer_counter.py b/docs/source/reference/_examples/use_reducer_counter.py index 44009422c..6f9581dfd 100644 --- a/docs/source/reference/_examples/use_reducer_counter.py +++ b/docs/source/reference/_examples/use_reducer_counter.py @@ -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 diff --git a/docs/source/reference/_examples/victory_chart.py b/docs/source/reference/_examples/victory_chart.py index 43bf2dbde..ce37c522f 100644 --- a/docs/source/reference/_examples/victory_chart.py +++ b/docs/source/reference/_examples/victory_chart.py @@ -1,6 +1,5 @@ import reactpy - victory = reactpy.web.module_from_template("react", "victory-bar", fallback="⌛") VictoryBar = reactpy.web.export(victory, "VictoryBar") diff --git a/pyproject.toml b/pyproject.toml index 8332710bf..b9be2a426 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,6 +67,10 @@ select = [ ignore = [ # TODO: turn this on later "N802", "N806", # allow TitleCase functions/variables + # Let Black deal with line-length + "E501", + # Allow unused args (useful for documenting what the parameter is for later) + "ARG001", "ARG005", # Allow non-abstract empty methods in abstract base classes "B027", # Allow boolean positional values in function calls, like `dict.get(... True)` @@ -90,3 +94,15 @@ 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", +] diff --git a/scripts/live_docs.py b/scripts/live_docs.py index 3f7d8b207..1160a0b52 100644 --- a/scripts/live_docs.py +++ b/scripts/live_docs.py @@ -17,7 +17,6 @@ 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"] = "" @@ -100,7 +99,7 @@ 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() diff --git a/scripts/one_example.py b/scripts/one_example.py index 079bd7a6a..5c0a89055 100644 --- a/scripts/one_example.py +++ b/scripts/one_example.py @@ -7,7 +7,6 @@ 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)) @@ -76,7 +75,7 @@ def _print_error(*args) -> None: def _print_available_options(): examples_by_path = {} - for i, name in enumerate(EXAMPLE_NAME_LIST): + for _i, name in enumerate(EXAMPLE_NAME_LIST): if "/" not in name: path = "" else: diff --git a/scripts/run_docs.py b/scripts/run_docs.py index 31c70a038..c947d8f58 100644 --- a/scripts/run_docs.py +++ b/scripts/run_docs.py @@ -1,14 +1,12 @@ 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__": diff --git a/src/py/reactpy/pyproject.toml b/src/py/reactpy/pyproject.toml index b7cafdc8b..7564854a1 100644 --- a/src/py/reactpy/pyproject.toml +++ b/src/py/reactpy/pyproject.toml @@ -116,70 +116,6 @@ artifacts = [ "app/dist/" ] -# --- Black ---------------------------------------------------------------------------- - -[tool.black] -target-version = ["py37"] -line-length = 88 -skip-string-normalization = true - -# --- Ruff ----------------------------------------------------------------------------- - -[tool.ruff] -target-version = "py37" -line-length = 88 -select = [ - "A", - "ARG", - "B", - "C", - "DTZ", - "E", - "EM", - "F", - "FBT", - "I", - "ICN", - "ISC", - "N", - "PLC", - "PLE", - "PLR", - "PLW", - "Q", - "RUF", - "S", - "T", - "TID", - "UP", - "W", - "YTT", -] -ignore = [ - # Allow non-abstract empty methods in abstract base classes - "B027", - # Allow boolean positional values in function calls, like `dict.get(... True)` - "FBT003", - # 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"] - # --- Coverage ------------------------------------------------------------------------- [tool.coverage.run] diff --git a/src/py/reactpy/reactpy/_console/ast_utils.py b/src/py/reactpy/reactpy/_console/ast_utils.py index 96c62610b..7a1fba3a7 100644 --- a/src/py/reactpy/reactpy/_console/ast_utils.py +++ b/src/py/reactpy/reactpy/_console/ast_utils.py @@ -1,13 +1,13 @@ from __future__ import annotations import ast -from collections.abc import Sequence +from collections.abc import Iterator, Sequence from dataclasses import dataclass from pathlib import Path from textwrap import indent from tokenize import COMMENT as COMMENT_TOKEN from tokenize import generate_tokens -from typing import Any, Iterator +from typing import Any import click @@ -42,7 +42,9 @@ def rewrite_changed_nodes( raise RuntimeError(msg) # check if an nodes to rewrite contain eachother, pick outermost nodes - current_outermost_node, *sorted_nodes_to_unparse = sorted(nodes_to_unparse, key=lambda n: n.lineno) + current_outermost_node, *sorted_nodes_to_unparse = sorted( + nodes_to_unparse, key=lambda n: n.lineno + ) outermost_nodes_to_unparse = [current_outermost_node] for node in sorted_nodes_to_unparse: if ( diff --git a/src/py/reactpy/reactpy/_warnings.py b/src/py/reactpy/reactpy/_warnings.py index 573a5ab70..dc6d2fa1f 100644 --- a/src/py/reactpy/reactpy/_warnings.py +++ b/src/py/reactpy/reactpy/_warnings.py @@ -1,7 +1,8 @@ +from collections.abc import Iterator from functools import wraps from inspect import currentframe from types import FrameType -from typing import TYPE_CHECKING, Any, Iterator +from typing import TYPE_CHECKING, Any from warnings import warn as _warn diff --git a/src/py/reactpy/reactpy/backend/_common.py b/src/py/reactpy/reactpy/backend/_common.py index 486f3aaec..57a7486e4 100644 --- a/src/py/reactpy/reactpy/backend/_common.py +++ b/src/py/reactpy/reactpy/backend/_common.py @@ -2,9 +2,10 @@ import asyncio import os +from collections.abc import Awaitable, Sequence from dataclasses import dataclass from pathlib import Path, PurePosixPath -from typing import Any, Awaitable, Sequence, cast +from typing import Any, cast import uvicorn from asgiref.typing import ASGIApplication diff --git a/src/py/reactpy/reactpy/backend/flask.py b/src/py/reactpy/reactpy/backend/flask.py index 5aecb05e9..7cef063bc 100644 --- a/src/py/reactpy/reactpy/backend/flask.py +++ b/src/py/reactpy/reactpy/backend/flask.py @@ -9,7 +9,7 @@ from queue import Queue as ThreadQueue from threading import Event as ThreadEvent from threading import Thread -from typing import Any, Callable, NamedTuple, NoReturn, Optional, cast +from typing import Any, Callable, NamedTuple, NoReturn, cast from flask import ( Blueprint, @@ -131,9 +131,7 @@ def use_connection() -> Connection[_FlaskCarrier]: conn = _use_connection() if not isinstance(conn.carrier, _FlaskCarrier): # pragma: no cover msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Flask server?" - raise TypeError( - msg - ) + raise TypeError(msg) return conn @@ -207,9 +205,9 @@ def _dispatch_in_thread( recv: Callable[[], Any | None], ) -> NoReturn: dispatch_thread_info_created = ThreadEvent() - dispatch_thread_info_ref: reactpy.Ref[ - _DispatcherThreadInfo | None - ] = reactpy.Ref(None) + dispatch_thread_info_ref: reactpy.Ref[_DispatcherThreadInfo | None] = reactpy.Ref( + None + ) @copy_current_request_context def run_dispatcher() -> None: diff --git a/src/py/reactpy/reactpy/backend/hooks.py b/src/py/reactpy/reactpy/backend/hooks.py index 69a57501b..b4d91ef55 100644 --- a/src/py/reactpy/reactpy/backend/hooks.py +++ b/src/py/reactpy/reactpy/backend/hooks.py @@ -1,6 +1,7 @@ from __future__ import annotations -from typing import Any, MutableMapping +from collections.abc import MutableMapping +from typing import Any from reactpy.backend.types import Connection, Location from reactpy.core.hooks import Context, create_context, use_context diff --git a/src/py/reactpy/reactpy/backend/sanic.py b/src/py/reactpy/reactpy/backend/sanic.py index 475942223..b1f4e2b60 100644 --- a/src/py/reactpy/reactpy/backend/sanic.py +++ b/src/py/reactpy/reactpy/backend/sanic.py @@ -4,7 +4,7 @@ import json import logging from dataclasses import dataclass -from typing import Any, Tuple +from typing import Any from urllib import parse as urllib_parse from uuid import uuid4 @@ -82,9 +82,7 @@ def use_connection() -> Connection[_SanicCarrier]: conn = _use_connection() if not isinstance(conn.carrier, _SanicCarrier): # pragma: no cover msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Sanic server?" - raise TypeError( - msg - ) + raise TypeError(msg) return conn diff --git a/src/py/reactpy/reactpy/backend/starlette.py b/src/py/reactpy/reactpy/backend/starlette.py index 0d8203fc7..1f4f0329f 100644 --- a/src/py/reactpy/reactpy/backend/starlette.py +++ b/src/py/reactpy/reactpy/backend/starlette.py @@ -3,8 +3,9 @@ import asyncio import json import logging +from collections.abc import Awaitable from dataclasses import dataclass -from typing import Any, Awaitable, Callable, Tuple +from typing import Any, Callable from starlette.applications import Starlette from starlette.middleware.cors import CORSMiddleware @@ -77,9 +78,7 @@ def use_connection() -> Connection[WebSocket]: conn = _use_connection() if not isinstance(conn.carrier, WebSocket): # pragma: no cover msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Flask server?" - raise TypeError( - msg - ) + raise TypeError(msg) return conn diff --git a/src/py/reactpy/reactpy/backend/tornado.py b/src/py/reactpy/reactpy/backend/tornado.py index 74e7a1343..b7b918b6f 100644 --- a/src/py/reactpy/reactpy/backend/tornado.py +++ b/src/py/reactpy/reactpy/backend/tornado.py @@ -100,9 +100,7 @@ def use_connection() -> Connection[HTTPServerRequest]: conn = _use_connection() if not isinstance(conn.carrier, HTTPServerRequest): # pragma: no cover msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Flask server?" - raise TypeError( - msg - ) + raise TypeError(msg) return conn diff --git a/src/py/reactpy/reactpy/backend/types.py b/src/py/reactpy/reactpy/backend/types.py index fa02c85f8..fbc4addc0 100644 --- a/src/py/reactpy/reactpy/backend/types.py +++ b/src/py/reactpy/reactpy/backend/types.py @@ -1,10 +1,9 @@ from __future__ import annotations import asyncio +from collections.abc import MutableMapping from dataclasses import dataclass -from typing import Any, Callable, Generic, MutableMapping, TypeVar - -from typing_extensions import Protocol, runtime_checkable +from typing import Any, Callable, Generic, Protocol, TypeVar, runtime_checkable from reactpy.core.types import RootComponentConstructor diff --git a/src/py/reactpy/reactpy/backend/utils.py b/src/py/reactpy/reactpy/backend/utils.py index 0dc08d703..ff5599718 100644 --- a/src/py/reactpy/reactpy/backend/utils.py +++ b/src/py/reactpy/reactpy/backend/utils.py @@ -3,9 +3,10 @@ import asyncio import logging import socket +from collections.abc import Iterator from contextlib import closing from importlib import import_module -from typing import Any, Iterator +from typing import Any from reactpy.backend.types import BackendImplementation from reactpy.types import RootComponentConstructor @@ -69,9 +70,7 @@ def find_available_port( else: return port msg = f"Host {host!r} has no available port in range {port_max}-{port_max}" - raise RuntimeError( - msg - ) + raise RuntimeError(msg) def all_implementations() -> Iterator[BackendImplementation[Any]]: diff --git a/src/py/reactpy/reactpy/core/component.py b/src/py/reactpy/reactpy/core/component.py index 2e702a539..f825aac71 100644 --- a/src/py/reactpy/reactpy/core/component.py +++ b/src/py/reactpy/reactpy/core/component.py @@ -2,7 +2,7 @@ import inspect from functools import wraps -from typing import Any, Callable, Dict, Optional, Tuple +from typing import Any, Callable from reactpy.core.types import ComponentType, VdomDict @@ -22,9 +22,7 @@ def component( inspect.Parameter.POSITIONAL_OR_KEYWORD, ): msg = f"Component render function {function} uses reserved parameter 'key'" - raise TypeError( - msg - ) + raise TypeError(msg) @wraps(function) def constructor(*args: Any, key: Any | None = None, **kwargs: Any) -> Component: diff --git a/src/py/reactpy/reactpy/core/events.py b/src/py/reactpy/reactpy/core/events.py index a9a63a54b..51fe8d0d3 100644 --- a/src/py/reactpy/reactpy/core/events.py +++ b/src/py/reactpy/reactpy/core/events.py @@ -1,10 +1,10 @@ from __future__ import annotations import asyncio -from typing import Any, Callable, Optional, Sequence, overload +from collections.abc import Sequence +from typing import Any, Callable, Literal, overload from anyio import create_task_group -from typing_extensions import Literal from reactpy.core.types import EventHandlerFunc, EventHandlerType @@ -192,9 +192,7 @@ def merge_event_handlers( or handler.target != target ): msg = "Cannot merge handlers - 'stop_propagation', 'prevent_default' or 'target' mistmatch." - raise ValueError( - msg - ) + raise ValueError(msg) return EventHandler( merge_event_handler_funcs([h.function for h in event_handlers]), diff --git a/src/py/reactpy/reactpy/core/hooks.py b/src/py/reactpy/reactpy/core/hooks.py index ba8166a70..532a52495 100644 --- a/src/py/reactpy/reactpy/core/hooks.py +++ b/src/py/reactpy/reactpy/core/hooks.py @@ -1,22 +1,22 @@ from __future__ import annotations import asyncio +from collections.abc import Awaitable, Sequence from logging import getLogger from types import FunctionType from typing import ( TYPE_CHECKING, Any, - Awaitable, Callable, Generic, NewType, - Sequence, + Protocol, TypeVar, cast, overload, ) -from typing_extensions import Protocol, TypeAlias +from typing_extensions import TypeAlias from reactpy.config import REACTPY_DEBUG_MODE from reactpy.core._thread_local import ThreadLocal @@ -165,7 +165,6 @@ def effect() -> None: if clean is not None: hook.add_effect(COMPONENT_WILL_UNMOUNT_EFFECT, clean) - return memoize(lambda: hook.add_effect(LAYOUT_DID_RENDER_EFFECT, effect)) if function is not None: diff --git a/src/py/reactpy/reactpy/core/layout.py b/src/py/reactpy/reactpy/core/layout.py index d7b17c1da..45e301999 100644 --- a/src/py/reactpy/reactpy/core/layout.py +++ b/src/py/reactpy/reactpy/core/layout.py @@ -3,16 +3,15 @@ import abc import asyncio from collections import Counter +from collections.abc import Iterator from contextlib import ExitStack from logging import getLogger from typing import ( Any, Callable, Generic, - Iterator, NamedTuple, NewType, - Optional, TypeVar, cast, ) @@ -81,7 +80,6 @@ async def __aexit__(self, *exc: Any) -> None: del self._root_life_cycle_state_id del self._model_states_by_life_cycle_state_id - async def deliver(self, event: LayoutEventMessage) -> None: """Dispatch an event to the targeted handler""" # It is possible for an element in the frontend to produce an event @@ -301,9 +299,7 @@ def _render_model_children( key_counter = Counter(item[2] for item in child_type_key_tuples) duplicate_keys = [key for key, count in key_counter.items() if count > 1] msg = f"Duplicate keys {duplicate_keys} at {new_state.patch_path or '/'!r}" - raise ValueError( - msg - ) + raise ValueError(msg) old_keys = set(old_state.children_by_key).difference(new_keys) if old_keys: @@ -393,9 +389,7 @@ def _render_model_children_without_old_state( key_counter = Counter(item[2] for item in child_type_key_tuples) duplicate_keys = [key for key, count in key_counter.items() if count > 1] msg = f"Duplicate keys {duplicate_keys} at {new_state.patch_path or '/'!r}" - raise ValueError( - msg - ) + raise ValueError(msg) new_state.model.current["children"] = [] for index, (child, child_type, key) in enumerate(child_type_key_tuples): diff --git a/src/py/reactpy/reactpy/core/serve.py b/src/py/reactpy/reactpy/core/serve.py index 3c2cea048..3cac0af6b 100644 --- a/src/py/reactpy/reactpy/core/serve.py +++ b/src/py/reactpy/reactpy/core/serve.py @@ -1,8 +1,9 @@ from __future__ import annotations from asyncio import create_task +from collections.abc import Awaitable from logging import getLogger -from typing import Awaitable, Callable +from typing import Callable from anyio import create_task_group diff --git a/src/py/reactpy/reactpy/core/types.py b/src/py/reactpy/reactpy/core/types.py index 957057d04..3462f4d27 100644 --- a/src/py/reactpy/reactpy/core/types.py +++ b/src/py/reactpy/reactpy/core/types.py @@ -2,21 +2,22 @@ import sys from collections import namedtuple -from collections.abc import Sequence +from collections.abc import Mapping, Sequence from types import TracebackType from typing import ( TYPE_CHECKING, Any, Callable, Generic, - Mapping, + Literal, NamedTuple, - Type, + Protocol, TypeVar, overload, + runtime_checkable, ) -from typing_extensions import Literal, Protocol, TypeAlias, TypedDict, runtime_checkable +from typing_extensions import TypeAlias, TypedDict _Type = TypeVar("_Type") diff --git a/src/py/reactpy/reactpy/core/vdom.py b/src/py/reactpy/reactpy/core/vdom.py index 8441bff4e..bd2c65e14 100644 --- a/src/py/reactpy/reactpy/core/vdom.py +++ b/src/py/reactpy/reactpy/core/vdom.py @@ -1,11 +1,11 @@ from __future__ import annotations import logging +from collections.abc import Mapping, Sequence from functools import wraps -from typing import Any, Mapping, Sequence, cast, overload +from typing import Any, Protocol, cast, overload from fastjsonschema import compile as compile_json_schema -from typing_extensions import Protocol from reactpy._warnings import warn from reactpy.config import REACTPY_DEBUG_MODE diff --git a/src/py/reactpy/reactpy/html.py b/src/py/reactpy/reactpy/html.py index 1efca8a7b..0387b3135 100644 --- a/src/py/reactpy/reactpy/html.py +++ b/src/py/reactpy/reactpy/html.py @@ -158,7 +158,7 @@ from __future__ import annotations -from typing import Sequence +from collections.abc import Sequence from reactpy.core.types import ( EventHandlerDict, @@ -382,14 +382,14 @@ def _fragment( area = make_vdom_constructor("area", allow_children=False) audio = make_vdom_constructor("audio") img = make_vdom_constructor("img", allow_children=False) -map = make_vdom_constructor("map") +map = make_vdom_constructor("map") # noqa: A001 track = make_vdom_constructor("track") video = make_vdom_constructor("video") # Embedded content embed = make_vdom_constructor("embed", allow_children=False) iframe = make_vdom_constructor("iframe", allow_children=False) -object = make_vdom_constructor("object") +object = make_vdom_constructor("object") # noqa: A001 param = make_vdom_constructor("param") picture = make_vdom_constructor("picture") portal = make_vdom_constructor("portal", allow_children=False) @@ -522,7 +522,7 @@ def _script( button = make_vdom_constructor("button") fieldset = make_vdom_constructor("fieldset") form = make_vdom_constructor("form") -input = make_vdom_constructor("input", allow_children=False) +input = make_vdom_constructor("input", allow_children=False) # noqa: A001 label = make_vdom_constructor("label") legend = make_vdom_constructor("legend") meter = make_vdom_constructor("meter") diff --git a/src/py/reactpy/reactpy/svg.py b/src/py/reactpy/reactpy/svg.py index cf3e75577..ebfe58ee6 100644 --- a/src/py/reactpy/reactpy/svg.py +++ b/src/py/reactpy/reactpy/svg.py @@ -106,7 +106,7 @@ fe_spot_light = make_vdom_constructor("feSpotLight", allow_children=False) fe_tile = make_vdom_constructor("feTile", allow_children=False) fe_turbulence = make_vdom_constructor("feTurbulence", allow_children=False) -filter = make_vdom_constructor("filter", allow_children=False) +filter = make_vdom_constructor("filter", allow_children=False) # noqa: A001 foreign_object = make_vdom_constructor("foreignObject", allow_children=False) g = make_vdom_constructor("g") hatch = make_vdom_constructor("hatch", allow_children=False) @@ -125,7 +125,7 @@ radial_gradient = make_vdom_constructor("radialGradient", allow_children=False) rect = make_vdom_constructor("rect", allow_children=False) script = make_vdom_constructor("script", allow_children=False) -set = make_vdom_constructor("set", allow_children=False) +set = make_vdom_constructor("set", allow_children=False) # noqa: A001 stop = make_vdom_constructor("stop", allow_children=False) style = make_vdom_constructor("style", allow_children=False) svg = make_vdom_constructor("svg") diff --git a/src/py/reactpy/reactpy/testing/backend.py b/src/py/reactpy/reactpy/testing/backend.py index 23d908291..3a1ade140 100644 --- a/src/py/reactpy/reactpy/testing/backend.py +++ b/src/py/reactpy/reactpy/testing/backend.py @@ -4,7 +4,7 @@ import logging from contextlib import AsyncExitStack from types import TracebackType -from typing import Any, Callable, Optional, Tuple, Type, Union +from typing import Any, Callable from urllib.parse import urlencode, urlunparse from reactpy.backend import default as default_server @@ -57,9 +57,7 @@ def __init__( if app is not None: if implementation is None: msg = "If an application instance its corresponding server implementation must be provided too." - raise ValueError( - msg - ) + raise ValueError(msg) self._app = app self.implementation = implementation or default_server @@ -158,7 +156,6 @@ async def __aexit__( raise LogAssertionError(msg) from logged_errors[0] - _MountFunc = Callable[["Callable[[], Any] | None"], None] @@ -221,7 +218,6 @@ def swap(constructor: Callable[[], Any] | None) -> None: for set_constructor in set_constructor_callbacks: set_constructor(constructor) - else: @component diff --git a/src/py/reactpy/reactpy/testing/common.py b/src/py/reactpy/reactpy/testing/common.py index dff7a90c3..525808a41 100644 --- a/src/py/reactpy/reactpy/testing/common.py +++ b/src/py/reactpy/reactpy/testing/common.py @@ -4,8 +4,9 @@ import inspect import shutil import time +from collections.abc import Awaitable from functools import wraps -from typing import Any, Awaitable, Callable, Generic, Optional, TypeVar, cast +from typing import Any, Callable, Generic, TypeVar, cast from uuid import uuid4 from weakref import ref @@ -67,9 +68,7 @@ async def until( break elif (time.time() - started_at) > timeout: # pragma: no cover msg = f"Expected {description} after {timeout} seconds - last value was {result!r}" - raise TimeoutError( - msg - ) + raise TimeoutError(msg) async def until_is( self, diff --git a/src/py/reactpy/reactpy/testing/logs.py b/src/py/reactpy/reactpy/testing/logs.py index 0a0bebf7a..c240eae18 100644 --- a/src/py/reactpy/reactpy/testing/logs.py +++ b/src/py/reactpy/reactpy/testing/logs.py @@ -2,9 +2,10 @@ import logging import re +from collections.abc import Iterator from contextlib import contextmanager from traceback import format_exception -from typing import Any, Iterator, NoReturn +from typing import Any, NoReturn from reactpy.logging import ROOT_LOGGER diff --git a/src/py/reactpy/reactpy/utils.py b/src/py/reactpy/reactpy/utils.py index 5f0cac906..c52723031 100644 --- a/src/py/reactpy/reactpy/utils.py +++ b/src/py/reactpy/reactpy/utils.py @@ -1,8 +1,9 @@ from __future__ import annotations import re +from collections.abc import Iterable from itertools import chain -from typing import Any, Callable, Generic, Iterable, TypeVar, cast +from typing import Any, Callable, Generic, TypeVar, cast from lxml import etree from lxml.html import fromstring, tostring @@ -113,9 +114,7 @@ def html_to_vdom( if not strict: raise e # pragma: no cover msg = "An error has occurred while parsing the HTML.\n\nThis HTML may be malformatted, or may not perfectly adhere to HTML5.\nIf you believe the exception above was due to something intentional, you can disable the strict parameter on html_to_vdom().\nOtherwise, repair your broken HTML and try again." - raise HTMLParseError( - msg - ) from e + raise HTMLParseError(msg) from e return _etree_to_vdom(root_node, transforms) @@ -139,9 +138,7 @@ def _etree_to_vdom( """ if not isinstance(node, etree._Element): # pragma: no cover msg = f"Expected node to be a etree._Element, not {type(node).__name__}" - raise TypeError( - msg - ) + raise TypeError(msg) # Recursively call _etree_to_vdom() on all children children = _generate_vdom_children(node, transforms) diff --git a/src/py/reactpy/reactpy/web/utils.py b/src/py/reactpy/reactpy/web/utils.py index 4bfd21351..17bd67f3b 100644 --- a/src/py/reactpy/reactpy/web/utils.py +++ b/src/py/reactpy/reactpy/web/utils.py @@ -1,7 +1,6 @@ import logging import re from pathlib import Path, PurePosixPath -from typing import Set, Tuple from urllib.parse import urlparse import requests @@ -21,7 +20,7 @@ def resolve_module_exports_from_file( file: Path, max_depth: int, is_re_export: bool = False, -) -> Set[str]: +) -> set[str]: if max_depth == 0: logger.warning(f"Did not resolve all exports for {file} - max depth reached") return set() @@ -51,7 +50,7 @@ def resolve_module_exports_from_url( url: str, max_depth: int, is_re_export: bool = False, -) -> Set[str]: +) -> set[str]: if max_depth == 0: logger.warning(f"Did not resolve all exports for {url} - max depth reached") return set() @@ -78,9 +77,9 @@ def resolve_module_exports_from_url( def resolve_module_exports_from_source( content: str, exclude_default: bool -) -> Tuple[Set[str], Set[str]]: - names: Set[str] = set() - references: Set[str] = set() +) -> tuple[set[str], set[str]]: + names: set[str] = set() + references: set[str] = set() if _JS_DEFAULT_EXPORT_PATTERN.search(content): names.add("default") diff --git a/src/py/reactpy/reactpy/widgets.py b/src/py/reactpy/reactpy/widgets.py index 593f9de00..e139c1281 100644 --- a/src/py/reactpy/reactpy/widgets.py +++ b/src/py/reactpy/reactpy/widgets.py @@ -1,9 +1,8 @@ from __future__ import annotations from base64 import b64encode -from typing import TYPE_CHECKING, Any, Callable, Sequence, Tuple, TypeVar, Union - -from typing_extensions import Protocol +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any, Callable, Protocol, TypeVar import reactpy from reactpy import html @@ -21,7 +20,7 @@ def image( The source value will automatically be encoded to base64 """ if format == "svg": - format = "svg+xml" + format = "svg+xml" # noqa: A001 if isinstance(value, str): bytes_value = value.encode() diff --git a/src/py/reactpy/setup.py b/src/py/reactpy/setup.py index 6531fe85e..37b6550a3 100644 --- a/src/py/reactpy/setup.py +++ b/src/py/reactpy/setup.py @@ -1,4 +1,3 @@ - import pipes import shutil import subprocess @@ -127,9 +126,7 @@ def list2cmdline(cmd_list): extra_requirements["all"].append(line) elif line: msg = f"No '# extra=' header before requirements in {extra_requirements_path}" - raise ValueError( - msg - ) + raise ValueError(msg) package["extras_require"] = extra_requirements diff --git a/src/py/reactpy/tests/test_backend/test_all.py b/src/py/reactpy/tests/test_backend/test_all.py index b9ef10c41..796f096db 100644 --- a/src/py/reactpy/tests/test_backend/test_all.py +++ b/src/py/reactpy/tests/test_backend/test_all.py @@ -1,4 +1,4 @@ -from typing import MutableMapping +from collections.abc import MutableMapping import pytest diff --git a/src/py/reactpy/tests/test_core/test_layout.py b/src/py/reactpy/tests/test_core/test_layout.py index 0510e1e3c..93a796ae3 100644 --- a/src/py/reactpy/tests/test_core/test_layout.py +++ b/src/py/reactpy/tests/test_core/test_layout.py @@ -782,7 +782,6 @@ def Parent(): @reactpy.component @child_hook.capture def Child(state): - reactpy.hooks.use_effect(lambda: lambda: print("unmount", state)) return reactpy.html.div(state) with assert_reactpy_did_log( diff --git a/src/py/reactpy/tests/test_core/test_serve.py b/src/py/reactpy/tests/test_core/test_serve.py index 02d54875d..fd63f39c4 100644 --- a/src/py/reactpy/tests/test_core/test_serve.py +++ b/src/py/reactpy/tests/test_core/test_serve.py @@ -1,5 +1,6 @@ import asyncio -from typing import Any, Sequence +from collections.abc import Sequence +from typing import Any from jsonpointer import set_pointer diff --git a/src/py/reactpy/tests/tooling/loop.py b/src/py/reactpy/tests/tooling/loop.py index f130d080f..f9e100981 100644 --- a/src/py/reactpy/tests/tooling/loop.py +++ b/src/py/reactpy/tests/tooling/loop.py @@ -1,10 +1,9 @@ import asyncio -import sys import threading import time from asyncio import wait_for +from collections.abc import Iterator from contextlib import contextmanager -from typing import Iterator from reactpy.config import REACTPY_TESTING_DEFAULT_TIMEOUT @@ -32,14 +31,12 @@ def open_event_loop(as_current: bool = True) -> Iterator[asyncio.AbstractEventLo REACTPY_TESTING_DEFAULT_TIMEOUT.current, ) ) - if sys.version_info >= (3, 9): - # shutdown_default_executor only available in Python 3.9+ - loop.run_until_complete( - wait_for( - loop.shutdown_default_executor(), - REACTPY_TESTING_DEFAULT_TIMEOUT.current, - ) + loop.run_until_complete( + wait_for( + loop.shutdown_default_executor(), + REACTPY_TESTING_DEFAULT_TIMEOUT.current, ) + ) finally: if as_current: asyncio.set_event_loop(None) @@ -47,9 +44,7 @@ def open_event_loop(as_current: bool = True) -> Iterator[asyncio.AbstractEventLo while loop.is_running(): if (time.time() - start) > REACTPY_TESTING_DEFAULT_TIMEOUT.current: msg = f"Failed to stop loop after {REACTPY_TESTING_DEFAULT_TIMEOUT.current} seconds" - raise TimeoutError( - msg - ) + raise TimeoutError(msg) time.sleep(0.1) loop.close() @@ -79,11 +74,9 @@ def one_task_finished(future): REACTPY_TESTING_DEFAULT_TIMEOUT.current, ) ) - else: - # user was responsible for cancelling all tasks - if not done.wait(timeout=3): - msg = "Could not stop event loop in time" - raise TimeoutError(msg) + elif not done.wait(timeout=3): # user was responsible for cancelling all tasks + msg = "Could not stop event loop in time" + raise TimeoutError(msg) for task in to_cancel: if task.cancelled(): From 8054b056ab92811cc3f81362014b916aaed5577b Mon Sep 17 00:00:00 2001 From: rmorshea Date: Wed, 31 May 2023 00:06:18 -0600 Subject: [PATCH 05/26] get workflows to run --- .github/workflows/.hatch-run.yml | 4 ++-- .github/workflows/publish.yml | 4 ++-- .github/workflows/test.yml | 16 +++++++--------- pyproject.toml | 8 ++++---- tasks.py | 5 +++++ 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/.github/workflows/.hatch-run.yml b/.github/workflows/.hatch-run.yml index 9d715908c..fa13dad62 100644 --- a/.github/workflows/.hatch-run.yml +++ b/.github/workflows/.hatch-run.yml @@ -46,8 +46,8 @@ 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 + - name: Run Scripts env: NODE_AUTH_TOKEN: ${{ secrets.node-auth-token }} PYPI_USERNAME: ${{ secrets.pypi-username }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 18090e4b0..d8af63e66 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,10 +9,10 @@ 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" secrets: node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }} pypi-username: ${{ secrets.PYPI_USERNAME }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 33b373f98..9de421fe4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,23 +15,21 @@ jobs: uses: ./.github/workflows/.hatch-run.yml with: job-name: "python-{0}" - hatch-run: "-t check-python" - nox-session-args: "--pytest --maxfail=3 --reruns 3" + hatch-run: "check-py" python-environments: - uses: ./.github/workflows/.nox-session.yml + uses: ./.github/workflows/.hatch-run.yml with: job-name: "python-{0} {1}" - nox-args: "-s check-python-tests" - nox-session-args: "--no-cov --pytest --maxfail=3 --reruns 3" + hatch-run: "check-py --no-cov" runs-on-array: '["ubuntu-latest", "macos-latest", "windows-latest"]' python-version-array: '["3.9", "3.10", "3.11"]' docs: - uses: ./.github/workflows/.nox-session.yml + uses: ./.github/workflows/.hatch-run.yml with: job-name: "python-{0}" - nox-args: "-s check-docs" + hatch-run: "test-docs" javascript: - uses: ./.github/workflows/.nox-session.yml + uses: ./.github/workflows/.hatch-run.yml with: job-name: "{1}" - nox-args: "-t check-javascript" + hatch-run: "test-js lint-js" diff --git a/pyproject.toml b/pyproject.toml index b9be2a426..3387d8a8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,15 +14,15 @@ post-install-commands = ["invoke env"] [tool.hatch.envs.default.scripts] publish = "invoke publish {args}" -checks = ["lint", "test"] -lint = ["lint-py {args}", "lint-js {args}"] -test = ["test-py", "test-js"] +check-py = "invoke test-py {args} lint-py" +check-js = "invoke test-js lint-js" lint-py = "invoke lint-py {args}" lint-js = "invoke lint-js {args}" test-py = "invoke test-py {args}" -test-js = "invoke test-js {args}" +test-js = "invoke test-js" +test-docs = "invoke test-docs" # --- Black ---------------------------------------------------------------------------- diff --git a/tasks.py b/tasks.py index dc0d444c0..32781f4ec 100644 --- a/tasks.py +++ b/tasks.py @@ -94,6 +94,11 @@ def test_js(context: Context): in_js(context, "npm run check:tests") +@task +def test_docs(context: Context): + raise Exit("Not implemented") + + @task def publish(context: Context, dry_run: str = ""): """Publish packages that have been tagged for release in the current commit From 3b181b2bf76fe491729ae4ebd8a6d042855b3934 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Wed, 31 May 2023 01:08:27 -0600 Subject: [PATCH 06/26] split up script runs --- .github/workflows/test.yml | 24 +++++++++++++++++------- pyproject.toml | 4 ---- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9de421fe4..db10eb599 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,25 +11,35 @@ on: - cron: "0 0 * * 0" jobs: - python-exhaustive: + test-py-cov: uses: ./.github/workflows/.hatch-run.yml with: job-name: "python-{0}" - hatch-run: "check-py" - python-environments: + 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: "check-py --no-cov" + hatch-run: "test-py --no-cov" runs-on-array: '["ubuntu-latest", "macos-latest", "windows-latest"]' python-version-array: '["3.9", "3.10", "3.11"]' - docs: + test-docs: uses: ./.github/workflows/.hatch-run.yml with: job-name: "python-{0}" hatch-run: "test-docs" - javascript: + 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: "test-js lint-js" + hatch-run: "lint-js" diff --git a/pyproject.toml b/pyproject.toml index 3387d8a8c..a9b43a93c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,9 +14,6 @@ post-install-commands = ["invoke env"] [tool.hatch.envs.default.scripts] publish = "invoke publish {args}" -check-py = "invoke test-py {args} lint-py" -check-js = "invoke test-js lint-js" - lint-py = "invoke lint-py {args}" lint-js = "invoke lint-js {args}" @@ -29,7 +26,6 @@ test-docs = "invoke test-docs" [tool.black] target-version = ["py39"] line-length = 88 -skip-string-normalization = true # --- Ruff ----------------------------------------------------------------------------- From 0dea69b745cc17d045afffe55009879934c6ab1f Mon Sep 17 00:00:00 2001 From: rmorshea Date: Wed, 31 May 2023 01:09:44 -0600 Subject: [PATCH 07/26] rename to check --- .github/workflows/{test.yml => check.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{test.yml => check.yml} (98%) diff --git a/.github/workflows/test.yml b/.github/workflows/check.yml similarity index 98% rename from .github/workflows/test.yml rename to .github/workflows/check.yml index db10eb599..af768579c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/check.yml @@ -1,4 +1,4 @@ -name: test +name: check on: push: From 731248b9bd98f3a7fbcc836ccbeac37a8842d8c3 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Wed, 31 May 2023 01:10:59 -0600 Subject: [PATCH 08/26] change matrix order --- .github/workflows/.hatch-run.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/.hatch-run.yml b/.github/workflows/.hatch-run.yml index fa13dad62..e64ab3204 100644 --- a/.github/workflows/.hatch-run.yml +++ b/.github/workflows/.hatch-run.yml @@ -30,8 +30,8 @@ jobs: 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 From 8f0ffae54641c1da311bb7b7a3ba4bd1627b2dc0 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Wed, 31 May 2023 02:07:48 -0600 Subject: [PATCH 09/26] make ruff happy --- docs/source/_exts/build_custom_js.py | 4 +- .../_examples/filterable_list/main.py | 5 +- .../source/reference/_examples/pigeon_maps.py | 2 +- pyproject.toml | 13 +- scripts/live_docs.py | 4 +- scripts/run_docs.py | 4 +- src/py/reactpy/reactpy/_console/ast_utils.py | 2 +- src/py/reactpy/reactpy/_warnings.py | 2 +- src/py/reactpy/reactpy/backend/default.py | 4 +- src/py/reactpy/reactpy/backend/flask.py | 4 +- src/py/reactpy/reactpy/core/_f_back.py | 3 +- src/py/reactpy/reactpy/core/hooks.py | 20 +- src/py/reactpy/reactpy/core/layout.py | 30 +-- src/py/reactpy/reactpy/core/serve.py | 10 +- src/py/reactpy/reactpy/testing/common.py | 3 +- src/py/reactpy/reactpy/utils.py | 7 +- src/py/reactpy/reactpy/web/utils.py | 2 +- src/py/reactpy/setup.py | 201 ------------------ src/py/reactpy/tests/test__option.py | 2 +- src/py/reactpy/tests/test_core/test_hooks.py | 43 +--- src/py/reactpy/tests/test_core/test_layout.py | 9 +- src/py/reactpy/tests/test_core/test_serve.py | 4 +- src/py/reactpy/tests/test_utils.py | 2 +- tasks.py | 3 +- 24 files changed, 82 insertions(+), 301 deletions(-) delete mode 100644 src/py/reactpy/setup.py diff --git a/docs/source/_exts/build_custom_js.py b/docs/source/_exts/build_custom_js.py index 08014a067..97857ba74 100644 --- a/docs/source/_exts/build_custom_js.py +++ b/docs/source/_exts/build_custom_js.py @@ -8,5 +8,5 @@ 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 diff --git a/docs/source/guides/managing-state/sharing-component-state/_examples/filterable_list/main.py b/docs/source/guides/managing-state/sharing-component-state/_examples/filterable_list/main.py index 4336f4812..ca68aedcb 100644 --- a/docs/source/guides/managing-state/sharing-component-state/_examples/filterable_list/main.py +++ b/docs/source/guides/managing-state/sharing-component-state/_examples/filterable_list/main.py @@ -34,9 +34,8 @@ def Table(value, set_value): tr = html.tr(name, descr, value) if not value: rows.append(tr) - else: - if value.lower() in row["name"].lower(): - rows.append(tr) + elif 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 diff --git a/docs/source/reference/_examples/pigeon_maps.py b/docs/source/reference/_examples/pigeon_maps.py index 84f5ec4d3..1ddf04fdc 100644 --- a/docs/source/reference/_examples/pigeon_maps.py +++ b/docs/source/reference/_examples/pigeon_maps.py @@ -12,7 +12,7 @@ def MapWithMarkers(): Marker( { "anchor": anchor, - "onClick": lambda: remove_marker_anchor(anchor), + "onClick": lambda event, a=anchor: remove_marker_anchor(a), }, key=str(anchor), ) diff --git a/pyproject.toml b/pyproject.toml index a9b43a93c..0bbf87e16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,8 @@ select = [ "C", "DTZ", "E", - "EM", + # error message linting is overkill + # "EM", "F", # TODO: turn this on later # "FBT", @@ -63,14 +64,22 @@ select = [ 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", "ARG005", + "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 diff --git a/scripts/live_docs.py b/scripts/live_docs.py index 1160a0b52..d1bc00f0c 100644 --- a/scripts/live_docs.py +++ b/scripts/live_docs.py @@ -86,8 +86,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: diff --git a/scripts/run_docs.py b/scripts/run_docs.py index c947d8f58..8d42d81ec 100644 --- a/scripts/run_docs.py +++ b/scripts/run_docs.py @@ -5,13 +5,13 @@ # to import docs sys.path.insert(0, os.getcwd()) -from docs.app import make_app +from docs.app import make_app # noqa: E402 app = make_app() if __name__ == "__main__": app.run( - host="0.0.0.0", + 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"))), diff --git a/src/py/reactpy/reactpy/_console/ast_utils.py b/src/py/reactpy/reactpy/_console/ast_utils.py index 7a1fba3a7..0383a913f 100644 --- a/src/py/reactpy/reactpy/_console/ast_utils.py +++ b/src/py/reactpy/reactpy/_console/ast_utils.py @@ -129,7 +129,7 @@ def find_element_constructor_usages( node.args.insert(1, maybe_attr_dict_node) else: continue - elif len(node.args) >= 2: + elif len(node.args) >= 2: # noqa: PLR2004 maybe_attr_dict_node = node.args[1] elif hasattr(html, name): if len(node.args) == 0: diff --git a/src/py/reactpy/reactpy/_warnings.py b/src/py/reactpy/reactpy/_warnings.py index dc6d2fa1f..678b5f868 100644 --- a/src/py/reactpy/reactpy/_warnings.py +++ b/src/py/reactpy/reactpy/_warnings.py @@ -13,7 +13,7 @@ def warn(*args: Any, **kwargs: Any) -> Any: if TYPE_CHECKING: - warn = _warn + warn = _warn # noqa F811 def _frame_depth_in_module() -> int: diff --git a/src/py/reactpy/reactpy/backend/default.py b/src/py/reactpy/reactpy/backend/default.py index 6177f5b64..2e6d754d0 100644 --- a/src/py/reactpy/reactpy/backend/default.py +++ b/src/py/reactpy/reactpy/backend/default.py @@ -50,7 +50,7 @@ async def serve_development_app( def _default_implementation() -> BackendImplementation[Any]: """Get the first available server implementation""" - global _DEFAULT_IMPLEMENTATION + global _DEFAULT_IMPLEMENTATION # noqa PLW0603 if _DEFAULT_IMPLEMENTATION is not None: return _DEFAULT_IMPLEMENTATION @@ -60,7 +60,7 @@ def _default_implementation() -> BackendImplementation[Any]: except StopIteration: # pragma: no cover logger.debug("Backend implementation import failed", exc_info=exc_info()) msg = "No built-in server implementation installed." - raise RuntimeError(msg) + raise RuntimeError(msg) from None else: _DEFAULT_IMPLEMENTATION = implementation return implementation diff --git a/src/py/reactpy/reactpy/backend/flask.py b/src/py/reactpy/reactpy/backend/flask.py index 7cef063bc..e582bc9b5 100644 --- a/src/py/reactpy/reactpy/backend/flask.py +++ b/src/py/reactpy/reactpy/backend/flask.py @@ -256,7 +256,9 @@ async def main() -> None: dispatch_thread_info_created.wait() dispatch_thread_info = cast(_DispatcherThreadInfo, dispatch_thread_info_ref.current) - assert dispatch_thread_info is not None + + if dispatch_thread_info is None: + raise RuntimeError("Failed to create dispatcher thread") stop = ThreadEvent() diff --git a/src/py/reactpy/reactpy/core/_f_back.py b/src/py/reactpy/reactpy/core/_f_back.py index 81e66a4f1..8a824bcd4 100644 --- a/src/py/reactpy/reactpy/core/_f_back.py +++ b/src/py/reactpy/reactpy/core/_f_back.py @@ -9,7 +9,8 @@ def f_module_name(index: int = 0) -> str: if frame is None: return "" # pragma: no cover name = frame.f_globals.get("__name__", "") - assert isinstance(name, str), "Expected module name to be a string" + if not isinstance(name, str): + raise TypeError("Expected module name to be a string") return name diff --git a/src/py/reactpy/reactpy/core/hooks.py b/src/py/reactpy/reactpy/core/hooks.py index 532a52495..de97fc9bd 100644 --- a/src/py/reactpy/reactpy/core/hooks.py +++ b/src/py/reactpy/reactpy/core/hooks.py @@ -246,13 +246,13 @@ def use_context(context: Context[_Type]) -> _Type: provider = hook.get_context_provider(context) if provider is None: - # force type checker to realize this is just a normal function - assert isinstance(context, FunctionType), f"{context} is not a Context" - # __kwdefault__ can be None if no kwarg only parameters exist - assert context.__kwdefaults__ is not None, f"{context} has no 'value' kwarg" - # lastly check that 'value' kwarg exists - assert "value" in context.__kwdefaults__, f"{context} has no 'value' kwarg" - # then we can safely access the context's default value + # same assertions but with normal exceptions + if not isinstance(context, FunctionType): + raise TypeError(f"{context} is not a Context") + if context.__kwdefaults__ is None: + raise TypeError(f"{context} has no 'value' kwarg") + if "value" not in context.__kwdefaults__: + raise TypeError(f"{context} has no 'value' kwarg") return cast(_Type, context.__kwdefaults__["value"]) return provider._value @@ -455,7 +455,7 @@ class _Memo(Generic[_Type]): def empty(self) -> bool: try: - self.value + self.value # noqa: B018 except AttributeError: return True else: @@ -708,8 +708,8 @@ def set_current(self) -> None: def unset_current(self) -> None: """Unset this hook as the active hook in this thread""" - # this assertion should never fail - primarilly useful for debug - assert _hook_stack.get().pop() is self + if _hook_stack.get().pop() is not self: + raise RuntimeError("Hook stack is in an invalid state") def _schedule_render(self) -> None: try: diff --git a/src/py/reactpy/reactpy/core/layout.py b/src/py/reactpy/reactpy/core/layout.py index 45e301999..be1a6d61b 100644 --- a/src/py/reactpy/reactpy/core/layout.py +++ b/src/py/reactpy/reactpy/core/layout.py @@ -318,21 +318,20 @@ def _render_model_children( index, key, ) + elif old_child_state.is_component_state: + self._unmount_model_states([old_child_state]) + new_child_state = _make_element_model_state( + new_state, + index, + key, + ) + old_child_state = None else: - if old_child_state.is_component_state: - self._unmount_model_states([old_child_state]) - new_child_state = _make_element_model_state( - new_state, - index, - key, - ) - old_child_state = None - else: - new_child_state = _update_element_model_state( - old_child_state, - new_state, - index, - ) + new_child_state = _update_element_model_state( + old_child_state, + new_state, + index, + ) self._render_model(exit_stack, old_child_state, new_child_state, child) new_state.append_child(new_child_state.model.current) new_state.children_by_key[key] = new_child_state @@ -595,7 +594,8 @@ def is_component_state(self) -> bool: @property def parent(self) -> _ModelState: parent = self._parent_ref() - assert parent is not None, "detached model state" + if parent is None: + raise RuntimeError("detached model state") return parent def append_child(self, child: Any) -> None: diff --git a/src/py/reactpy/reactpy/core/serve.py b/src/py/reactpy/reactpy/core/serve.py index 3cac0af6b..61a7e4ce6 100644 --- a/src/py/reactpy/reactpy/core/serve.py +++ b/src/py/reactpy/reactpy/core/serve.py @@ -1,11 +1,11 @@ from __future__ import annotations -from asyncio import create_task from collections.abc import Awaitable from logging import getLogger from typing import Callable from anyio import create_task_group +from anyio.abc import TaskGroup from reactpy.core.types import LayoutEventMessage, LayoutType, LayoutUpdateMessage @@ -40,7 +40,7 @@ async def serve_layout( try: async with create_task_group() as task_group: task_group.start_soon(_single_outgoing_loop, layout, send) - task_group.start_soon(_single_incoming_loop, layout, recv) + task_group.start_soon(_single_incoming_loop, task_group, layout, recv) except Stop: logger.info(f"Stopped serving {layout}") @@ -53,9 +53,11 @@ async def _single_outgoing_loop( async def _single_incoming_loop( - layout: LayoutType[LayoutUpdateMessage, LayoutEventMessage], recv: RecvCoroutine + task_group: TaskGroup, + layout: LayoutType[LayoutUpdateMessage, LayoutEventMessage], + recv: RecvCoroutine, ) -> None: while True: # We need to fire and forget here so that we avoid waiting on the completion # of this event handler before receiving and running the next one. - create_task(layout.deliver(await recv())) + task_group.start_soon(layout.deliver, await recv()) diff --git a/src/py/reactpy/reactpy/testing/common.py b/src/py/reactpy/reactpy/testing/common.py index 525808a41..0c718036a 100644 --- a/src/py/reactpy/reactpy/testing/common.py +++ b/src/py/reactpy/reactpy/testing/common.py @@ -141,7 +141,8 @@ def capture(self, render_function: Callable[..., Any]) -> Callable[..., Any]: @wraps(render_function) def wrapper(*args: Any, **kwargs: Any) -> Any: self = self_ref() - assert self is not None, "Hook catcher has been garbage collected" + if self is None: + raise RuntimeError("Hook catcher has been garbage collected") hook = current_hook() if self.index_by_kwarg is not None: diff --git a/src/py/reactpy/reactpy/utils.py b/src/py/reactpy/reactpy/utils.py index c52723031..3da8f4d3f 100644 --- a/src/py/reactpy/reactpy/utils.py +++ b/src/py/reactpy/reactpy/utils.py @@ -214,7 +214,7 @@ def _mutate_vdom(vdom: VdomDict) -> None: and isinstance(vdom["attributes"]["style"], str) ): # Convince type checker that it's safe to mutate attributes - assert isinstance(vdom["attributes"], dict) + assert isinstance(vdom["attributes"], dict) # noqa: S101 # Convert style attribute from str -> dict with camelCase keys vdom["attributes"]["style"] = { @@ -291,9 +291,8 @@ def _vdom_attr_to_html_str(key: str, value: Any) -> tuple[str, str]: ): key = _CAMEL_CASE_SUB_PATTERN.sub("-", key) - assert not callable( - value - ), f"Could not convert callable attribute {key}={value} to HTML" + if callable(value): + raise TypeError(f"Cannot convert callable attribute {key}={value} to HTML") # Again, we lower the attribute name only to normalize - HTML is case-insensitive: # http://w3c.github.io/html-reference/documents.html#case-insensitivity diff --git a/src/py/reactpy/reactpy/web/utils.py b/src/py/reactpy/reactpy/web/utils.py index 17bd67f3b..cf8b8638b 100644 --- a/src/py/reactpy/reactpy/web/utils.py +++ b/src/py/reactpy/reactpy/web/utils.py @@ -56,7 +56,7 @@ def resolve_module_exports_from_url( return set() try: - text = requests.get(url).text + text = requests.get(url, timeout=5).text except requests.exceptions.ConnectionError as error: reason = "" if error is None else " - {error.errno}" logger.warning("Did not resolve exports for url " + url + reason) diff --git a/src/py/reactpy/setup.py b/src/py/reactpy/setup.py deleted file mode 100644 index 37b6550a3..000000000 --- a/src/py/reactpy/setup.py +++ /dev/null @@ -1,201 +0,0 @@ -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: - msg = "%r uses the reserved extra name 'all'" - raise ValueError(msg) - 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: - msg = f"No '# extra=' header before requirements in {extra_requirements_path}" - raise ValueError(msg) -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: - msg = "NPM is not installed." - raise RuntimeError(msg) - 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) diff --git a/src/py/reactpy/tests/test__option.py b/src/py/reactpy/tests/test__option.py index 1fbd6459e..d2d538e98 100644 --- a/src/py/reactpy/tests/test__option.py +++ b/src/py/reactpy/tests/test__option.py @@ -105,7 +105,7 @@ def test_deprecated_option(): opt = DeprecatedOption("is deprecated!", "A_FAKE_OPTION", None) with pytest.warns(DeprecationWarning, match="is deprecated!"): - opt.current + assert opt.current == "is deprecated" with pytest.warns(DeprecationWarning, match="is deprecated!"): opt.current = "something" diff --git a/src/py/reactpy/tests/test_core/test_hooks.py b/src/py/reactpy/tests/test_core/test_hooks.py index 52e7c68df..598a877b3 100644 --- a/src/py/reactpy/tests/test_core/test_hooks.py +++ b/src/py/reactpy/tests/test_core/test_hooks.py @@ -5,12 +5,7 @@ import reactpy from reactpy import html from reactpy.config import REACTPY_DEBUG_MODE -from reactpy.core.hooks import ( - COMPONENT_DID_RENDER_EFFECT, - LifeCycleHook, - current_hook, - strictly_equal, -) +from reactpy.core.hooks import LifeCycleHook, strictly_equal from reactpy.core.layout import Layout from reactpy.testing import DisplayFixture, HookCatcher, assert_reactpy_did_log, poll from reactpy.testing.logs import assert_reactpy_did_not_log @@ -567,30 +562,6 @@ def bad_effect(): await layout.render() # no error -async def test_error_in_effect_cleanup_is_gracefully_handled(caplog): - caplog.clear() - component_hook = HookCatcher() - - @reactpy.component - @component_hook.capture - def ComponentWithEffect(): - @reactpy.hooks.use_effect(dependencies=None) # force this to run every time - def ok_effect(): - def bad_cleanup(): - msg = "Something went wong :(" - raise ValueError(msg) - - return bad_cleanup - - return reactpy.html.div() - - with assert_reactpy_did_log(match_error=r"Layout post-render effect .* failed"): - async with reactpy.Layout(ComponentWithEffect()) as layout: - await layout.render() - component_hook.latest.schedule_render() - await layout.render() # no error - - async def test_error_in_effect_pre_unmount_cleanup_is_gracefully_handled(): set_key = reactpy.Ref() @@ -878,7 +849,7 @@ def CounterWithEffect(): @reactpy.hooks.use_effect def some_effect_that_uses_count(): """should automatically trigger on count change""" - count # use count in this closure + _ = count # use count in this closure did_effect.set() return reactpy.html.div() @@ -906,7 +877,7 @@ def CounterWithEffect(): @reactpy.hooks.use_memo def some_memo_func_that_uses_count(): """should automatically trigger on count change""" - count # use count in this closure + _ = count # use count in this closure did_memo.set() return reactpy.html.div() @@ -941,11 +912,11 @@ def ComponentUsesContext(): assert value.current == "something" @reactpy.component - def ComponentUsesContext(): + def ComponentUsesContext2(): value.current = reactpy.use_context(Context) return html.div() - async with reactpy.Layout(ComponentUsesContext()) as layout: + async with reactpy.Layout(ComponentUsesContext2()) as layout: await layout.render() assert value.current == "something" @@ -1023,13 +994,11 @@ async def test_error_in_effect_cleanup_is_gracefully_handled(): @reactpy.component @component_hook.capture def ComponentWithEffect(): - hook = current_hook() - + @reactpy.hooks.use_effect(dependencies=None) # always run def bad_effect(): msg = "The error message" raise ValueError(msg) - hook.add_effect(COMPONENT_DID_RENDER_EFFECT, bad_effect) return reactpy.html.div() with assert_reactpy_did_log( diff --git a/src/py/reactpy/tests/test_core/test_layout.py b/src/py/reactpy/tests/test_core/test_layout.py index 93a796ae3..4aa31fe73 100644 --- a/src/py/reactpy/tests/test_core/test_layout.py +++ b/src/py/reactpy/tests/test_core/test_layout.py @@ -58,13 +58,10 @@ def Component(): component = Component() layout = reactpy.Layout(component) - with pytest.raises(Exception): + with pytest.raises(AttributeError): await layout.deliver(event_message("something")) - with pytest.raises(Exception): - layout.update(component) - - with pytest.raises(Exception): + with pytest.raises(AttributeError): await layout.render() @@ -387,7 +384,7 @@ def wrapper(*args, **kwargs): @reactpy.component @add_to_live_hooks def Outer(): - nonlocal set_inner_component + nonlocal set_inner_component # noqa PLE0117 inner_component, set_inner_component = reactpy.hooks.use_state( Inner(key="first") ) diff --git a/src/py/reactpy/tests/test_core/test_serve.py b/src/py/reactpy/tests/test_core/test_serve.py index fd63f39c4..4d39f3e3d 100644 --- a/src/py/reactpy/tests/test_core/test_serve.py +++ b/src/py/reactpy/tests/test_core/test_serve.py @@ -122,7 +122,7 @@ async def handle_event(): send_queue = asyncio.Queue() recv_queue = asyncio.Queue() - asyncio.ensure_future( + task = asyncio.create_task( serve_layout( reactpy.Layout(ComponentWithTwoEventHandlers()), send_queue.put, @@ -135,3 +135,5 @@ async def handle_event(): await recv_queue.put(event_message(non_blocked_handler.target)) await second_event_did_execute.wait() + + task.cancel() diff --git a/src/py/reactpy/tests/test_utils.py b/src/py/reactpy/tests/test_utils.py index d3bc64e04..c764b04af 100644 --- a/src/py/reactpy/tests/test_utils.py +++ b/src/py/reactpy/tests/test_utils.py @@ -24,7 +24,7 @@ def test_basic_ref_behavior(): r = reactpy.Ref() with pytest.raises(AttributeError): - r.current + r.current # noqa B018 r.current = 4 assert r.current == 4 diff --git a/tasks.py b/tasks.py index 32781f4ec..b257dabb8 100644 --- a/tasks.py +++ b/tasks.py @@ -175,7 +175,8 @@ def get_packages(context: Context) -> dict[str, PackageInfo]: raise Exit(msg) packages_by_name = {p.name: p for p in packages} - assert len(packages_by_name) == len(packages), "duplicate package names" + if len(packages_by_name) != len(packages): + raise Exit("duplicate package names detected") return packages_by_name From 878b0a378c6eadefc2e038b73273777b1a8774b8 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Wed, 31 May 2023 22:29:26 -0600 Subject: [PATCH 10/26] get tests to pass --- pyproject.toml | 11 +++- src/py/reactpy/.gitignore | 2 + src/py/reactpy/pyproject-temp.toml | 61 -------------------- src/py/reactpy/pyproject.toml | 44 +++++++++++++- src/py/reactpy/tests/test__option.py | 2 +- src/py/reactpy/tests/test_core/test_hooks.py | 2 +- tasks.py | 23 +++++++- 7 files changed, 74 insertions(+), 71 deletions(-) delete mode 100644 src/py/reactpy/pyproject-temp.toml diff --git a/pyproject.toml b/pyproject.toml index 0bbf87e16..884f0fba3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,20 @@ # --- Project ---------------------------------------------------------------------------- [project] -name = "project" +name = "scripts" version = "0.0.0" description = "Scripts for managing the ReactPy respository" -dependencies = ["invoke", "ruff", "black"] # --- Hatch ---------------------------------------------------------------------------- [tool.hatch.envs.default] -post-install-commands = ["invoke env"] +detached = true +dependencies = [ + "invoke", + "black", + "ruff", + "toml", +] [tool.hatch.envs.default.scripts] publish = "invoke publish {args}" diff --git a/src/py/reactpy/.gitignore b/src/py/reactpy/.gitignore index 29eb7f90f..0499d7590 100644 --- a/src/py/reactpy/.gitignore +++ b/src/py/reactpy/.gitignore @@ -1,2 +1,4 @@ +.coverage.* + # --- Build Artifacts --- reactpy/_static diff --git a/src/py/reactpy/pyproject-temp.toml b/src/py/reactpy/pyproject-temp.toml deleted file mode 100644 index f843140ea..000000000 --- a/src/py/reactpy/pyproject-temp.toml +++ /dev/null @@ -1,61 +0,0 @@ -[build-system] -requires = ["setuptools>=42", "wheel"] -build-backend = "setuptools.build_meta" - -[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 - -[tool.mypy] -incremental = false -ignore_missing_imports = true -warn_unused_configs = true -warn_redundant_casts = true -warn_unused_ignores = true - -[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.pydocstyle] -inherit = false -match = '.*\.py' -convention = "google" -add_ignore = ["D100", "D101", "D102", "D103", "D104", "D105", "D107", "D412", "D415"] - -[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", -] -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" diff --git a/src/py/reactpy/pyproject.toml b/src/py/reactpy/pyproject.toml index 7564854a1..23716db61 100644 --- a/src/py/reactpy/pyproject.toml +++ b/src/py/reactpy/pyproject.toml @@ -36,6 +36,8 @@ dependencies = [ "lxml >=4", ] [project.optional-dependencies] +all = ["reactpy[starlette,sanic,fastapi,flask,tornado,testing]"] + starlette = [ "starlette >=0.13.6", "uvicorn[standard] >=0.19.0", @@ -73,13 +75,24 @@ Source = "https://github.com/reactive-python/reactpy" path = "reactpy/__init__.py" [tool.hatch.envs.default] +features = ["all"] pre-install-command = "hatch run build --hooks-only" dependencies = [ "coverage[toml]>=6.5", "pytest", + "pytest-asyncio>=0.17", + "pytest-mock", + "pytest-rerunfailures", + "pytest-timeout", + "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", ] [tool.hatch.envs.default.scripts] -test = "pytest {args:tests}" +test = "playwright install && pytest {args:tests}" test-cov = "coverage run -m pytest {args:tests}" cov-report = [ "- coverage combine", @@ -102,8 +115,8 @@ dependencies = [ ] [tool.hatch.envs.lint.scripts] -typing = "mypy --install-types --non-interactive {args:reactpy tests}" -all = ["typing"] +types = "mypy --strict reactpy" +all = ["types"] [[tool.hatch.build.hooks.build-scripts.scripts]] work_dir = "../../js" @@ -116,6 +129,23 @@ artifacts = [ "app/dist/" ] +# --- Pytest --------------------------------------------------------------------------- + +[tool.pytest.ini_options] +testpaths = "tests" +xfail_strict = true +python_files = "*asserts.py test_*.py" +asyncio_mode = "auto" + +# --- MyPy ----------------------------------------------------------------------------- + +[tool.mypy] +incremental = false +ignore_missing_imports = true +warn_unused_configs = true +warn_redundant_casts = true +warn_unused_ignores = true + # --- Coverage ------------------------------------------------------------------------- [tool.coverage.run] @@ -131,8 +161,16 @@ reactpy = ["reactpy", "*/reactpy/reactpy"] tests = ["tests", "*/reactpy/tests"] [tool.coverage.report] +fail_under = 100 +show_missing = true +skip_covered = true +sort = "Name" exclude_lines = [ "no cov", + '\.\.\.\$', "if __name__ == .__main__.:", "if TYPE_CHECKING:", ] +omit = [ + "reactpy/__main__.py", +] diff --git a/src/py/reactpy/tests/test__option.py b/src/py/reactpy/tests/test__option.py index d2d538e98..63f2fada8 100644 --- a/src/py/reactpy/tests/test__option.py +++ b/src/py/reactpy/tests/test__option.py @@ -105,7 +105,7 @@ def test_deprecated_option(): opt = DeprecatedOption("is deprecated!", "A_FAKE_OPTION", None) with pytest.warns(DeprecationWarning, match="is deprecated!"): - assert opt.current == "is deprecated" + assert opt.current is None with pytest.warns(DeprecationWarning, match="is deprecated!"): opt.current = "something" diff --git a/src/py/reactpy/tests/test_core/test_hooks.py b/src/py/reactpy/tests/test_core/test_hooks.py index 598a877b3..ca2b319d1 100644 --- a/src/py/reactpy/tests/test_core/test_hooks.py +++ b/src/py/reactpy/tests/test_core/test_hooks.py @@ -1002,7 +1002,7 @@ def bad_effect(): return reactpy.html.div() with assert_reactpy_did_log( - match_message="Component post-render effect .*? failed", + match_message=r"post-render effect .*? failed", error_type=ValueError, match_error="The error message", ): diff --git a/tasks.py b/tasks.py index b257dabb8..b4d782bd6 100644 --- a/tasks.py +++ b/tasks.py @@ -1,10 +1,13 @@ from __future__ import annotations import json +import logging import os import re +from shlex import join +import sys +import toml from dataclasses import dataclass -from logging import getLogger from pathlib import Path from shutil import rmtree from typing import TYPE_CHECKING, Any, Callable @@ -33,7 +36,16 @@ def __call__( # --- Constants ------------------------------------------------------------------------ -log = getLogger(__name__) +log = logging.getLogger(__name__) +log.setLevel("INFO") +log_handler = logging.StreamHandler(sys.stdout) +log_handler.setFormatter(logging.Formatter("%(message)s")) +log.addHandler(log_handler) + + +# --- Constants ------------------------------------------------------------------------ + + ROOT = Path(__file__).parent SRC_DIR = ROOT / "src" JS_DIR = SRC_DIR / "js" @@ -59,6 +71,11 @@ def env(context: Context): """Install development environment""" in_py(context, "pip install -e .", hide="out") in_js(context, "npm ci", hide="out") + for py_proj in PY_PROJECTS: + py_proj_toml = toml.load(py_proj / "pyproject.toml") + hatch_default_env = py_proj_toml["tool"]["hatch"]["envs"].get("default", {}) + hatch_default_deps = hatch_default_env.get("dependencies", []) + context.run(f"pip install {' '.join(map(repr, hatch_default_deps))}") @task @@ -70,6 +87,7 @@ def lint_py(context: Context, fix: bool = False): else: context.run("ruff .") context.run("black --check --diff .") + in_py(context, "hatch run lint:all") @task @@ -142,6 +160,7 @@ def publish(context: Context, dry_run: str = ""): def in_py(context: Context, *commands: str, **kwargs: Any) -> None: for p in PY_PROJECTS: with context.cd(p): + log.info(f"Running commands in {p}...") for c in commands: context.run(c, **kwargs) From e23fe71867594be0aa6c7ca842b55e5134081b4e Mon Sep 17 00:00:00 2001 From: rmorshea Date: Wed, 31 May 2023 22:48:12 -0600 Subject: [PATCH 11/26] check semver --- pyproject.toml | 1 + requirements/nox-deps.txt | 2 -- requirements/pkg-deps.txt | 9 --------- requirements/pkg-extras.txt | 24 ------------------------ requirements/test-env.txt | 15 --------------- tasks.py | 7 ++++++- 6 files changed, 7 insertions(+), 51 deletions(-) delete mode 100644 requirements/nox-deps.txt delete mode 100644 requirements/pkg-deps.txt delete mode 100644 requirements/pkg-extras.txt delete mode 100644 requirements/test-env.txt diff --git a/pyproject.toml b/pyproject.toml index 884f0fba3..fb68492ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ dependencies = [ "black", "ruff", "toml", + "semver >=2, <3", ] [tool.hatch.envs.default.scripts] diff --git a/requirements/nox-deps.txt b/requirements/nox-deps.txt deleted file mode 100644 index 218b48093..000000000 --- a/requirements/nox-deps.txt +++ /dev/null @@ -1,2 +0,0 @@ -nox -noxopt diff --git a/requirements/pkg-deps.txt b/requirements/pkg-deps.txt deleted file mode 100644 index 885fa4828..000000000 --- a/requirements/pkg-deps.txt +++ /dev/null @@ -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 diff --git a/requirements/pkg-extras.txt b/requirements/pkg-extras.txt deleted file mode 100644 index 70edde6d5..000000000 --- a/requirements/pkg-extras.txt +++ /dev/null @@ -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 diff --git a/requirements/test-env.txt b/requirements/test-env.txt deleted file mode 100644 index ac59ea45b..000000000 --- a/requirements/test-env.txt +++ /dev/null @@ -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 diff --git a/tasks.py b/tasks.py index b4d782bd6..1764070eb 100644 --- a/tasks.py +++ b/tasks.py @@ -4,7 +4,6 @@ import logging import os import re -from shlex import join import sys import toml from dataclasses import dataclass @@ -12,6 +11,7 @@ from shutil import rmtree from typing import TYPE_CHECKING, Any, Callable +import semver from invoke import task from invoke.context import Context from invoke.exceptions import Exit @@ -273,6 +273,11 @@ def parse_tag(tag: str) -> TagInfo: if not match: msg = f"Invalid tag: {tag}" raise Exit(msg) + + version = match.group("version") + if not semver.Version.is_valid(version): + raise Exit(f"Invalid version: {version} in tag {tag}") + return TagInfo(tag=tag, name=match.group("name"), version=match.group("version")) From 3f84313ebaa47e35e9e846fd0f1d55f0cda70285 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Wed, 31 May 2023 23:45:23 -0600 Subject: [PATCH 12/26] more fixes --- pyproject.toml | 12 +++++++++ requirements/check-types.txt | 6 ----- requirements/make-release.txt | 1 - src/py/reactpy/pyproject.toml | 23 ++++++++-------- src/py/reactpy/reactpy/backend/default.py | 4 +-- tasks.py | 33 +++++++++++++++++------ 6 files changed, 50 insertions(+), 29 deletions(-) delete mode 100644 requirements/check-types.txt delete mode 100644 requirements/make-release.txt diff --git a/pyproject.toml b/pyproject.toml index fb68492ac..d7b81e1b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,10 +11,16 @@ description = "Scripts for managing the ReactPy respository" detached = true dependencies = [ "invoke", + # lint "black", "ruff", "toml", + "flake8", + "flake8-pyproject", + "reactpy-flake8 >=0.7", + # publish "semver >=2, <3", + "twine", ] [tool.hatch.envs.default.scripts] @@ -33,6 +39,12 @@ test-docs = "invoke test-docs" target-version = ["py39"] line-length = 88 +# --- Flake8 ---------------------------------------------------------------------------- + +[tool.flake8] +select = ["RPY"] # only need to check with reactpy-flake8 +exclude = ["**/node_modules/*", ".eggs/*", ".tox/*", "**/venv/*"] + # --- Ruff ----------------------------------------------------------------------------- [tool.ruff] diff --git a/requirements/check-types.txt b/requirements/check-types.txt deleted file mode 100644 index 2ae26d60a..000000000 --- a/requirements/check-types.txt +++ /dev/null @@ -1,6 +0,0 @@ -mypy -types-click -types-tornado -types-pkg-resources -types-flask -types-requests diff --git a/requirements/make-release.txt b/requirements/make-release.txt deleted file mode 100644 index da7b791ef..000000000 --- a/requirements/make-release.txt +++ /dev/null @@ -1 +0,0 @@ -semver >=2, <3 diff --git a/src/py/reactpy/pyproject.toml b/src/py/reactpy/pyproject.toml index 23716db61..4423e7135 100644 --- a/src/py/reactpy/pyproject.toml +++ b/src/py/reactpy/pyproject.toml @@ -93,9 +93,9 @@ dependencies = [ ] [tool.hatch.envs.default.scripts] test = "playwright install && pytest {args:tests}" -test-cov = "coverage run -m pytest {args:tests}" +test-cov = "playwright install && coverage run -m pytest {args:tests}" cov-report = [ - "- coverage combine", + # "- coverage combine", "coverage report", ] cov = [ @@ -103,8 +103,11 @@ cov = [ "cov-report", ] +[tool.hatch.envs.default.env-vars] +REACTPY_DEBUG_MODE="1" + [tool.hatch.envs.lint] -detached = true +features = ["all"] dependencies = [ "mypy>=1.0.0", "types-click", @@ -149,25 +152,21 @@ warn_unused_ignores = true # --- Coverage ------------------------------------------------------------------------- [tool.coverage.run] -source_pkgs = ["reactpy", "tests"] -branch = true -parallel = true +source_pkgs = ["reactpy"] +branch = false +parallel = false omit = [ "reactpy/__init__.py", ] -[tool.coverage.paths] -reactpy = ["reactpy", "*/reactpy/reactpy"] -tests = ["tests", "*/reactpy/tests"] - [tool.coverage.report] fail_under = 100 show_missing = true skip_covered = true sort = "Name" exclude_lines = [ - "no cov", - '\.\.\.\$', + "no ?cov", + '\.\.\.', "if __name__ == .__main__.:", "if TYPE_CHECKING:", ] diff --git a/src/py/reactpy/reactpy/backend/default.py b/src/py/reactpy/reactpy/backend/default.py index 2e6d754d0..bec608f8d 100644 --- a/src/py/reactpy/reactpy/backend/default.py +++ b/src/py/reactpy/reactpy/backend/default.py @@ -27,10 +27,10 @@ def create_development_app() -> Any: return _default_implementation().create_development_app() -def Options(*args: Any, **kwargs: Any) -> NoReturn: +def Options(*args: Any, **kwargs: Any) -> NoReturn: # pragma: no cover """Create configuration options""" msg = "Default implementation has no options." - raise ValueError(msg) # pragma: no cover + raise ValueError(msg) async def serve_development_app( diff --git a/tasks.py b/tasks.py index 1764070eb..c0fa36fb1 100644 --- a/tasks.py +++ b/tasks.py @@ -5,13 +5,13 @@ import os import re import sys -import toml from dataclasses import dataclass from pathlib import Path from shutil import rmtree from typing import TYPE_CHECKING, Any, Callable import semver +import toml from invoke import task from invoke.context import Context from invoke.exceptions import Exit @@ -69,28 +69,45 @@ def __call__( @task def env(context: Context): """Install development environment""" - in_py(context, "pip install -e .", hide="out") - in_js(context, "npm ci", hide="out") + env_py(context) + env_js(context) + + +@task +def env_py(context: Context): + """Install Python development environment""" for py_proj in PY_PROJECTS: py_proj_toml = toml.load(py_proj / "pyproject.toml") hatch_default_env = py_proj_toml["tool"]["hatch"]["envs"].get("default", {}) + hatch_default_features = hatch_default_env.get("features", []) hatch_default_deps = hatch_default_env.get("dependencies", []) + with context.cd(py_proj): + context.run(f"pip install '.[{','.join(hatch_default_features)}]'") context.run(f"pip install {' '.join(map(repr, hatch_default_deps))}") +@task +def env_js(context: Context): + """Install JS development environment""" + in_js(context, "npm ci", hide="out") + + @task def lint_py(context: Context, fix: bool = False): """Run linters and type checkers""" if fix: - context.run("black .") context.run("ruff --fix .") else: context.run("ruff .") context.run("black --check --diff .") - in_py(context, "hatch run lint:all") + in_py( + context, + f"flake8 --toml-config {ROOT / 'pyproject.toml'} .", + "hatch run lint:all", + ) -@task +@task(pre=[env_js]) def lint_js(context: Context, fix: bool = False): """Run linters and type checkers""" if fix: @@ -106,13 +123,13 @@ def test_py(context: Context, no_cov: bool = False): in_py(context, f"hatch run {'test' if no_cov else 'cov'}") -@task +@task(pre=[env_js]) def test_js(context: Context): """Run test suites""" in_js(context, "npm run check:tests") -@task +@task(pre=[env_py]) def test_docs(context: Context): raise Exit("Not implemented") From ed7b56c08823e69cce1ca85abd5acef4559def5e Mon Sep 17 00:00:00 2001 From: rmorshea Date: Wed, 31 May 2023 23:53:14 -0600 Subject: [PATCH 13/26] ignore missing coverage --- src/py/reactpy/reactpy/_console/ast_utils.py | 2 +- .../reactpy/_console/rewrite_camel_case_props.py | 2 +- src/py/reactpy/reactpy/_console/rewrite_keys.py | 2 +- src/py/reactpy/reactpy/_option.py | 2 +- src/py/reactpy/reactpy/backend/_common.py | 2 +- src/py/reactpy/reactpy/backend/default.py | 6 +++--- src/py/reactpy/reactpy/backend/flask.py | 10 +++++----- src/py/reactpy/reactpy/backend/hooks.py | 4 ++-- src/py/reactpy/reactpy/backend/sanic.py | 6 +++--- src/py/reactpy/reactpy/backend/starlette.py | 4 ++-- src/py/reactpy/reactpy/backend/tornado.py | 2 +- src/py/reactpy/reactpy/backend/utils.py | 4 ++-- src/py/reactpy/reactpy/core/_f_back.py | 6 +++--- src/py/reactpy/reactpy/core/hooks.py | 6 +++--- src/py/reactpy/reactpy/core/layout.py | 8 ++++---- src/py/reactpy/reactpy/core/types.py | 2 +- src/py/reactpy/reactpy/core/vdom.py | 2 +- src/py/reactpy/reactpy/testing/backend.py | 4 ++-- src/py/reactpy/reactpy/testing/common.py | 2 +- src/py/reactpy/reactpy/testing/display.py | 4 ++-- src/py/reactpy/reactpy/testing/logs.py | 2 +- src/py/reactpy/reactpy/utils.py | 8 ++++---- src/py/reactpy/reactpy/widgets.py | 2 +- src/py/reactpy/tests/conftest.py | 2 +- 24 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/py/reactpy/reactpy/_console/ast_utils.py b/src/py/reactpy/reactpy/_console/ast_utils.py index 0383a913f..180cb0e4b 100644 --- a/src/py/reactpy/reactpy/_console/ast_utils.py +++ b/src/py/reactpy/reactpy/_console/ast_utils.py @@ -37,7 +37,7 @@ def rewrite_changed_nodes( ): nodes_to_unparse.append(current_node) break - else: # pragma: no cover + else: # nocov msg = "Failed to change code" raise RuntimeError(msg) diff --git a/src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py b/src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py index edf5b6960..e5d1860c2 100644 --- a/src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py +++ b/src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py @@ -23,7 +23,7 @@ @click.argument("paths", nargs=-1, type=click.Path(exists=True)) def rewrite_camel_case_props(paths: list[str]) -> None: """Rewrite camelCase props to snake_case""" - if sys.version_info < (3, 9): # pragma: no cover + if sys.version_info < (3, 9): # nocov msg = "This command requires Python>=3.9" raise RuntimeError(msg) diff --git a/src/py/reactpy/reactpy/_console/rewrite_keys.py b/src/py/reactpy/reactpy/_console/rewrite_keys.py index 4d6dd7338..cbd90351a 100644 --- a/src/py/reactpy/reactpy/_console/rewrite_keys.py +++ b/src/py/reactpy/reactpy/_console/rewrite_keys.py @@ -45,7 +45,7 @@ def rewrite_keys(paths: list[str]) -> None: just above its changes. As such it requires manual intervention to put those comments back in their original location. """ - if sys.version_info < (3, 9): # pragma: no cover + if sys.version_info < (3, 9): # nocov msg = "This command requires Python>=3.9" raise RuntimeError(msg) diff --git a/src/py/reactpy/reactpy/_option.py b/src/py/reactpy/reactpy/_option.py index 633eb34fe..1421f33a3 100644 --- a/src/py/reactpy/reactpy/_option.py +++ b/src/py/reactpy/reactpy/_option.py @@ -119,7 +119,7 @@ def __repr__(self) -> str: return f"Option({self._name}={self.current!r})" -class DeprecatedOption(Option[_O]): # pragma: no cover +class DeprecatedOption(Option[_O]): # nocov def __init__(self, message: str, *args: Any, **kwargs: Any) -> None: self._deprecation_message = message super().__init__(*args, **kwargs) diff --git a/src/py/reactpy/reactpy/backend/_common.py b/src/py/reactpy/reactpy/backend/_common.py index 57a7486e4..c2e9ee583 100644 --- a/src/py/reactpy/reactpy/backend/_common.py +++ b/src/py/reactpy/reactpy/backend/_common.py @@ -53,7 +53,7 @@ async def serve_development_asgi( # Since we aren't using the uvicorn's `run()` API, we can't guarantee uvicorn's # order of operations. So we need to make sure `shutdown()` always has an initialized # list of `self.servers` to use. - if not hasattr(server, "servers"): # pragma: no cover + if not hasattr(server, "servers"): # nocov server.servers = [] await asyncio.wait_for(server.shutdown(), timeout=3) diff --git a/src/py/reactpy/reactpy/backend/default.py b/src/py/reactpy/reactpy/backend/default.py index bec608f8d..eb27ca93b 100644 --- a/src/py/reactpy/reactpy/backend/default.py +++ b/src/py/reactpy/reactpy/backend/default.py @@ -16,7 +16,7 @@ def configure( app: Any, component: RootComponentConstructor, options: None = None ) -> None: """Configure the given app instance to display the given component""" - if options is not None: # pragma: no cover + if options is not None: # nocov msg = "Default implementation cannot be configured with options" raise ValueError(msg) return _default_implementation().configure(app, component) @@ -27,7 +27,7 @@ def create_development_app() -> Any: return _default_implementation().create_development_app() -def Options(*args: Any, **kwargs: Any) -> NoReturn: # pragma: no cover +def Options(*args: Any, **kwargs: Any) -> NoReturn: # nocov """Create configuration options""" msg = "Default implementation has no options." raise ValueError(msg) @@ -57,7 +57,7 @@ def _default_implementation() -> BackendImplementation[Any]: try: implementation = next(all_implementations()) - except StopIteration: # pragma: no cover + except StopIteration: # nocov logger.debug("Backend implementation import failed", exc_info=exc_info()) msg = "No built-in server implementation installed." raise RuntimeError(msg) from None diff --git a/src/py/reactpy/reactpy/backend/flask.py b/src/py/reactpy/reactpy/backend/flask.py index e582bc9b5..e28ed7652 100644 --- a/src/py/reactpy/reactpy/backend/flask.py +++ b/src/py/reactpy/reactpy/backend/flask.py @@ -111,7 +111,7 @@ def run_server() -> None: # the thread should eventually join thread.join(timeout=3) # just double check it happened - if thread.is_alive(): # pragma: no cover + if thread.is_alive(): # nocov msg = "Failed to shutdown server." raise RuntimeError(msg) @@ -129,7 +129,7 @@ def use_request() -> Request: def use_connection() -> Connection[_FlaskCarrier]: """Get the current :class:`Connection`""" conn = _use_connection() - if not isinstance(conn.carrier, _FlaskCarrier): # pragma: no cover + if not isinstance(conn.carrier, _FlaskCarrier): # nocov msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Flask server?" raise TypeError(msg) return conn @@ -152,7 +152,7 @@ def _setup_common_routes( options: Options, ) -> None: cors_options = options.cors - if cors_options: # pragma: no cover + if cors_options: # nocov cors_params = cors_options if isinstance(cors_options, dict) else {} CORS(api_blueprint, **cors_params) @@ -258,7 +258,7 @@ async def main() -> None: dispatch_thread_info = cast(_DispatcherThreadInfo, dispatch_thread_info_ref.current) if dispatch_thread_info is None: - raise RuntimeError("Failed to create dispatcher thread") + raise RuntimeError("Failed to create dispatcher thread") # nocov stop = ThreadEvent() @@ -274,7 +274,7 @@ def run_send() -> None: dispatch_thread_info.dispatch_loop.call_soon_threadsafe( dispatch_thread_info.async_recv_queue.put_nowait, value ) - finally: # pragma: no cover + finally: # nocov dispatch_thread_info.dispatch_loop.call_soon_threadsafe( dispatch_thread_info.dispatch_future.cancel ) diff --git a/src/py/reactpy/reactpy/backend/hooks.py b/src/py/reactpy/reactpy/backend/hooks.py index b4d91ef55..19ad114ed 100644 --- a/src/py/reactpy/reactpy/backend/hooks.py +++ b/src/py/reactpy/reactpy/backend/hooks.py @@ -13,9 +13,9 @@ def use_connection() -> Connection[Any]: """Get the current :class:`~reactpy.backend.types.Connection`.""" conn = use_context(ConnectionContext) - if conn is None: + if conn is None: # nocov msg = "No backend established a connection." - raise RuntimeError(msg) # pragma: no cover + raise RuntimeError(msg) return conn diff --git a/src/py/reactpy/reactpy/backend/sanic.py b/src/py/reactpy/reactpy/backend/sanic.py index b1f4e2b60..53dd0ce68 100644 --- a/src/py/reactpy/reactpy/backend/sanic.py +++ b/src/py/reactpy/reactpy/backend/sanic.py @@ -80,7 +80,7 @@ def use_websocket() -> WebSocketConnection: def use_connection() -> Connection[_SanicCarrier]: """Get the current :class:`Connection`""" conn = _use_connection() - if not isinstance(conn.carrier, _SanicCarrier): # pragma: no cover + if not isinstance(conn.carrier, _SanicCarrier): # nocov msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Sanic server?" raise TypeError(msg) return conn @@ -103,7 +103,7 @@ def _setup_common_routes( options: Options, ) -> None: cors_options = options.cors - if cors_options: # pragma: no cover + if cors_options: # nocov cors_params = cors_options if isinstance(cors_options, dict) else {} CORS(api_blueprint, **cors_params) @@ -159,7 +159,7 @@ async def model_stream( ) -> None: asgi_app = getattr(request.app, "_asgi_app", None) scope = asgi_app.transport.scope if asgi_app else {} - if not scope: # pragma: no cover + if not scope: # nocov logger.warning("No scope. Sanic may not be running with an ASGI server") send, recv = _make_send_recv_callbacks(socket) diff --git a/src/py/reactpy/reactpy/backend/starlette.py b/src/py/reactpy/reactpy/backend/starlette.py index 1f4f0329f..658fccfbd 100644 --- a/src/py/reactpy/reactpy/backend/starlette.py +++ b/src/py/reactpy/reactpy/backend/starlette.py @@ -76,7 +76,7 @@ def use_websocket() -> WebSocket: def use_connection() -> Connection[WebSocket]: conn = _use_connection() - if not isinstance(conn.carrier, WebSocket): # pragma: no cover + if not isinstance(conn.carrier, WebSocket): # nocov msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Flask server?" raise TypeError(msg) return conn @@ -95,7 +95,7 @@ class Options(CommonOptions): def _setup_common_routes(options: Options, app: Starlette) -> None: cors_options = options.cors - if cors_options: # pragma: no cover + if cors_options: # nocov cors_params = ( cors_options if isinstance(cors_options, dict) else {"allow_origins": ["*"]} ) diff --git a/src/py/reactpy/reactpy/backend/tornado.py b/src/py/reactpy/reactpy/backend/tornado.py index b7b918b6f..5ec877532 100644 --- a/src/py/reactpy/reactpy/backend/tornado.py +++ b/src/py/reactpy/reactpy/backend/tornado.py @@ -98,7 +98,7 @@ def use_request() -> HTTPServerRequest: def use_connection() -> Connection[HTTPServerRequest]: conn = _use_connection() - if not isinstance(conn.carrier, HTTPServerRequest): # pragma: no cover + if not isinstance(conn.carrier, HTTPServerRequest): # nocov msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Flask server?" raise TypeError(msg) return conn diff --git a/src/py/reactpy/reactpy/backend/utils.py b/src/py/reactpy/reactpy/backend/utils.py index ff5599718..3d9be13a4 100644 --- a/src/py/reactpy/reactpy/backend/utils.py +++ b/src/py/reactpy/reactpy/backend/utils.py @@ -79,11 +79,11 @@ def all_implementations() -> Iterator[BackendImplementation[Any]]: try: relative_import_name = f"{__name__.rsplit('.', 1)[0]}.{name}" module = import_module(relative_import_name) - except ImportError: # pragma: no cover + except ImportError: # nocov logger.debug(f"Failed to import {name!r}", exc_info=True) continue - if not isinstance(module, BackendImplementation): # pragma: no cover + if not isinstance(module, BackendImplementation): # nocov msg = f"{module.__name__!r} is an invalid implementation" raise TypeError(msg) diff --git a/src/py/reactpy/reactpy/core/_f_back.py b/src/py/reactpy/reactpy/core/_f_back.py index 8a824bcd4..fe1a6c10c 100644 --- a/src/py/reactpy/reactpy/core/_f_back.py +++ b/src/py/reactpy/reactpy/core/_f_back.py @@ -7,10 +7,10 @@ def f_module_name(index: int = 0) -> str: frame = f_back(index + 1) if frame is None: - return "" # pragma: no cover + return "" # nocov name = frame.f_globals.get("__name__", "") if not isinstance(name, str): - raise TypeError("Expected module name to be a string") + raise TypeError("Expected module name to be a string") # nocov return name @@ -21,4 +21,4 @@ def f_back(index: int = 0) -> FrameType | None: return frame frame = frame.f_back index -= 1 - return None # pragma: no cover + return None # nocov diff --git a/src/py/reactpy/reactpy/core/hooks.py b/src/py/reactpy/reactpy/core/hooks.py index de97fc9bd..ab3ff7bf8 100644 --- a/src/py/reactpy/reactpy/core/hooks.py +++ b/src/py/reactpy/reactpy/core/hooks.py @@ -248,11 +248,11 @@ def use_context(context: Context[_Type]) -> _Type: if provider is None: # same assertions but with normal exceptions if not isinstance(context, FunctionType): - raise TypeError(f"{context} is not a Context") + raise TypeError(f"{context} is not a Context") # nocov if context.__kwdefaults__ is None: - raise TypeError(f"{context} has no 'value' kwarg") + raise TypeError(f"{context} has no 'value' kwarg") # nocov if "value" not in context.__kwdefaults__: - raise TypeError(f"{context} has no 'value' kwarg") + raise TypeError(f"{context} has no 'value' kwarg") # nocov return cast(_Type, context.__kwdefaults__["value"]) return provider._value diff --git a/src/py/reactpy/reactpy/core/layout.py b/src/py/reactpy/reactpy/core/layout.py index be1a6d61b..368e79c7d 100644 --- a/src/py/reactpy/reactpy/core/layout.py +++ b/src/py/reactpy/reactpy/core/layout.py @@ -45,7 +45,7 @@ class Layout: "_model_states_by_life_cycle_state_id", ] - if not hasattr(abc.ABC, "__weakref__"): # pragma: no cover + if not hasattr(abc.ABC, "__weakref__"): # nocov __slots__.append("__weakref__") def __init__(self, root: ComponentType) -> None: @@ -197,7 +197,7 @@ def _render_model( ) -> None: try: new_state.model.current = {"tagName": raw_model["tagName"]} - except Exception as e: # pragma: no cover + except Exception as e: # nocov msg = f"Expected a VDOM element dict, not {raw_model}" raise ValueError(msg) from e if "key" in raw_model: @@ -595,13 +595,13 @@ def is_component_state(self) -> bool: def parent(self) -> _ModelState: parent = self._parent_ref() if parent is None: - raise RuntimeError("detached model state") + raise RuntimeError("detached model state") # nocov return parent def append_child(self, child: Any) -> None: self.model.current["children"].append(child) - def __repr__(self) -> str: # pragma: no cover + def __repr__(self) -> str: # nocov return f"ModelState({ {s: getattr(self, s, None) for s in self.__slots__} })" diff --git a/src/py/reactpy/reactpy/core/types.py b/src/py/reactpy/reactpy/core/types.py index 3462f4d27..93f4fe36b 100644 --- a/src/py/reactpy/reactpy/core/types.py +++ b/src/py/reactpy/reactpy/core/types.py @@ -28,7 +28,7 @@ class State(NamedTuple, Generic[_Type]): value: _Type set_value: Callable[[_Type | Callable[[_Type], _Type]], None] -else: # pragma: no cover +else: # nocov State = namedtuple("State", ("value", "set_value")) diff --git a/src/py/reactpy/reactpy/core/vdom.py b/src/py/reactpy/reactpy/core/vdom.py index bd2c65e14..c716fe347 100644 --- a/src/py/reactpy/reactpy/core/vdom.py +++ b/src/py/reactpy/reactpy/core/vdom.py @@ -162,7 +162,7 @@ def vdom( (subject to change) specifies javascript that, when evaluated returns a React component. """ - if kwargs: # pragma: no cover + if kwargs: # nocov if "key" in kwargs: if attributes_and_children: maybe_attributes, *children = attributes_and_children diff --git a/src/py/reactpy/reactpy/testing/backend.py b/src/py/reactpy/reactpy/testing/backend.py index 3a1ade140..8e4042284 100644 --- a/src/py/reactpy/reactpy/testing/backend.py +++ b/src/py/reactpy/reactpy/testing/backend.py @@ -133,7 +133,7 @@ async def stop_server() -> None: try: await asyncio.wait_for(started.wait(), timeout=self.timeout) - except Exception: # pragma: no cover + except Exception: # nocov # see if we can await the future for a more helpful error await asyncio.wait_for(server_future, timeout=self.timeout) raise @@ -151,7 +151,7 @@ async def __aexit__( self.mount(None) # reset the view logged_errors = self.list_logged_exceptions(del_log_records=False) - if logged_errors: # pragma: no cover + if logged_errors: # nocov msg = "Unexpected logged exception" raise LogAssertionError(msg) from logged_errors[0] diff --git a/src/py/reactpy/reactpy/testing/common.py b/src/py/reactpy/reactpy/testing/common.py index 0c718036a..945c1c31d 100644 --- a/src/py/reactpy/reactpy/testing/common.py +++ b/src/py/reactpy/reactpy/testing/common.py @@ -66,7 +66,7 @@ async def until( result = await self._func(*self._args, **self._kwargs) if condition(result): break - elif (time.time() - started_at) > timeout: # pragma: no cover + elif (time.time() - started_at) > timeout: # nocov msg = f"Expected {description} after {timeout} seconds - last value was {result!r}" raise TimeoutError(msg) diff --git a/src/py/reactpy/reactpy/testing/display.py b/src/py/reactpy/reactpy/testing/display.py index 1afb79940..bb0d8351d 100644 --- a/src/py/reactpy/reactpy/testing/display.py +++ b/src/py/reactpy/reactpy/testing/display.py @@ -56,9 +56,9 @@ async def goto( async def root_element(self) -> ElementHandle: element = await self.page.wait_for_selector("#app", state="attached") - if element is None: + if element is None: # nocov msg = "Root element not attached" - raise RuntimeError(msg) # pragma: no cover + raise RuntimeError(msg) return element async def __aenter__(self) -> DisplayFixture: diff --git a/src/py/reactpy/reactpy/testing/logs.py b/src/py/reactpy/reactpy/testing/logs.py index c240eae18..e9337b19c 100644 --- a/src/py/reactpy/reactpy/testing/logs.py +++ b/src/py/reactpy/reactpy/testing/logs.py @@ -61,7 +61,7 @@ def assert_reactpy_did_log( ) ): break - else: # pragma: no cover + else: # nocov _raise_log_message_error( "Could not find a log record matching the given", match_message, diff --git a/src/py/reactpy/reactpy/utils.py b/src/py/reactpy/reactpy/utils.py index 3da8f4d3f..fead642f2 100644 --- a/src/py/reactpy/reactpy/utils.py +++ b/src/py/reactpy/reactpy/utils.py @@ -95,7 +95,7 @@ def html_to_vdom( If ``True``, raise an exception if the HTML does not perfectly follow HTML5 syntax. """ - if not isinstance(html, str): # pragma: no cover + if not isinstance(html, str): # nocov msg = f"Expected html to be a string, not {type(html).__name__}" raise TypeError(msg) @@ -112,7 +112,7 @@ def html_to_vdom( ) except etree.XMLSyntaxError as e: if not strict: - raise e # pragma: no cover + raise e # nocov msg = "An error has occurred while parsing the HTML.\n\nThis HTML may be malformatted, or may not perfectly adhere to HTML5.\nIf you believe the exception above was due to something intentional, you can disable the strict parameter on html_to_vdom().\nOtherwise, repair your broken HTML and try again." raise HTMLParseError(msg) from e @@ -136,7 +136,7 @@ def _etree_to_vdom( dictionary which will be replaced by ``new``. For example, you could use a transform function to add highlighting to a ```` block. """ - if not isinstance(node, etree._Element): # pragma: no cover + if not isinstance(node, etree._Element): # nocov msg = f"Expected node to be a etree._Element, not {type(node).__name__}" raise TypeError(msg) @@ -291,7 +291,7 @@ def _vdom_attr_to_html_str(key: str, value: Any) -> tuple[str, str]: ): key = _CAMEL_CASE_SUB_PATTERN.sub("-", key) - if callable(value): + if callable(value): # nocov raise TypeError(f"Cannot convert callable attribute {key}={value} to HTML") # Again, we lower the attribute name only to normalize - HTML is case-insensitive: diff --git a/src/py/reactpy/reactpy/widgets.py b/src/py/reactpy/reactpy/widgets.py index e139c1281..cc19be04d 100644 --- a/src/py/reactpy/reactpy/widgets.py +++ b/src/py/reactpy/reactpy/widgets.py @@ -92,7 +92,7 @@ def __call__(self, value: str) -> _CastTo: def hotswap( update_on_change: bool = False, -) -> tuple[_MountFunc, ComponentConstructor]: # pragma: no cover +) -> tuple[_MountFunc, ComponentConstructor]: # nocov warn( "The 'hotswap' function is deprecated and will be removed in a future release", DeprecationWarning, diff --git a/src/py/reactpy/tests/conftest.py b/src/py/reactpy/tests/conftest.py index ceadbbb78..1a1f02098 100644 --- a/src/py/reactpy/tests/conftest.py +++ b/src/py/reactpy/tests/conftest.py @@ -57,7 +57,7 @@ async def browser(pytestconfig: Config): @pytest.fixture(scope="session") def event_loop(): - if os.name == "nt": # pragma: no cover + if os.name == "nt": # nocov asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) with open_event_loop() as loop: yield loop From 3deba06b86942a78f2ca5ebc49af5e0958da4920 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Thu, 1 Jun 2023 00:04:43 -0600 Subject: [PATCH 14/26] fix cov --- src/py/reactpy/reactpy/core/hooks.py | 2 +- src/py/reactpy/tests/test_core/test_hooks.py | 34 ++++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/py/reactpy/reactpy/core/hooks.py b/src/py/reactpy/reactpy/core/hooks.py index ab3ff7bf8..9b025c0ca 100644 --- a/src/py/reactpy/reactpy/core/hooks.py +++ b/src/py/reactpy/reactpy/core/hooks.py @@ -709,7 +709,7 @@ def set_current(self) -> None: def unset_current(self) -> None: """Unset this hook as the active hook in this thread""" if _hook_stack.get().pop() is not self: - raise RuntimeError("Hook stack is in an invalid state") + raise RuntimeError("Hook stack is in an invalid state") # nocov def _schedule_render(self) -> None: try: diff --git a/src/py/reactpy/tests/test_core/test_hooks.py b/src/py/reactpy/tests/test_core/test_hooks.py index ca2b319d1..b16f31261 100644 --- a/src/py/reactpy/tests/test_core/test_hooks.py +++ b/src/py/reactpy/tests/test_core/test_hooks.py @@ -5,7 +5,12 @@ import reactpy from reactpy import html from reactpy.config import REACTPY_DEBUG_MODE -from reactpy.core.hooks import LifeCycleHook, strictly_equal +from reactpy.core.hooks import ( + LifeCycleHook, + strictly_equal, + COMPONENT_DID_RENDER_EFFECT, + current_hook, +) from reactpy.core.layout import Layout from reactpy.testing import DisplayFixture, HookCatcher, assert_reactpy_did_log, poll from reactpy.testing.logs import assert_reactpy_did_not_log @@ -988,7 +993,7 @@ def Child2(): await layout.render() -async def test_error_in_effect_cleanup_is_gracefully_handled(): +async def test_error_in_layout_effect_cleanup_is_gracefully_handled(): component_hook = HookCatcher() @reactpy.component @@ -1227,3 +1232,28 @@ def some_component(): state.current.set_value(2) await layout.render() assert state.current.value == 2 + + +async def test_error_in_component_effect_cleanup_is_gracefully_handled(): + component_hook = HookCatcher() + + @reactpy.component + @component_hook.capture + def ComponentWithEffect(): + hook = current_hook() + + def bad_effect(): + raise ValueError("The error message") + + hook.add_effect(COMPONENT_DID_RENDER_EFFECT, bad_effect) + return reactpy.html.div() + + with assert_reactpy_did_log( + match_message="Component post-render effect .*? failed", + error_type=ValueError, + match_error="The error message", + ): + async with reactpy.Layout(ComponentWithEffect()) as layout: + await layout.render() + component_hook.latest.schedule_render() + await layout.render() # no error From 1da3959d6e3d2699b2e6b9310a57b3578f325a09 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Thu, 1 Jun 2023 00:06:10 -0600 Subject: [PATCH 15/26] fix import sort --- src/py/reactpy/tests/test_core/test_hooks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/py/reactpy/tests/test_core/test_hooks.py b/src/py/reactpy/tests/test_core/test_hooks.py index b16f31261..ce9afdc12 100644 --- a/src/py/reactpy/tests/test_core/test_hooks.py +++ b/src/py/reactpy/tests/test_core/test_hooks.py @@ -6,10 +6,10 @@ from reactpy import html from reactpy.config import REACTPY_DEBUG_MODE from reactpy.core.hooks import ( - LifeCycleHook, - strictly_equal, COMPONENT_DID_RENDER_EFFECT, + LifeCycleHook, current_hook, + strictly_equal, ) from reactpy.core.layout import Layout from reactpy.testing import DisplayFixture, HookCatcher, assert_reactpy_did_log, poll From bbe4ba5e4c7a9517517541cc3fd24b0674cb94fb Mon Sep 17 00:00:00 2001 From: rmorshea Date: Thu, 1 Jun 2023 00:13:38 -0600 Subject: [PATCH 16/26] try build in env-js --- tasks.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index c0fa36fb1..5b3b74ea0 100644 --- a/tasks.py +++ b/tasks.py @@ -89,7 +89,12 @@ def env_py(context: Context): @task def env_js(context: Context): """Install JS development environment""" - in_js(context, "npm ci", hide="out") + in_js( + context, + "npm ci", + "npm run build", + hide="out", + ) @task From dc1b92dd2f6cd4e0f58a6285e269e8bb16e07a77 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Thu, 1 Jun 2023 01:33:06 -0600 Subject: [PATCH 17/26] try latest hatch-build-scripts --- src/py/reactpy/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/reactpy/pyproject.toml b/src/py/reactpy/pyproject.toml index 4423e7135..177034893 100644 --- a/src/py/reactpy/pyproject.toml +++ b/src/py/reactpy/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["hatchling", "hatch-build-scripts>=0.0.2"] +requires = ["hatchling", "hatch-build-scripts>=0.0.3"] build-backend = "hatchling.build" # --- Project -------------------------------------------------------------------------- From b15343017c4fdd108728d9c8abb9bc07e9db0d3d Mon Sep 17 00:00:00 2001 From: rmorshea Date: Thu, 1 Jun 2023 02:09:57 -0600 Subject: [PATCH 18/26] misc fixes --- src/js/.gitignore | 1 + src/js/app/package.json | 1 - src/js/package.json | 8 +++++--- tasks.py | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/js/.gitignore b/src/js/.gitignore index 52128d3e1..fedd7ea26 100644 --- a/src/js/.gitignore +++ b/src/js/.gitignore @@ -1,2 +1,3 @@ tsconfig.tsbuildinfo packages/**/package-lock.json +dist diff --git a/src/js/app/package.json b/src/js/app/package.json index 7c1e04a05..b9371dba3 100644 --- a/src/js/app/package.json +++ b/src/js/app/package.json @@ -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" } diff --git a/src/js/package.json b/src/js/package.json index b34abd67a..dc6dc7fca 100644 --- a/src/js/package.json +++ b/src/js/package.json @@ -4,10 +4,12 @@ "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", diff --git a/tasks.py b/tasks.py index 5b3b74ea0..e7a2f0e18 100644 --- a/tasks.py +++ b/tasks.py @@ -125,7 +125,7 @@ def lint_js(context: Context, fix: bool = False): @task def test_py(context: Context, no_cov: bool = False): """Run test suites""" - in_py(context, f"hatch run {'test' if no_cov else 'cov'}") + in_py(context, f"hatch run {'test' if no_cov else 'cov'} --maxfail=3") @task(pre=[env_js]) From fe6b620b82803c122b1eef6f1f8fb0b939bec369 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Sat, 3 Jun 2023 12:10:27 -0600 Subject: [PATCH 19/26] try to fix npm in gh action --- .github/workflows/.hatch-run.yml | 8 ++++++++ .github/workflows/publish.yml | 1 + src/py/reactpy/pyproject.toml | 6 +++--- tasks.py | 8 ++++++-- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/workflows/.hatch-run.yml b/.github/workflows/.hatch-run.yml index e64ab3204..3d267be3a 100644 --- a/.github/workflows/.hatch-run.yml +++ b/.github/workflows/.hatch-run.yml @@ -17,6 +17,9 @@ on: required: false type: string default: '["3.x"]' + uses-secrets: + type: boolean + default: false secrets: node-auth-token: required: false @@ -36,9 +39,14 @@ jobs: steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 + if: ${{ inputs.uses-secrets }}} with: node-version: "14.x" registry-url: "https://registry.npmjs.org" + - uses: actions/setup-node@v2 + if: ${{ inputs.uses-secrets }}} + with: + node-version: "14.x" - name: Pin NPM Version run: npm install -g npm@8.19.3 - name: Use Python ${{ matrix.python-version }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d8af63e66..e0a283275 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,6 +13,7 @@ jobs: with: job-name: "publish" hatch-run: "publish" + uses-secrets: true secrets: node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }} pypi-username: ${{ secrets.PYPI_USERNAME }} diff --git a/src/py/reactpy/pyproject.toml b/src/py/reactpy/pyproject.toml index 177034893..715503051 100644 --- a/src/py/reactpy/pyproject.toml +++ b/src/py/reactpy/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["hatchling", "hatch-build-scripts>=0.0.3"] +requires = ["hatchling", "hatch-build-scripts>=0.0.4"] build-backend = "hatchling.build" # --- Project -------------------------------------------------------------------------- @@ -76,7 +76,7 @@ path = "reactpy/__init__.py" [tool.hatch.envs.default] features = ["all"] -pre-install-command = "hatch run build --hooks-only" +pre-install-command = "hatch build --hooks-only" dependencies = [ "coverage[toml]>=6.5", "pytest", @@ -125,7 +125,7 @@ all = ["types"] work_dir = "../../js" out_dir = "reactpy/_static" commands = [ - "npm install", + "npm ci", "npm run build" ] artifacts = [ diff --git a/tasks.py b/tasks.py index e7a2f0e18..26cb42687 100644 --- a/tasks.py +++ b/tasks.py @@ -125,7 +125,11 @@ def lint_js(context: Context, fix: bool = False): @task def test_py(context: Context, no_cov: bool = False): """Run test suites""" - in_py(context, f"hatch run {'test' if no_cov else 'cov'} --maxfail=3") + in_py( + context, + "hatch build --hooks-only", + f"hatch run {'test' if no_cov else 'cov'} --maxfail=3", + ) @task(pre=[env_js]) @@ -329,7 +333,7 @@ def publish(dry_run: bool) -> None: return context.run( f"npm --workspace {package.name} publish --access public", - env_dict={"NODE_AUTH_TOKEN": node_auth_token}, + env={"NODE_AUTH_TOKEN": node_auth_token}, ) return publish From a20e9183ab1af534b2eed84462fcbe9024707424 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Sat, 3 Jun 2023 12:29:43 -0600 Subject: [PATCH 20/26] do not set registry url by default --- .github/workflows/.hatch-run.yml | 14 +++++--------- .github/workflows/publish.yml | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/.hatch-run.yml b/.github/workflows/.hatch-run.yml index 3d267be3a..2e55b8aec 100644 --- a/.github/workflows/.hatch-run.yml +++ b/.github/workflows/.hatch-run.yml @@ -17,9 +17,10 @@ on: required: false type: string default: '["3.x"]' - uses-secrets: - type: boolean - default: false + node-registry-url: + required: false + type: string + default: "" secrets: node-auth-token: required: false @@ -39,14 +40,9 @@ jobs: steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 - if: ${{ inputs.uses-secrets }}} - with: - node-version: "14.x" - registry-url: "https://registry.npmjs.org" - - uses: actions/setup-node@v2 - if: ${{ inputs.uses-secrets }}} with: node-version: "14.x" + registry-url: ${{ inputs.node-registry-url }} - name: Pin NPM Version run: npm install -g npm@8.19.3 - name: Use Python ${{ matrix.python-version }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e0a283275..e9271cbd5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,7 +13,7 @@ jobs: with: job-name: "publish" hatch-run: "publish" - uses-secrets: true + node-registry-url: "https://registry.npmjs.org" secrets: node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }} pypi-username: ${{ secrets.PYPI_USERNAME }} From e8691ff200f21b70d0dcbd7d7c2f1ddc19094052 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Sat, 3 Jun 2023 12:51:25 -0600 Subject: [PATCH 21/26] allow re-runs --- tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 26cb42687..db5bca5b6 100644 --- a/tasks.py +++ b/tasks.py @@ -128,7 +128,7 @@ def test_py(context: Context, no_cov: bool = False): in_py( context, "hatch build --hooks-only", - f"hatch run {'test' if no_cov else 'cov'} --maxfail=3", + f"hatch run {'test' if no_cov else 'cov'} --maxfail=3, --reruns=3", ) From e473a9031d2d9b7cbd7c6605cf07c11a8a48e5cf Mon Sep 17 00:00:00 2001 From: rmorshea Date: Sat, 3 Jun 2023 12:52:47 -0600 Subject: [PATCH 22/26] no need for extra build --- tasks.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tasks.py b/tasks.py index db5bca5b6..f22f6d0d5 100644 --- a/tasks.py +++ b/tasks.py @@ -127,7 +127,6 @@ def test_py(context: Context, no_cov: bool = False): """Run test suites""" in_py( context, - "hatch build --hooks-only", f"hatch run {'test' if no_cov else 'cov'} --maxfail=3, --reruns=3", ) From 412256eab8a45a52ddeeee81e053a525cd1c1853 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Sat, 3 Jun 2023 15:22:35 -0600 Subject: [PATCH 23/26] fix doc build and tests --- .github/workflows/.hatch-run.yml | 2 +- docs/Dockerfile | 42 +- docs/Makefile | 19 - docs/{ => docs_app}/__init__.py | 0 docs/{ => docs_app}/app.py | 13 +- scripts/live_docs.py => docs/docs_app/dev.py | 10 +- docs/{ => docs_app}/examples.py | 2 +- docs/docs_app/prod.py | 14 + docs/main.py | 9 + docs/make.bat | 35 - docs/poetry.lock | 2269 ++++++++++++++++++ docs/pyproject.toml | 23 + docs/source/_custom_js/package-lock.json | 25 +- docs/source/_custom_js/package.json | 2 +- docs/source/_exts/autogen_api_docs.py | 11 +- docs/source/_exts/reactpy_example.py | 11 +- docs/source/_exts/reactpy_view.py | 6 +- docs/source/conf.py | 17 +- pyproject.toml | 1 + requirements/build-docs.txt | 9 - requirements/build-pkg.txt | 3 - requirements/check-style.txt | 10 - scripts/one_example.py | 205 +- scripts/run_docs.py | 18 - src/py/reactpy/README.md | 23 + src/py/reactpy/pyproject.toml | 4 +- tasks.py | 55 +- 27 files changed, 2569 insertions(+), 269 deletions(-) delete mode 100644 docs/Makefile rename docs/{ => docs_app}/__init__.py (100%) rename docs/{ => docs_app}/app.py (79%) rename scripts/live_docs.py => docs/docs_app/dev.py (95%) rename docs/{ => docs_app}/examples.py (99%) create mode 100644 docs/docs_app/prod.py create mode 100644 docs/main.py delete mode 100644 docs/make.bat create mode 100644 docs/poetry.lock create mode 100644 docs/pyproject.toml delete mode 100644 requirements/build-docs.txt delete mode 100644 requirements/build-pkg.txt delete mode 100644 requirements/check-style.txt delete mode 100644 scripts/run_docs.py create mode 100644 src/py/reactpy/README.md diff --git a/.github/workflows/.hatch-run.yml b/.github/workflows/.hatch-run.yml index 2e55b8aec..b312869e4 100644 --- a/.github/workflows/.hatch-run.yml +++ b/.github/workflows/.hatch-run.yml @@ -50,7 +50,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install Python Dependencies - run: pip install hatch + run: pip install hatch poetry - name: Run Scripts env: NODE_AUTH_TOKEN: ${{ secrets.node-auth-token }} diff --git a/docs/Dockerfile b/docs/Dockerfile index 12e133d2c..ee28b3265 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -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 diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 69fe55ecf..000000000 --- a/docs/Makefile +++ /dev/null @@ -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) \ No newline at end of file diff --git a/docs/__init__.py b/docs/docs_app/__init__.py similarity index 100% rename from docs/__init__.py rename to docs/docs_app/__init__.py diff --git a/docs/app.py b/docs/docs_app/app.py similarity index 79% rename from docs/app.py rename to docs/docs_app/app.py index eb3f5dc93..3fe4669ff 100644 --- a/docs/app.py +++ b/docs/docs_app/app.py @@ -3,12 +3,15 @@ from sanic import Sanic, response -from docs.examples import get_normalized_example_name, load_examples +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 -HERE = Path(__file__).parent +THIS_DIR = Path(__file__).parent +DOCS_DIR = THIS_DIR.parent +DOCS_BUILD_DIR = DOCS_DIR / "build" + REACTPY_MODEL_SERVER_URL_PREFIX = "/_reactpy" logger = getLogger(__name__) @@ -38,10 +41,10 @@ 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(_): diff --git a/scripts/live_docs.py b/docs/docs_app/dev.py similarity index 95% rename from scripts/live_docs.py rename to docs/docs_app/dev.py index d1bc00f0c..5d661924d 100644 --- a/scripts/live_docs.py +++ b/docs/docs_app/dev.py @@ -13,7 +13,7 @@ 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 @@ -21,13 +21,11 @@ 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() @@ -104,7 +102,3 @@ def opener(): threading.Thread(target=opener, daemon=True).start() server.serve(port=portn, host=args.host, root=outdir) - - -if __name__ == "__main__": - main() diff --git a/docs/examples.py b/docs/docs_app/examples.py similarity index 99% rename from docs/examples.py rename to docs/docs_app/examples.py index 3a29ec691..a71a0b111 100644 --- a/docs/examples.py +++ b/docs/docs_app/examples.py @@ -10,7 +10,7 @@ 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 diff --git a/docs/docs_app/prod.py b/docs/docs_app/prod.py new file mode 100644 index 000000000..0acf12432 --- /dev/null +++ b/docs/docs_app/prod.py @@ -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"))), + ) diff --git a/docs/main.py b/docs/main.py new file mode 100644 index 000000000..e3181f393 --- /dev/null +++ b/docs/main.py @@ -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() diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 543c6b13b..000000000 --- a/docs/make.bat +++ /dev/null @@ -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 diff --git a/docs/poetry.lock b/docs/poetry.lock new file mode 100644 index 000000000..8e1daef24 --- /dev/null +++ b/docs/poetry.lock @@ -0,0 +1,2269 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "aiofiles" +version = "23.1.0" +description = "File support for asyncio." +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "aiofiles-23.1.0-py3-none-any.whl", hash = "sha256:9312414ae06472eb6f1d163f555e466a23aed1c8f60c30cccf7121dba2e53eb2"}, + {file = "aiofiles-23.1.0.tar.gz", hash = "sha256:edd247df9a19e0db16534d4baaf536d6609a43e1de5401d7a4c1c148753a1635"}, +] + +[[package]] +name = "alabaster" +version = "0.7.13" +description = "A configurable sidebar-enabled Sphinx theme" +optional = false +python-versions = ">=3.6" +files = [ + {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, + {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, +] + +[[package]] +name = "anyio" +version = "3.7.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.7" +files = [ + {file = "anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0"}, + {file = "anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce"}, +] + +[package.dependencies] +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx (>=6.1.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme", "sphinxcontrib-jquery"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (<0.22)"] + +[[package]] +name = "asgiref" +version = "3.7.2" +description = "ASGI specs, helper code, and adapters" +optional = false +python-versions = ">=3.7" +files = [ + {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, + {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} + +[package.extras] +tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] + +[[package]] +name = "babel" +version = "2.12.1" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, + {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, +] + +[[package]] +name = "beautifulsoup4" +version = "4.12.2" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, + {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "certifi" +version = "2023.5.7" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, + {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.1.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, + {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, +] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "colorlog" +version = "6.7.0" +description = "Add colours to the output of Python's logging module." +optional = false +python-versions = ">=3.6" +files = [ + {file = "colorlog-6.7.0-py2.py3-none-any.whl", hash = "sha256:0d33ca236784a1ba3ff9c532d4964126d8a2c44f1f0cb1d2b0728196f512f662"}, + {file = "colorlog-6.7.0.tar.gz", hash = "sha256:bd94bd21c1e13fac7bd3153f4bc3a7dc0eb0974b8bc2fdf1a989e474f6e582e5"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +development = ["black", "flake8", "mypy", "pytest", "types-colorama"] + +[[package]] +name = "contourpy" +version = "1.0.7" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.8" +files = [ + {file = "contourpy-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:95c3acddf921944f241b6773b767f1cbce71d03307270e2d769fd584d5d1092d"}, + {file = "contourpy-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc1464c97579da9f3ab16763c32e5c5d5bb5fa1ec7ce509a4ca6108b61b84fab"}, + {file = "contourpy-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8acf74b5d383414401926c1598ed77825cd530ac7b463ebc2e4f46638f56cce6"}, + {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c71fdd8f1c0f84ffd58fca37d00ca4ebaa9e502fb49825484da075ac0b0b803"}, + {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f99e9486bf1bb979d95d5cffed40689cb595abb2b841f2991fc894b3452290e8"}, + {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87f4d8941a9564cda3f7fa6a6cd9b32ec575830780677932abdec7bcb61717b0"}, + {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9e20e5a1908e18aaa60d9077a6d8753090e3f85ca25da6e25d30dc0a9e84c2c6"}, + {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a877ada905f7d69b2a31796c4b66e31a8068b37aa9b78832d41c82fc3e056ddd"}, + {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6381fa66866b0ea35e15d197fc06ac3840a9b2643a6475c8fff267db8b9f1e69"}, + {file = "contourpy-1.0.7-cp310-cp310-win32.whl", hash = "sha256:3c184ad2433635f216645fdf0493011a4667e8d46b34082f5a3de702b6ec42e3"}, + {file = "contourpy-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:3caea6365b13119626ee996711ab63e0c9d7496f65641f4459c60a009a1f3e80"}, + {file = "contourpy-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed33433fc3820263a6368e532f19ddb4c5990855e4886088ad84fd7c4e561c71"}, + {file = "contourpy-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:38e2e577f0f092b8e6774459317c05a69935a1755ecfb621c0a98f0e3c09c9a5"}, + {file = "contourpy-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ae90d5a8590e5310c32a7630b4b8618cef7563cebf649011da80874d0aa8f414"}, + {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130230b7e49825c98edf0b428b7aa1125503d91732735ef897786fe5452b1ec2"}, + {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58569c491e7f7e874f11519ef46737cea1d6eda1b514e4eb5ac7dab6aa864d02"}, + {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d43960d809c4c12508a60b66cb936e7ed57d51fb5e30b513934a4a23874fae"}, + {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:152fd8f730c31fd67fe0ffebe1df38ab6a669403da93df218801a893645c6ccc"}, + {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9056c5310eb1daa33fc234ef39ebfb8c8e2533f088bbf0bc7350f70a29bde1ac"}, + {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a9d7587d2fdc820cc9177139b56795c39fb8560f540bba9ceea215f1f66e1566"}, + {file = "contourpy-1.0.7-cp311-cp311-win32.whl", hash = "sha256:4ee3ee247f795a69e53cd91d927146fb16c4e803c7ac86c84104940c7d2cabf0"}, + {file = "contourpy-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:5caeacc68642e5f19d707471890f037a13007feba8427eb7f2a60811a1fc1350"}, + {file = "contourpy-1.0.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd7dc0e6812b799a34f6d12fcb1000539098c249c8da54f3566c6a6461d0dbad"}, + {file = "contourpy-1.0.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0f9d350b639db6c2c233d92c7f213d94d2e444d8e8fc5ca44c9706cf72193772"}, + {file = "contourpy-1.0.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e96a08b62bb8de960d3a6afbc5ed8421bf1a2d9c85cc4ea73f4bc81b4910500f"}, + {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:031154ed61f7328ad7f97662e48660a150ef84ee1bc8876b6472af88bf5a9b98"}, + {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e9ebb4425fc1b658e13bace354c48a933b842d53c458f02c86f371cecbedecc"}, + {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efb8f6d08ca7998cf59eaf50c9d60717f29a1a0a09caa46460d33b2924839dbd"}, + {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6c180d89a28787e4b73b07e9b0e2dac7741261dbdca95f2b489c4f8f887dd810"}, + {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b8d587cc39057d0afd4166083d289bdeff221ac6d3ee5046aef2d480dc4b503c"}, + {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:769eef00437edf115e24d87f8926955f00f7704bede656ce605097584f9966dc"}, + {file = "contourpy-1.0.7-cp38-cp38-win32.whl", hash = "sha256:62398c80ef57589bdbe1eb8537127321c1abcfdf8c5f14f479dbbe27d0322e66"}, + {file = "contourpy-1.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:57119b0116e3f408acbdccf9eb6ef19d7fe7baf0d1e9aaa5381489bc1aa56556"}, + {file = "contourpy-1.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:30676ca45084ee61e9c3da589042c24a57592e375d4b138bd84d8709893a1ba4"}, + {file = "contourpy-1.0.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e927b3868bd1e12acee7cc8f3747d815b4ab3e445a28d2e5373a7f4a6e76ba1"}, + {file = "contourpy-1.0.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:366a0cf0fc079af5204801786ad7a1c007714ee3909e364dbac1729f5b0849e5"}, + {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ba9bb365446a22411f0673abf6ee1fea3b2cf47b37533b970904880ceb72f3"}, + {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:71b0bf0c30d432278793d2141362ac853859e87de0a7dee24a1cea35231f0d50"}, + {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7281244c99fd7c6f27c1c6bfafba878517b0b62925a09b586d88ce750a016d2"}, + {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b6d0f9e1d39dbfb3977f9dd79f156c86eb03e57a7face96f199e02b18e58d32a"}, + {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7f6979d20ee5693a1057ab53e043adffa1e7418d734c1532e2d9e915b08d8ec2"}, + {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5dd34c1ae752515318224cba7fc62b53130c45ac6a1040c8b7c1a223c46e8967"}, + {file = "contourpy-1.0.7-cp39-cp39-win32.whl", hash = "sha256:c5210e5d5117e9aec8c47d9156d1d3835570dd909a899171b9535cb4a3f32693"}, + {file = "contourpy-1.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:60835badb5ed5f4e194a6f21c09283dd6e007664a86101431bf870d9e86266c4"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ce41676b3d0dd16dbcfabcc1dc46090aaf4688fd6e819ef343dbda5a57ef0161"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a011cf354107b47c58ea932d13b04d93c6d1d69b8b6dce885e642531f847566"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31a55dccc8426e71817e3fe09b37d6d48ae40aae4ecbc8c7ad59d6893569c436"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69f8ff4db108815addd900a74df665e135dbbd6547a8a69333a68e1f6e368ac2"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efe99298ba37e37787f6a2ea868265465410822f7bea163edcc1bd3903354ea9"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a1e97b86f73715e8670ef45292d7cc033548266f07d54e2183ecb3c87598888f"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc331c13902d0f50845099434cd936d49d7a2ca76cb654b39691974cb1e4812d"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24847601071f740837aefb730e01bd169fbcaa610209779a78db7ebb6e6a7051"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abf298af1e7ad44eeb93501e40eb5a67abbf93b5d90e468d01fc0c4451971afa"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:64757f6460fc55d7e16ed4f1de193f362104285c667c112b50a804d482777edd"}, + {file = "contourpy-1.0.7.tar.gz", hash = "sha256:d8165a088d31798b59e91117d1f5fc3df8168d8b48c4acc10fc0df0d0bdbcc5e"}, +] + +[package.dependencies] +numpy = ">=1.16" + +[package.extras] +bokeh = ["bokeh", "chromedriver", "selenium"] +docs = ["furo", "sphinx-copybutton"] +mypy = ["contourpy[bokeh]", "docutils-stubs", "mypy (==0.991)", "types-Pillow"] +test = ["Pillow", "matplotlib", "pytest"] +test-no-images = ["pytest"] + +[[package]] +name = "cycler" +version = "0.11.0" +description = "Composable style cycles" +optional = false +python-versions = ">=3.6" +files = [ + {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"}, + {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"}, +] + +[[package]] +name = "docutils" +version = "0.17.1" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, + {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.1.1" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, + {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "fastapi" +version = "0.96.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.7" +files = [ + {file = "fastapi-0.96.0-py3-none-any.whl", hash = "sha256:b8e11fe81e81eab4e1504209917338e0b80f783878a42c2b99467e5e1019a1e9"}, + {file = "fastapi-0.96.0.tar.gz", hash = "sha256:71232d47c2787446991c81c41c249f8a16238d52d779c0e6b43927d3773dbe3c"}, +] + +[package.dependencies] +pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" +starlette = ">=0.27.0,<0.28.0" + +[package.extras] +all = ["email-validator (>=1.1.1)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +dev = ["pre-commit (>=2.17.0,<3.0.0)", "ruff (==0.0.138)", "uvicorn[standard] (>=0.12.0,<0.21.0)"] +doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer-cli (>=0.0.13,<0.0.14)", "typer[all] (>=0.6.1,<0.8.0)"] +test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==23.1.0)", "coverage[toml] (>=6.5.0,<8.0)", "databases[sqlite] (>=0.3.2,<0.7.0)", "email-validator (>=1.1.1,<2.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.23.0,<0.24.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.982)", "orjson (>=3.2.1,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=7.1.3,<8.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.7)", "pyyaml (>=5.3.1,<7.0.0)", "ruff (==0.0.138)", "sqlalchemy (>=1.3.18,<1.4.43)", "types-orjson (==3.6.2)", "types-ujson (==5.7.0.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"] + +[[package]] +name = "fastjsonschema" +version = "2.17.1" +description = "Fastest Python implementation of JSON schema" +optional = false +python-versions = "*" +files = [ + {file = "fastjsonschema-2.17.1-py3-none-any.whl", hash = "sha256:4b90b252628ca695280924d863fe37234eebadc29c5360d322571233dc9746e0"}, + {file = "fastjsonschema-2.17.1.tar.gz", hash = "sha256:f4eeb8a77cef54861dbf7424ac8ce71306f12cbb086c45131bcba2c6a4f726e3"}, +] + +[package.extras] +devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] + +[[package]] +name = "flask" +version = "2.1.3" +description = "A simple framework for building complex web applications." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Flask-2.1.3-py3-none-any.whl", hash = "sha256:9013281a7402ad527f8fd56375164f3aa021ecfaff89bfe3825346c24f87e04c"}, + {file = "Flask-2.1.3.tar.gz", hash = "sha256:15972e5017df0575c3d6c090ba168b6db90259e620ac8d7ea813a396bad5b6cb"}, +] + +[package.dependencies] +click = ">=8.0" +importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} +itsdangerous = ">=2.0" +Jinja2 = ">=3.0" +Werkzeug = ">=2.0" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "flask-cors" +version = "3.0.10" +description = "A Flask extension adding a decorator for CORS support" +optional = false +python-versions = "*" +files = [ + {file = "Flask-Cors-3.0.10.tar.gz", hash = "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"}, + {file = "Flask_Cors-3.0.10-py2.py3-none-any.whl", hash = "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438"}, +] + +[package.dependencies] +Flask = ">=0.9" +Six = "*" + +[[package]] +name = "flask-sock" +version = "0.6.0" +description = "WebSocket support for Flask" +optional = false +python-versions = ">=3.6" +files = [ + {file = "flask-sock-0.6.0.tar.gz", hash = "sha256:435cf81bb497ac7622cd1dda554fbfa3e369e629daea0a1d21b73a24f1bd6229"}, + {file = "flask_sock-0.6.0-py3-none-any.whl", hash = "sha256:593fffb186928080a5b5b03d717efc56dac2d5ed690ce6bfff333b3597a2f518"}, +] + +[package.dependencies] +flask = ">=2" +simple-websocket = ">=0.5.1" + +[[package]] +name = "fonttools" +version = "4.39.4" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fonttools-4.39.4-py3-none-any.whl", hash = "sha256:106caf6167c4597556b31a8d9175a3fdc0356fdcd70ab19973c3b0d4c893c461"}, + {file = "fonttools-4.39.4.zip", hash = "sha256:dba8d7cdb8e2bac1b3da28c5ed5960de09e59a2fe7e63bb73f5a59e57b0430d2"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "scipy"] +lxml = ["lxml (>=4.0,<5)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=15.0.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + +[[package]] +name = "furo" +version = "2022.4.7" +description = "A clean customisable Sphinx documentation theme." +optional = false +python-versions = ">=3.6" +files = [ + {file = "furo-2022.4.7-py3-none-any.whl", hash = "sha256:7f3e3d2fb977483590f8ecb2c2cd511bd82661b79c18efb24de9558bc9cdf2d7"}, + {file = "furo-2022.4.7.tar.gz", hash = "sha256:96204ab7cd047e4b6c523996e0279c4c629a8fc31f4f109b2efd470c17f49c80"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +pygments = ">=2.7,<3.0" +sphinx = ">=4.0,<5.0" + +[[package]] +name = "greenlet" +version = "2.0.2" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +files = [ + {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, + {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, + {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, + {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, + {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, + {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, + {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, + {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, + {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, + {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, + {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, + {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, + {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, + {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, + {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, + {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, + {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, + {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, + {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, + {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, + {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, + {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, + {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, + {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, + {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, + {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, +] + +[package.extras] +docs = ["Sphinx", "docutils (<0.18)"] +test = ["objgraph", "psutil"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "html5tagger" +version = "1.3.0" +description = "Pythonic HTML generation/templating (no template files)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "html5tagger-1.3.0-py3-none-any.whl", hash = "sha256:ce14313515edffec8ed8a36c5890d023922641171b4e6e5774ad1a74998f5351"}, + {file = "html5tagger-1.3.0.tar.gz", hash = "sha256:84fa3dfb49e5c83b79bbd856ab7b1de8e2311c3bb46a8be925f119e3880a8da9"}, +] + +[[package]] +name = "httptools" +version = "0.5.0" +description = "A collection of framework independent HTTP protocol utils." +optional = false +python-versions = ">=3.5.0" +files = [ + {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8f470c79061599a126d74385623ff4744c4e0f4a0997a353a44923c0b561ee51"}, + {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e90491a4d77d0cb82e0e7a9cb35d86284c677402e4ce7ba6b448ccc7325c5421"}, + {file = "httptools-0.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1d2357f791b12d86faced7b5736dea9ef4f5ecdc6c3f253e445ee82da579449"}, + {file = "httptools-0.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f90cd6fd97c9a1b7fe9215e60c3bd97336742a0857f00a4cb31547bc22560c2"}, + {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5230a99e724a1bdbbf236a1b58d6e8504b912b0552721c7c6b8570925ee0ccde"}, + {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3a47a34f6015dd52c9eb629c0f5a8a5193e47bf2a12d9a3194d231eaf1bc451a"}, + {file = "httptools-0.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:24bb4bb8ac3882f90aa95403a1cb48465de877e2d5298ad6ddcfdebec060787d"}, + {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e67d4f8734f8054d2c4858570cc4b233bf753f56e85217de4dfb2495904cf02e"}, + {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e5eefc58d20e4c2da82c78d91b2906f1a947ef42bd668db05f4ab4201a99f49"}, + {file = "httptools-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0297822cea9f90a38df29f48e40b42ac3d48a28637368f3ec6d15eebefd182f9"}, + {file = "httptools-0.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:557be7fbf2bfa4a2ec65192c254e151684545ebab45eca5d50477d562c40f986"}, + {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:54465401dbbec9a6a42cf737627fb0f014d50dc7365a6b6cd57753f151a86ff0"}, + {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4d9ebac23d2de960726ce45f49d70eb5466725c0087a078866043dad115f850f"}, + {file = "httptools-0.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:e8a34e4c0ab7b1ca17b8763613783e2458e77938092c18ac919420ab8655c8c1"}, + {file = "httptools-0.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f659d7a48401158c59933904040085c200b4be631cb5f23a7d561fbae593ec1f"}, + {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1616b3ba965cd68e6f759eeb5d34fbf596a79e84215eeceebf34ba3f61fdc7"}, + {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3625a55886257755cb15194efbf209584754e31d336e09e2ffe0685a76cb4b60"}, + {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:72ad589ba5e4a87e1d404cc1cb1b5780bfcb16e2aec957b88ce15fe879cc08ca"}, + {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:850fec36c48df5a790aa735417dca8ce7d4b48d59b3ebd6f83e88a8125cde324"}, + {file = "httptools-0.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f222e1e9d3f13b68ff8a835574eda02e67277d51631d69d7cf7f8e07df678c86"}, + {file = "httptools-0.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3cb8acf8f951363b617a8420768a9f249099b92e703c052f9a51b66342eea89b"}, + {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550059885dc9c19a072ca6d6735739d879be3b5959ec218ba3e013fd2255a11b"}, + {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a04fe458a4597aa559b79c7f48fe3dceabef0f69f562daf5c5e926b153817281"}, + {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d0c1044bce274ec6711f0770fd2d5544fe392591d204c68328e60a46f88843b"}, + {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c6eeefd4435055a8ebb6c5cc36111b8591c192c56a95b45fe2af22d9881eee25"}, + {file = "httptools-0.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5b65be160adcd9de7a7e6413a4966665756e263f0d5ddeffde277ffeee0576a5"}, + {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fe9c766a0c35b7e3d6b6939393c8dfdd5da3ac5dec7f971ec9134f284c6c36d6"}, + {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85b392aba273566c3d5596a0a490978c085b79700814fb22bfd537d381dd230c"}, + {file = "httptools-0.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5e3088f4ed33947e16fd865b8200f9cfae1144f41b64a8cf19b599508e096bc"}, + {file = "httptools-0.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c2a56b6aad7cc8f5551d8e04ff5a319d203f9d870398b94702300de50190f63"}, + {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b571b281a19762adb3f48a7731f6842f920fa71108aff9be49888320ac3e24d"}, + {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa47ffcf70ba6f7848349b8a6f9b481ee0f7637931d91a9860a1838bfc586901"}, + {file = "httptools-0.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:bede7ee075e54b9a5bde695b4fc8f569f30185891796b2e4e09e2226801d09bd"}, + {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:64eba6f168803a7469866a9c9b5263a7463fa8b7a25b35e547492aa7322036b6"}, + {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b098e4bb1174096a93f48f6193e7d9aa7071506a5877da09a783509ca5fff42"}, + {file = "httptools-0.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9423a2de923820c7e82e18980b937893f4aa8251c43684fa1772e341f6e06887"}, + {file = "httptools-0.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca1b7becf7d9d3ccdbb2f038f665c0f4857e08e1d8481cbcc1a86a0afcfb62b2"}, + {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:50d4613025f15f4b11f1c54bbed4761c0020f7f921b95143ad6d58c151198142"}, + {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8ffce9d81c825ac1deaa13bc9694c0562e2840a48ba21cfc9f3b4c922c16f372"}, + {file = "httptools-0.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:1af91b3650ce518d226466f30bbba5b6376dbd3ddb1b2be8b0658c6799dd450b"}, + {file = "httptools-0.5.0.tar.gz", hash = "sha256:295874861c173f9101960bba332429bb77ed4dcd8cdf5cee9922eb00e4f6bc09"}, +] + +[package.extras] +test = ["Cython (>=0.29.24,<0.30.0)"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + +[[package]] +name = "importlib-metadata" +version = "6.6.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-6.6.0-py3-none-any.whl", hash = "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed"}, + {file = "importlib_metadata-6.6.0.tar.gz", hash = "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + +[[package]] +name = "importlib-resources" +version = "5.12.0" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"}, + {file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[[package]] +name = "itsdangerous" +version = "2.1.2" +description = "Safely pass data to untrusted environments and back." +optional = false +python-versions = ">=3.7" +files = [ + {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, + {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, +] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jsonpatch" +version = "1.32" +description = "Apply JSON-Patches (RFC 6902)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "jsonpatch-1.32-py2.py3-none-any.whl", hash = "sha256:26ac385719ac9f54df8a2f0827bb8253aa3ea8ab7b3368457bcdb8c14595a397"}, + {file = "jsonpatch-1.32.tar.gz", hash = "sha256:b6ddfe6c3db30d81a96aaeceb6baf916094ffa23d7dd5fa2c13e13f8b6e600c2"}, +] + +[package.dependencies] +jsonpointer = ">=1.9" + +[[package]] +name = "jsonpointer" +version = "2.3" +description = "Identify specific nodes in a JSON document (RFC 6901)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "jsonpointer-2.3-py2.py3-none-any.whl", hash = "sha256:51801e558539b4e9cd268638c078c6c5746c9ac96bc38152d443400e4f3793e9"}, + {file = "jsonpointer-2.3.tar.gz", hash = "sha256:97cba51526c829282218feb99dab1b1e6bdf8efd1c43dc9d57be093c0d69c99a"}, +] + +[[package]] +name = "kiwisolver" +version = "1.4.4" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.7" +files = [ + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6"}, + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c"}, + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32"}, + {file = "kiwisolver-1.4.4-cp310-cp310-win32.whl", hash = "sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408"}, + {file = "kiwisolver-1.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4"}, + {file = "kiwisolver-1.4.4-cp311-cp311-win32.whl", hash = "sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e"}, + {file = "kiwisolver-1.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-win32.whl", hash = "sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c"}, + {file = "kiwisolver-1.4.4-cp38-cp38-win32.whl", hash = "sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191"}, + {file = "kiwisolver-1.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9"}, + {file = "kiwisolver-1.4.4-cp39-cp39-win32.whl", hash = "sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea"}, + {file = "kiwisolver-1.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b"}, + {file = "kiwisolver-1.4.4.tar.gz", hash = "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955"}, +] + +[[package]] +name = "livereload" +version = "2.6.3" +description = "Python LiveReload is an awesome tool for web developers" +optional = false +python-versions = "*" +files = [ + {file = "livereload-2.6.3-py2.py3-none-any.whl", hash = "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4"}, + {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, +] + +[package.dependencies] +six = "*" +tornado = {version = "*", markers = "python_version > \"2.7\""} + +[[package]] +name = "lxml" +version = "4.9.2" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +files = [ + {file = "lxml-4.9.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:76cf573e5a365e790396a5cc2b909812633409306c6531a6877c59061e42c4f2"}, + {file = "lxml-4.9.2-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1f42b6921d0e81b1bcb5e395bc091a70f41c4d4e55ba99c6da2b31626c44892"}, + {file = "lxml-4.9.2-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9f102706d0ca011de571de32c3247c6476b55bb6bc65a20f682f000b07a4852a"}, + {file = "lxml-4.9.2-cp27-cp27m-win32.whl", hash = "sha256:8d0b4612b66ff5d62d03bcaa043bb018f74dfea51184e53f067e6fdcba4bd8de"}, + {file = "lxml-4.9.2-cp27-cp27m-win_amd64.whl", hash = "sha256:4c8f293f14abc8fd3e8e01c5bd86e6ed0b6ef71936ded5bf10fe7a5efefbaca3"}, + {file = "lxml-4.9.2-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2899456259589aa38bfb018c364d6ae7b53c5c22d8e27d0ec7609c2a1ff78b50"}, + {file = "lxml-4.9.2-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6749649eecd6a9871cae297bffa4ee76f90b4504a2a2ab528d9ebe912b101975"}, + {file = "lxml-4.9.2-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:a08cff61517ee26cb56f1e949cca38caabe9ea9fbb4b1e10a805dc39844b7d5c"}, + {file = "lxml-4.9.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:85cabf64adec449132e55616e7ca3e1000ab449d1d0f9d7f83146ed5bdcb6d8a"}, + {file = "lxml-4.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8340225bd5e7a701c0fa98284c849c9b9fc9238abf53a0ebd90900f25d39a4e4"}, + {file = "lxml-4.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:1ab8f1f932e8f82355e75dda5413a57612c6ea448069d4fb2e217e9a4bed13d4"}, + {file = "lxml-4.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:699a9af7dffaf67deeae27b2112aa06b41c370d5e7633e0ee0aea2e0b6c211f7"}, + {file = "lxml-4.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184"}, + {file = "lxml-4.9.2-cp310-cp310-win32.whl", hash = "sha256:d02a5399126a53492415d4906ab0ad0375a5456cc05c3fc0fc4ca11771745cda"}, + {file = "lxml-4.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab"}, + {file = "lxml-4.9.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9"}, + {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf"}, + {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380"}, + {file = "lxml-4.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3efea981d956a6f7173b4659849f55081867cf897e719f57383698af6f618a92"}, + {file = "lxml-4.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:df0623dcf9668ad0445e0558a21211d4e9a149ea8f5666917c8eeec515f0a6d1"}, + {file = "lxml-4.9.2-cp311-cp311-win32.whl", hash = "sha256:da248f93f0418a9e9d94b0080d7ebc407a9a5e6d0b57bb30db9b5cc28de1ad33"}, + {file = "lxml-4.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:3818b8e2c4b5148567e1b09ce739006acfaa44ce3156f8cbbc11062994b8e8dd"}, + {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0"}, + {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e"}, + {file = "lxml-4.9.2-cp35-cp35m-win32.whl", hash = "sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df"}, + {file = "lxml-4.9.2-cp35-cp35m-win_amd64.whl", hash = "sha256:998c7c41910666d2976928c38ea96a70d1aa43be6fe502f21a651e17483a43c5"}, + {file = "lxml-4.9.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f49e52d174375a7def9915c9f06ec4e569d235ad428f70751765f48d5926678c"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:36c3c175d34652a35475a73762b545f4527aec044910a651d2bf50de9c3352b1"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a35f8b7fa99f90dd2f5dc5a9fa12332642f087a7641289ca6c40d6e1a2637d8e"}, + {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74"}, + {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38"}, + {file = "lxml-4.9.2-cp36-cp36m-win32.whl", hash = "sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5"}, + {file = "lxml-4.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:3ab9fa9d6dc2a7f29d7affdf3edebf6ece6fb28a6d80b14c3b2fb9d39b9322c3"}, + {file = "lxml-4.9.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c9ec3eaf616d67db0764b3bb983962b4f385a1f08304fd30c7283954e6a7869b"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2a29ba94d065945944016b6b74e538bdb1751a1db6ffb80c9d3c2e40d6fa9894"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a82d05da00a58b8e4c0008edbc8a4b6ec5a4bc1e2ee0fb6ed157cf634ed7fa45"}, + {file = "lxml-4.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:223f4232855ade399bd409331e6ca70fb5578efef22cf4069a6090acc0f53c0e"}, + {file = "lxml-4.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d17bc7c2ccf49c478c5bdd447594e82692c74222698cfc9b5daae7ae7e90743b"}, + {file = "lxml-4.9.2-cp37-cp37m-win32.whl", hash = "sha256:b64d891da92e232c36976c80ed7ebb383e3f148489796d8d31a5b6a677825efe"}, + {file = "lxml-4.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a0a336d6d3e8b234a3aae3c674873d8f0e720b76bc1d9416866c41cd9500ffb9"}, + {file = "lxml-4.9.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:da4dd7c9c50c059aba52b3524f84d7de956f7fef88f0bafcf4ad7dde94a064e8"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:821b7f59b99551c69c85a6039c65b75f5683bdc63270fec660f75da67469ca24"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e5168986b90a8d1f2f9dc1b841467c74221bd752537b99761a93d2d981e04889"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8e20cb5a47247e383cf4ff523205060991021233ebd6f924bca927fcf25cf86f"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:13598ecfbd2e86ea7ae45ec28a2a54fb87ee9b9fdb0f6d343297d8e548392c03"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:880bbbcbe2fca64e2f4d8e04db47bcdf504936fa2b33933efd945e1b429bea8c"}, + {file = "lxml-4.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d2278d59425777cfcb19735018d897ca8303abe67cc735f9f97177ceff8027f"}, + {file = "lxml-4.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5344a43228767f53a9df6e5b253f8cdca7dfc7b7aeae52551958192f56d98457"}, + {file = "lxml-4.9.2-cp38-cp38-win32.whl", hash = "sha256:925073b2fe14ab9b87e73f9a5fde6ce6392da430f3004d8b72cc86f746f5163b"}, + {file = "lxml-4.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:9b22c5c66f67ae00c0199f6055705bc3eb3fcb08d03d2ec4059a2b1b25ed48d7"}, + {file = "lxml-4.9.2-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5f50a1c177e2fa3ee0667a5ab79fdc6b23086bc8b589d90b93b4bd17eb0e64d1"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:090c6543d3696cbe15b4ac6e175e576bcc3f1ccfbba970061b7300b0c15a2140"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:63da2ccc0857c311d764e7d3d90f429c252e83b52d1f8f1d1fe55be26827d1f4"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:5b4545b8a40478183ac06c073e81a5ce4cf01bf1734962577cf2bb569a5b3bbf"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2e430cd2824f05f2d4f687701144556646bae8f249fd60aa1e4c768ba7018947"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6804daeb7ef69e7b36f76caddb85cccd63d0c56dedb47555d2fc969e2af6a1a5"}, + {file = "lxml-4.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a6e441a86553c310258aca15d1c05903aaf4965b23f3bc2d55f200804e005ee5"}, + {file = "lxml-4.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ca34efc80a29351897e18888c71c6aca4a359247c87e0b1c7ada14f0ab0c0fb2"}, + {file = "lxml-4.9.2-cp39-cp39-win32.whl", hash = "sha256:6b418afe5df18233fc6b6093deb82a32895b6bb0b1155c2cdb05203f583053f1"}, + {file = "lxml-4.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:f1496ea22ca2c830cbcbd473de8f114a320da308438ae65abad6bab7867fe38f"}, + {file = "lxml-4.9.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b264171e3143d842ded311b7dccd46ff9ef34247129ff5bf5066123c55c2431c"}, + {file = "lxml-4.9.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0dc313ef231edf866912e9d8f5a042ddab56c752619e92dfd3a2c277e6a7299a"}, + {file = "lxml-4.9.2-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:16efd54337136e8cd72fb9485c368d91d77a47ee2d42b057564aae201257d419"}, + {file = "lxml-4.9.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0f2b1e0d79180f344ff9f321327b005ca043a50ece8713de61d1cb383fb8ac05"}, + {file = "lxml-4.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:7b770ed79542ed52c519119473898198761d78beb24b107acf3ad65deae61f1f"}, + {file = "lxml-4.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efa29c2fe6b4fdd32e8ef81c1528506895eca86e1d8c4657fda04c9b3786ddf9"}, + {file = "lxml-4.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7e91ee82f4199af8c43d8158024cbdff3d931df350252288f0d4ce656df7f3b5"}, + {file = "lxml-4.9.2-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b23e19989c355ca854276178a0463951a653309fb8e57ce674497f2d9f208746"}, + {file = "lxml-4.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:01d36c05f4afb8f7c20fd9ed5badca32a2029b93b1750f571ccc0b142531caf7"}, + {file = "lxml-4.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7b515674acfdcadb0eb5d00d8a709868173acece5cb0be3dd165950cbfdf5409"}, + {file = "lxml-4.9.2.tar.gz", hash = "sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=0.29.7)"] + +[[package]] +name = "markupsafe" +version = "2.0.1" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.6" +files = [ + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, + {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, +] + +[[package]] +name = "matplotlib" +version = "3.7.1" +description = "Python plotting package" +optional = false +python-versions = ">=3.8" +files = [ + {file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:95cbc13c1fc6844ab8812a525bbc237fa1470863ff3dace7352e910519e194b1"}, + {file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:08308bae9e91aca1ec6fd6dda66237eef9f6294ddb17f0d0b3c863169bf82353"}, + {file = "matplotlib-3.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:544764ba51900da4639c0f983b323d288f94f65f4024dc40ecb1542d74dc0500"}, + {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d94989191de3fcc4e002f93f7f1be5da476385dde410ddafbb70686acf00ea"}, + {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99bc9e65901bb9a7ce5e7bb24af03675cbd7c70b30ac670aa263240635999a4"}, + {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb7d248c34a341cd4c31a06fd34d64306624c8cd8d0def7abb08792a5abfd556"}, + {file = "matplotlib-3.7.1-cp310-cp310-win32.whl", hash = "sha256:ce463ce590f3825b52e9fe5c19a3c6a69fd7675a39d589e8b5fbe772272b3a24"}, + {file = "matplotlib-3.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:3d7bc90727351fb841e4d8ae620d2d86d8ed92b50473cd2b42ce9186104ecbba"}, + {file = "matplotlib-3.7.1-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:770a205966d641627fd5cf9d3cb4b6280a716522cd36b8b284a8eb1581310f61"}, + {file = "matplotlib-3.7.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f67bfdb83a8232cb7a92b869f9355d677bce24485c460b19d01970b64b2ed476"}, + {file = "matplotlib-3.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2bf092f9210e105f414a043b92af583c98f50050559616930d884387d0772aba"}, + {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89768d84187f31717349c6bfadc0e0d8c321e8eb34522acec8a67b1236a66332"}, + {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83111e6388dec67822e2534e13b243cc644c7494a4bb60584edbff91585a83c6"}, + {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a867bf73a7eb808ef2afbca03bcdb785dae09595fbe550e1bab0cd023eba3de0"}, + {file = "matplotlib-3.7.1-cp311-cp311-win32.whl", hash = "sha256:fbdeeb58c0cf0595efe89c05c224e0a502d1aa6a8696e68a73c3efc6bc354304"}, + {file = "matplotlib-3.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:c0bd19c72ae53e6ab979f0ac6a3fafceb02d2ecafa023c5cca47acd934d10be7"}, + {file = "matplotlib-3.7.1-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:6eb88d87cb2c49af00d3bbc33a003f89fd9f78d318848da029383bfc08ecfbfb"}, + {file = "matplotlib-3.7.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:cf0e4f727534b7b1457898c4f4ae838af1ef87c359b76dcd5330fa31893a3ac7"}, + {file = "matplotlib-3.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:46a561d23b91f30bccfd25429c3c706afe7d73a5cc64ef2dfaf2b2ac47c1a5dc"}, + {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8704726d33e9aa8a6d5215044b8d00804561971163563e6e6591f9dcf64340cc"}, + {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4cf327e98ecf08fcbb82685acaf1939d3338548620ab8dfa02828706402c34de"}, + {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:617f14ae9d53292ece33f45cba8503494ee199a75b44de7717964f70637a36aa"}, + {file = "matplotlib-3.7.1-cp38-cp38-win32.whl", hash = "sha256:7c9a4b2da6fac77bcc41b1ea95fadb314e92508bf5493ceff058e727e7ecf5b0"}, + {file = "matplotlib-3.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:14645aad967684e92fc349493fa10c08a6da514b3d03a5931a1bac26e6792bd1"}, + {file = "matplotlib-3.7.1-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:81a6b377ea444336538638d31fdb39af6be1a043ca5e343fe18d0f17e098770b"}, + {file = "matplotlib-3.7.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:28506a03bd7f3fe59cd3cd4ceb2a8d8a2b1db41afede01f66c42561b9be7b4b7"}, + {file = "matplotlib-3.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8c587963b85ce41e0a8af53b9b2de8dddbf5ece4c34553f7bd9d066148dc719c"}, + {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8bf26ade3ff0f27668989d98c8435ce9327d24cffb7f07d24ef609e33d582439"}, + {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:def58098f96a05f90af7e92fd127d21a287068202aa43b2a93476170ebd99e87"}, + {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f883a22a56a84dba3b588696a2b8a1ab0d2c3d41be53264115c71b0a942d8fdb"}, + {file = "matplotlib-3.7.1-cp39-cp39-win32.whl", hash = "sha256:4f99e1b234c30c1e9714610eb0c6d2f11809c9c78c984a613ae539ea2ad2eb4b"}, + {file = "matplotlib-3.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:3ba2af245e36990facf67fde840a760128ddd71210b2ab6406e640188d69d136"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3032884084f541163f295db8a6536e0abb0db464008fadca6c98aaf84ccf4717"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a2cb34336110e0ed8bb4f650e817eed61fa064acbefeb3591f1b33e3a84fd96"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b867e2f952ed592237a1828f027d332d8ee219ad722345b79a001f49df0936eb"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:57bfb8c8ea253be947ccb2bc2d1bb3862c2bccc662ad1b4626e1f5e004557042"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:438196cdf5dc8d39b50a45cb6e3f6274edbcf2254f85fa9b895bf85851c3a613"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:21e9cff1a58d42e74d01153360de92b326708fb205250150018a52c70f43c290"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d4725d70b7c03e082bbb8a34639ede17f333d7247f56caceb3801cb6ff703d"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:97cc368a7268141afb5690760921765ed34867ffb9655dd325ed207af85c7529"}, + {file = "matplotlib-3.7.1.tar.gz", hash = "sha256:7b73305f25eab4541bd7ee0b96d87e53ae9c9f1823be5659b806cd85786fe882"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} +kiwisolver = ">=1.0.1" +numpy = ">=1.20" +packaging = ">=20.0" +pillow = ">=6.2.0" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[[package]] +name = "multidict" +version = "6.0.4" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "numpy" +version = "1.24.3" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "numpy-1.24.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570"}, + {file = "numpy-1.24.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7"}, + {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8535303847b89aa6b0f00aa1dc62867b5a32923e4d1681a35b5eef2d9591a463"}, + {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d926b52ba1367f9acb76b0df6ed21f0b16a1ad87c6720a1121674e5cf63e2b6"}, + {file = "numpy-1.24.3-cp310-cp310-win32.whl", hash = "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b"}, + {file = "numpy-1.24.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f23af8c16022663a652d3b25dcdc272ac3f83c3af4c02eb8b824e6b3ab9d7"}, + {file = "numpy-1.24.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a7721ec204d3a237225db3e194c25268faf92e19338a35f3a224469cb6039a3"}, + {file = "numpy-1.24.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d6cc757de514c00b24ae8cf5c876af2a7c3df189028d68c0cb4eaa9cd5afc2bf"}, + {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76e3f4e85fc5d4fd311f6e9b794d0c00e7002ec122be271f2019d63376f1d385"}, + {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1d3c026f57ceaad42f8231305d4653d5f05dc6332a730ae5c0bea3513de0950"}, + {file = "numpy-1.24.3-cp311-cp311-win32.whl", hash = "sha256:c91c4afd8abc3908e00a44b2672718905b8611503f7ff87390cc0ac3423fb096"}, + {file = "numpy-1.24.3-cp311-cp311-win_amd64.whl", hash = "sha256:5342cf6aad47943286afa6f1609cad9b4266a05e7f2ec408e2cf7aea7ff69d80"}, + {file = "numpy-1.24.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7776ea65423ca6a15255ba1872d82d207bd1e09f6d0894ee4a64678dd2204078"}, + {file = "numpy-1.24.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ae8d0be48d1b6ed82588934aaaa179875e7dc4f3d84da18d7eae6eb3f06c242c"}, + {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c"}, + {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4749e053a29364d3452c034827102ee100986903263e89884922ef01a0a6fd2f"}, + {file = "numpy-1.24.3-cp38-cp38-win32.whl", hash = "sha256:d933fabd8f6a319e8530d0de4fcc2e6a61917e0b0c271fded460032db42a0fe4"}, + {file = "numpy-1.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:56e48aec79ae238f6e4395886b5eaed058abb7231fb3361ddd7bfdf4eed54289"}, + {file = "numpy-1.24.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4719d5aefb5189f50887773699eaf94e7d1e02bf36c1a9d353d9f46703758ca4"}, + {file = "numpy-1.24.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ec87a7084caa559c36e0a2309e4ecb1baa03b687201d0a847c8b0ed476a7187"}, + {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea8282b9bcfe2b5e7d491d0bf7f3e2da29700cec05b49e64d6246923329f2b02"}, + {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210461d87fb02a84ef243cac5e814aad2b7f4be953b32cb53327bb49fd77fbb4"}, + {file = "numpy-1.24.3-cp39-cp39-win32.whl", hash = "sha256:784c6da1a07818491b0ffd63c6bbe5a33deaa0e25a20e1b3ea20cf0e43f8046c"}, + {file = "numpy-1.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:d5036197ecae68d7f491fcdb4df90082b0d4960ca6599ba2659957aafced7c17"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:352ee00c7f8387b44d19f4cada524586f07379c0d49270f87233983bc5087ca0"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d6acc2e7524c9955e5c903160aa4ea083736fde7e91276b0e5d98e6332812"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:35400e6a8d102fd07c71ed7dcadd9eb62ee9a6e84ec159bd48c28235bbb0f8e4"}, + {file = "numpy-1.24.3.tar.gz", hash = "sha256:ab344f1bf21f140adab8e47fdbc7c35a477dc01408791f8ba00d018dd0bc5155"}, +] + +[[package]] +name = "packaging" +version = "23.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] + +[[package]] +name = "pillow" +version = "9.5.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16"}, + {file = "Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a"}, + {file = "Pillow-9.5.0-cp310-cp310-win32.whl", hash = "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44"}, + {file = "Pillow-9.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296"}, + {file = "Pillow-9.5.0-cp311-cp311-win32.whl", hash = "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec"}, + {file = "Pillow-9.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4"}, + {file = "Pillow-9.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089"}, + {file = "Pillow-9.5.0-cp312-cp312-win32.whl", hash = "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb"}, + {file = "Pillow-9.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b"}, + {file = "Pillow-9.5.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47"}, + {file = "Pillow-9.5.0-cp37-cp37m-win32.whl", hash = "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7"}, + {file = "Pillow-9.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f"}, + {file = "Pillow-9.5.0-cp38-cp38-win32.whl", hash = "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc"}, + {file = "Pillow-9.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865"}, + {file = "Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964"}, + {file = "Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799"}, + {file = "Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "playwright" +version = "1.34.0" +description = "A high-level API to automate web browsers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "playwright-1.34.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:69bb9b3296e366a23a99277b4c7673cb54ce71a3f5d630f114f7701b61f98f25"}, + {file = "playwright-1.34.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:402d946631c8458436e099d7731bbf54cf79c9e62e3acae0ea8421e72616926b"}, + {file = "playwright-1.34.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:462251cda0fcbb273497d357dbe14b11e43ebceb0bac9b892beda041ff209aa9"}, + {file = "playwright-1.34.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:a8ba124ea302596a03a66993cd500484fb255cbc10fe0757fa4d49f974267a80"}, + {file = "playwright-1.34.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf0cb6aac49d24335fe361868aea72b11f276a95e7809f1a5d1c69b4120c46ac"}, + {file = "playwright-1.34.0-py3-none-win32.whl", hash = "sha256:c50fef189d87243cc09ae0feb8e417fbe434359ccbcc863fb19ba06d46d31c33"}, + {file = "playwright-1.34.0-py3-none-win_amd64.whl", hash = "sha256:42e16c930e1e910461f4c551a72fc1b900f37124431bf2b6a6d9ddae70042db4"}, +] + +[package.dependencies] +greenlet = "2.0.2" +pyee = "9.0.4" + +[[package]] +name = "pydantic" +version = "1.10.8" +description = "Data validation and settings management using python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-1.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1243d28e9b05003a89d72e7915fdb26ffd1d39bdd39b00b7dbe4afae4b557f9d"}, + {file = "pydantic-1.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0ab53b609c11dfc0c060d94335993cc2b95b2150e25583bec37a49b2d6c6c3f"}, + {file = "pydantic-1.10.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9613fadad06b4f3bc5db2653ce2f22e0de84a7c6c293909b48f6ed37b83c61f"}, + {file = "pydantic-1.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df7800cb1984d8f6e249351139667a8c50a379009271ee6236138a22a0c0f319"}, + {file = "pydantic-1.10.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0c6fafa0965b539d7aab0a673a046466d23b86e4b0e8019d25fd53f4df62c277"}, + {file = "pydantic-1.10.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e82d4566fcd527eae8b244fa952d99f2ca3172b7e97add0b43e2d97ee77f81ab"}, + {file = "pydantic-1.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:ab523c31e22943713d80d8d342d23b6f6ac4b792a1e54064a8d0cf78fd64e800"}, + {file = "pydantic-1.10.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:666bdf6066bf6dbc107b30d034615d2627e2121506c555f73f90b54a463d1f33"}, + {file = "pydantic-1.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:35db5301b82e8661fa9c505c800d0990bc14e9f36f98932bb1d248c0ac5cada5"}, + {file = "pydantic-1.10.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90c1e29f447557e9e26afb1c4dbf8768a10cc676e3781b6a577841ade126b85"}, + {file = "pydantic-1.10.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93e766b4a8226e0708ef243e843105bf124e21331694367f95f4e3b4a92bbb3f"}, + {file = "pydantic-1.10.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:88f195f582851e8db960b4a94c3e3ad25692c1c1539e2552f3df7a9e972ef60e"}, + {file = "pydantic-1.10.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:34d327c81e68a1ecb52fe9c8d50c8a9b3e90d3c8ad991bfc8f953fb477d42fb4"}, + {file = "pydantic-1.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:d532bf00f381bd6bc62cabc7d1372096b75a33bc197a312b03f5838b4fb84edd"}, + {file = "pydantic-1.10.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7d5b8641c24886d764a74ec541d2fc2c7fb19f6da2a4001e6d580ba4a38f7878"}, + {file = "pydantic-1.10.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b1f6cb446470b7ddf86c2e57cd119a24959af2b01e552f60705910663af09a4"}, + {file = "pydantic-1.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c33b60054b2136aef8cf190cd4c52a3daa20b2263917c49adad20eaf381e823b"}, + {file = "pydantic-1.10.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1952526ba40b220b912cdc43c1c32bcf4a58e3f192fa313ee665916b26befb68"}, + {file = "pydantic-1.10.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bb14388ec45a7a0dc429e87def6396f9e73c8c77818c927b6a60706603d5f2ea"}, + {file = "pydantic-1.10.8-cp37-cp37m-win_amd64.whl", hash = "sha256:16f8c3e33af1e9bb16c7a91fc7d5fa9fe27298e9f299cff6cb744d89d573d62c"}, + {file = "pydantic-1.10.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ced8375969673929809d7f36ad322934c35de4af3b5e5b09ec967c21f9f7887"}, + {file = "pydantic-1.10.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:93e6bcfccbd831894a6a434b0aeb1947f9e70b7468f274154d03d71fabb1d7c6"}, + {file = "pydantic-1.10.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:191ba419b605f897ede9892f6c56fb182f40a15d309ef0142212200a10af4c18"}, + {file = "pydantic-1.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:052d8654cb65174d6f9490cc9b9a200083a82cf5c3c5d3985db765757eb3b375"}, + {file = "pydantic-1.10.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ceb6a23bf1ba4b837d0cfe378329ad3f351b5897c8d4914ce95b85fba96da5a1"}, + {file = "pydantic-1.10.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f2e754d5566f050954727c77f094e01793bcb5725b663bf628fa6743a5a9108"}, + {file = "pydantic-1.10.8-cp38-cp38-win_amd64.whl", hash = "sha256:6a82d6cda82258efca32b40040228ecf43a548671cb174a1e81477195ed3ed56"}, + {file = "pydantic-1.10.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e59417ba8a17265e632af99cc5f35ec309de5980c440c255ab1ca3ae96a3e0e"}, + {file = "pydantic-1.10.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:84d80219c3f8d4cad44575e18404099c76851bc924ce5ab1c4c8bb5e2a2227d0"}, + {file = "pydantic-1.10.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e4148e635994d57d834be1182a44bdb07dd867fa3c2d1b37002000646cc5459"}, + {file = "pydantic-1.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12f7b0bf8553e310e530e9f3a2f5734c68699f42218bf3568ef49cd9b0e44df4"}, + {file = "pydantic-1.10.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42aa0c4b5c3025483240a25b09f3c09a189481ddda2ea3a831a9d25f444e03c1"}, + {file = "pydantic-1.10.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17aef11cc1b997f9d574b91909fed40761e13fac438d72b81f902226a69dac01"}, + {file = "pydantic-1.10.8-cp39-cp39-win_amd64.whl", hash = "sha256:66a703d1983c675a6e0fed8953b0971c44dba48a929a2000a493c3772eb61a5a"}, + {file = "pydantic-1.10.8-py3-none-any.whl", hash = "sha256:7456eb22ed9aaa24ff3e7b4757da20d9e5ce2a81018c1b3ebd81a0b88a18f3b2"}, + {file = "pydantic-1.10.8.tar.gz", hash = "sha256:1410275520dfa70effadf4c21811d755e7ef9bb1f1d077a21958153a92c8d9ca"}, +] + +[package.dependencies] +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pyee" +version = "9.0.4" +description = "A port of node.js's EventEmitter to python." +optional = false +python-versions = "*" +files = [ + {file = "pyee-9.0.4-py2.py3-none-any.whl", hash = "sha256:9f066570130c554e9cc12de5a9d86f57c7ee47fece163bbdaa3e9c933cfbdfa5"}, + {file = "pyee-9.0.4.tar.gz", hash = "sha256:2770c4928abc721f46b705e6a72b0c59480c4a69c9a83ca0b00bb994f1ea4b32"}, +] + +[package.dependencies] +typing-extensions = "*" + +[[package]] +name = "pygments" +version = "2.15.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, + {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-dotenv" +version = "1.0.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, + {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] + +[[package]] +name = "reactpy" +version = "1.0.0" +description = "Reactive user interfaces with pure Python" +optional = false +python-versions = ">=3.9" +files = [] +develop = false + +[package.dependencies] +anyio = ">=3" +asgiref = ">=3" +colorlog = ">=6" +fastapi = {version = ">=0.63.0", optional = true, markers = "extra == \"fastapi\""} +fastjsonschema = ">=2.14.5" +flask = {version = "*", optional = true, markers = "extra == \"flask\""} +flask-cors = {version = "*", optional = true, markers = "extra == \"flask\""} +flask-sock = {version = "*", optional = true, markers = "extra == \"flask\""} +jsonpatch = ">=1.32" +lxml = ">=4" +markupsafe = {version = ">=1.1.1,<2.1", optional = true, markers = "extra == \"flask\""} +mypy-extensions = ">=0.4.3" +playwright = {version = "*", optional = true, markers = "extra == \"testing\""} +requests = ">=2" +sanic = {version = ">=21", optional = true, markers = "extra == \"sanic\""} +sanic-cors = {version = "*", optional = true, markers = "extra == \"sanic\""} +starlette = {version = ">=0.13.6", optional = true, markers = "extra == \"starlette\""} +tornado = {version = "*", optional = true, markers = "extra == \"tornado\""} +typing-extensions = ">=3.10" +uvicorn = {version = ">=0.19.0", extras = ["standard"], optional = true, markers = "extra == \"fastapi\" or extra == \"sanic\" or extra == \"starlette\""} + +[package.extras] +all = ["reactpy[fastapi,flask,sanic,starlette,testing,tornado]"] +fastapi = ["fastapi (>=0.63.0)", "uvicorn[standard] (>=0.19.0)"] +flask = ["flask", "flask-cors", "flask-sock", "markupsafe (>=1.1.1,<2.1)"] +sanic = ["sanic (>=21)", "sanic-cors", "uvicorn[standard] (>=0.19.0)"] +starlette = ["starlette (>=0.13.6)", "uvicorn[standard] (>=0.19.0)"] +testing = ["playwright"] +tornado = ["tornado"] + +[package.source] +type = "directory" +url = "../src/py/reactpy" + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "sanic" +version = "23.3.0" +description = "A web server and web framework that's written to go fast. Build fast. Run fast." +optional = false +python-versions = ">=3.7" +files = [ + {file = "sanic-23.3.0-py3-none-any.whl", hash = "sha256:7cafbd63da9c6c6d8aeb8cb4304addf8a274352ab812014386c63e55f474fbee"}, + {file = "sanic-23.3.0.tar.gz", hash = "sha256:b80ebc5c38c983cb45ae5ecc7a669a54c823ec1dff297fbd5f817b1e9e9e49af"}, +] + +[package.dependencies] +aiofiles = ">=0.6.0" +html5tagger = ">=1.2.1" +httptools = ">=0.0.10" +multidict = ">=5.0,<7.0" +sanic-routing = ">=22.8.0" +tracerite = ">=1.0.0" +ujson = {version = ">=1.35", markers = "sys_platform != \"win32\" and implementation_name == \"cpython\""} +uvloop = {version = ">=0.15.0", markers = "sys_platform != \"win32\" and implementation_name == \"cpython\""} +websockets = ">=10.0" + +[package.extras] +all = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage", "cryptography", "docutils", "enum-tools[sphinx]", "flake8", "isort (>=5.0.0)", "m2r2", "mistune (<2.0.0)", "mypy (>=0.901,<0.910)", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "sanic-testing (>=23.3.0)", "slotscheck (>=0.8.0,<1)", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"] +dev = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage", "cryptography", "docutils", "flake8", "isort (>=5.0.0)", "mypy (>=0.901,<0.910)", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "sanic-testing (>=23.3.0)", "slotscheck (>=0.8.0,<1)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"] +docs = ["docutils", "enum-tools[sphinx]", "m2r2", "mistune (<2.0.0)", "pygments", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)"] +ext = ["sanic-ext"] +http3 = ["aioquic"] +test = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage", "docutils", "flake8", "isort (>=5.0.0)", "mypy (>=0.901,<0.910)", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "sanic-testing (>=23.3.0)", "slotscheck (>=0.8.0,<1)", "types-ujson", "uvicorn (<0.15.0)"] + +[[package]] +name = "sanic-cors" +version = "2.2.0" +description = "A Sanic extension adding a decorator for CORS support. Based on flask-cors by Cory Dolphin." +optional = false +python-versions = "*" +files = [ + {file = "Sanic-Cors-2.2.0.tar.gz", hash = "sha256:f8d7515da4c8b837871d422c66314c4b5704396a78894b59c50e26aa72a95873"}, + {file = "Sanic_Cors-2.2.0-py2.py3-none-any.whl", hash = "sha256:c3b133ff1f0bb609a53db35f727f5c371dc4ebeb6be4cc2c37c19dd8b9301115"}, +] + +[package.dependencies] +packaging = ">=21.3" +sanic = ">=21.9.3" + +[[package]] +name = "sanic-routing" +version = "22.8.0" +description = "Core routing component for Sanic" +optional = false +python-versions = "*" +files = [ + {file = "sanic-routing-22.8.0.tar.gz", hash = "sha256:305729b4e0bf01f074044a2a315ff401fa7eeffb009eec1d2c81d35e1038ddfc"}, + {file = "sanic_routing-22.8.0-py3-none-any.whl", hash = "sha256:9a928ed9e19a36bc019223be90a5da0ab88cdd76b101e032510b6a7073c017e9"}, +] + +[[package]] +name = "simple-websocket" +version = "0.10.0" +description = "Simple WebSocket server and client for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "simple-websocket-0.10.0.tar.gz", hash = "sha256:82c0b0b1006d5490f09ff66392394d90dd758285635edad241e093e9a8abd3eb"}, + {file = "simple_websocket-0.10.0-py3-none-any.whl", hash = "sha256:fc1bc56c393a187e7268f8ab99da1a8e8da9b5dfb7769a2f3b8dada00067745b"}, +] + +[package.dependencies] +wsproto = "*" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "soupsieve" +version = "2.4.1" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, + {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, +] + +[[package]] +name = "sphinx" +version = "4.5.0" +description = "Python documentation generator" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"}, + {file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"}, +] + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=1.3" +colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.14,<0.18" +imagesize = "*" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} +Jinja2 = ">=2.3" +packaging = "*" +Pygments = ">=2.0" +requests = ">=2.5.0" +snowballstemmer = ">=1.1" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "types-requests", "types-typed-ast"] +test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"] + +[[package]] +name = "sphinx-autobuild" +version = "2021.3.14" +description = "Rebuild Sphinx documentation on changes, with live-reload in the browser." +optional = false +python-versions = ">=3.6" +files = [ + {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, + {file = "sphinx_autobuild-2021.3.14-py3-none-any.whl", hash = "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac"}, +] + +[package.dependencies] +colorama = "*" +livereload = "*" +sphinx = "*" + +[package.extras] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "sphinx-autodoc-typehints" +version = "1.19.1" +description = "Type hints (PEP 484) support for the Sphinx autodoc extension" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx_autodoc_typehints-1.19.1-py3-none-any.whl", hash = "sha256:9be46aeeb1b315eb5df1f3a7cb262149895d16c7d7dcd77b92513c3c3a1e85e6"}, + {file = "sphinx_autodoc_typehints-1.19.1.tar.gz", hash = "sha256:6c841db55e0e9be0483ff3962a2152b60e79306f4288d8c4e7e86ac84486a5ea"}, +] + +[package.dependencies] +Sphinx = ">=4.5" + +[package.extras] +testing = ["covdefaults (>=2.2)", "coverage (>=6.3)", "diff-cover (>=6.4)", "nptyping (>=2.1.2)", "pytest (>=7.1)", "pytest-cov (>=3)", "sphobjinv (>=2)", "typing-extensions (>=4.1)"] +type-comments = ["typed-ast (>=1.5.2)"] + +[[package]] +name = "sphinx-copybutton" +version = "0.5.2" +description = "Add a copy button to each of your code cells." +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd"}, + {file = "sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e"}, +] + +[package.dependencies] +sphinx = ">=1.8" + +[package.extras] +code-style = ["pre-commit (==2.12.1)"] +rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] + +[[package]] +name = "sphinx-design" +version = "0.4.1" +description = "A sphinx extension for designing beautiful, view size responsive web components." +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx_design-0.4.1-py3-none-any.whl", hash = "sha256:23bf5705eb31296d4451f68b0222a698a8a84396ffe8378dfd9319ba7ab8efd9"}, + {file = "sphinx_design-0.4.1.tar.gz", hash = "sha256:5b6418ba4a2dc3d83592ea0ff61a52a891fe72195a4c3a18b2fa1c7668ce4708"}, +] + +[package.dependencies] +sphinx = ">=4,<7" + +[package.extras] +code-style = ["pre-commit (>=2.12,<3.0)"] +rtd = ["myst-parser (>=0.18.0,<2)"] +testing = ["myst-parser (>=0.18.0,<2)", "pytest (>=7.1,<8.0)", "pytest-cov", "pytest-regressions"] +theme-furo = ["furo (>=2022.06.04,<2022.07)"] +theme-pydata = ["pydata-sphinx-theme (>=0.9.0,<0.10.0)"] +theme-rtd = ["sphinx-rtd-theme (>=1.0,<2.0)"] +theme-sbt = ["sphinx-book-theme (>=0.3.0,<0.4.0)"] + +[[package]] +name = "sphinx-reredirects" +version = "0.1.2" +description = "Handles redirects for moved pages in Sphinx documentation projects" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinx_reredirects-0.1.2-py3-none-any.whl", hash = "sha256:3a22161771aadd448bb608a4fe7277252182a337af53c18372b7104531d71489"}, + {file = "sphinx_reredirects-0.1.2.tar.gz", hash = "sha256:a0e7213304759b01edc22f032f1715a1c61176fc8f167164e7a52b9feec9ac64"}, +] + +[package.dependencies] +sphinx = "*" + +[[package]] +name = "sphinx-resolve-py-references" +version = "0.1.0" +description = "Better python object resolution in Sphinx" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx_resolve_py_references-0.1.0-py2.py3-none-any.whl", hash = "sha256:ccf44a6b62d75c3a568285f4e1815734088c1a7cab7bbb7935bb22fbf0d78bc2"}, + {file = "sphinx_resolve_py_references-0.1.0.tar.gz", hash = "sha256:0f87c06b29ec128964aee2e40d170d1d3c0e5f4955b2618a89ca724f42385372"}, +] + +[package.dependencies] +sphinx = "*" + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.4" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, + {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.2" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.1" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, + {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.3" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxext-opengraph" +version = "0.8.2" +description = "Sphinx Extension to enable OGP support" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinxext-opengraph-0.8.2.tar.gz", hash = "sha256:45a693b6704052c426576f0a1f630649c55b4188bc49eb63e9587e24a923db39"}, + {file = "sphinxext_opengraph-0.8.2-py3-none-any.whl", hash = "sha256:6a05bdfe5176d9dd0a1d58a504f17118362ab976631213cd36fb44c4c40544c9"}, +] + +[package.dependencies] +matplotlib = "*" +sphinx = ">=4.0" + +[[package]] +name = "starlette" +version = "0.27.0" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.7" +files = [ + {file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"}, + {file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] + +[[package]] +name = "tornado" +version = "6.3.2" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">= 3.8" +files = [ + {file = "tornado-6.3.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:c367ab6c0393d71171123ca5515c61ff62fe09024fa6bf299cd1339dc9456829"}, + {file = "tornado-6.3.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b46a6ab20f5c7c1cb949c72c1994a4585d2eaa0be4853f50a03b5031e964fc7c"}, + {file = "tornado-6.3.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2de14066c4a38b4ecbbcd55c5cc4b5340eb04f1c5e81da7451ef555859c833f"}, + {file = "tornado-6.3.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05615096845cf50a895026f749195bf0b10b8909f9be672f50b0fe69cba368e4"}, + {file = "tornado-6.3.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b17b1cf5f8354efa3d37c6e28fdfd9c1c1e5122f2cb56dac121ac61baa47cbe"}, + {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:29e71c847a35f6e10ca3b5c2990a52ce38b233019d8e858b755ea6ce4dcdd19d"}, + {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:834ae7540ad3a83199a8da8f9f2d383e3c3d5130a328889e4cc991acc81e87a0"}, + {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6a0848f1aea0d196a7c4f6772197cbe2abc4266f836b0aac76947872cd29b411"}, + {file = "tornado-6.3.2-cp38-abi3-win32.whl", hash = "sha256:7efcbcc30b7c654eb6a8c9c9da787a851c18f8ccd4a5a3a95b05c7accfa068d2"}, + {file = "tornado-6.3.2-cp38-abi3-win_amd64.whl", hash = "sha256:0c325e66c8123c606eea33084976c832aa4e766b7dff8aedd7587ea44a604cdf"}, + {file = "tornado-6.3.2.tar.gz", hash = "sha256:4b927c4f19b71e627b13f3db2324e4ae660527143f9e1f2e2fb404f3a187e2ba"}, +] + +[[package]] +name = "tracerite" +version = "1.1.0" +description = "Human-readable HTML tracebacks for Python exceptions" +optional = false +python-versions = "*" +files = [ + {file = "tracerite-1.1.0-py3-none-any.whl", hash = "sha256:4cccac04db05eeeabda45e72b57199e147fa2f73cf64d89cfd625df321bd2ab6"}, + {file = "tracerite-1.1.0.tar.gz", hash = "sha256:041dab8fd4bb405f73506293ac7438a2d311e5f9044378ba7d9a6540392f9e4b"}, +] + +[package.dependencies] +html5tagger = ">=1.2.1" + +[[package]] +name = "typing-extensions" +version = "4.6.3" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"}, + {file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"}, +] + +[[package]] +name = "ujson" +version = "5.7.0" +description = "Ultra fast JSON encoder and decoder for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ujson-5.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5eba5e69e4361ac3a311cf44fa71bc619361b6e0626768a494771aacd1c2f09b"}, + {file = "ujson-5.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aae4d9e1b4c7b61780f0a006c897a4a1904f862fdab1abb3ea8f45bd11aa58f3"}, + {file = "ujson-5.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2e43ccdba1cb5c6d3448eadf6fc0dae7be6c77e357a3abc968d1b44e265866d"}, + {file = "ujson-5.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54384ce4920a6d35fa9ea8e580bc6d359e3eb961fa7e43f46c78e3ed162d56ff"}, + {file = "ujson-5.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24ad1aa7fc4e4caa41d3d343512ce68e41411fb92adf7f434a4d4b3749dc8f58"}, + {file = "ujson-5.7.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:afff311e9f065a8f03c3753db7011bae7beb73a66189c7ea5fcb0456b7041ea4"}, + {file = "ujson-5.7.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e80f0d03e7e8646fc3d79ed2d875cebd4c83846e129737fdc4c2532dbd43d9e"}, + {file = "ujson-5.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:137831d8a0db302fb6828ee21c67ad63ac537bddc4376e1aab1c8573756ee21c"}, + {file = "ujson-5.7.0-cp310-cp310-win32.whl", hash = "sha256:7df3fd35ebc14dafeea031038a99232b32f53fa4c3ecddb8bed132a43eefb8ad"}, + {file = "ujson-5.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:af4639f684f425177d09ae409c07602c4096a6287027469157bfb6f83e01448b"}, + {file = "ujson-5.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b0f2680ce8a70f77f5d70aaf3f013d53e6af6d7058727a35d8ceb4a71cdd4e9"}, + {file = "ujson-5.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a19fd8e7d8cc58a169bea99fed5666023adf707a536d8f7b0a3c51dd498abf"}, + {file = "ujson-5.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6abb8e6d8f1ae72f0ed18287245f5b6d40094e2656d1eab6d99d666361514074"}, + {file = "ujson-5.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8cd622c069368d5074bd93817b31bdb02f8d818e57c29e206f10a1f9c6337dd"}, + {file = "ujson-5.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14f9082669f90e18e64792b3fd0bf19f2b15e7fe467534a35ea4b53f3bf4b755"}, + {file = "ujson-5.7.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d7ff6ebb43bc81b057724e89550b13c9a30eda0f29c2f506f8b009895438f5a6"}, + {file = "ujson-5.7.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f7f241488879d91a136b299e0c4ce091996c684a53775e63bb442d1a8e9ae22a"}, + {file = "ujson-5.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5593263a7fcfb934107444bcfba9dde8145b282de0ee9f61e285e59a916dda0f"}, + {file = "ujson-5.7.0-cp311-cp311-win32.whl", hash = "sha256:26c2b32b489c393106e9cb68d0a02e1a7b9d05a07429d875c46b94ee8405bdb7"}, + {file = "ujson-5.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:ed24406454bb5a31df18f0a423ae14beb27b28cdfa34f6268e7ebddf23da807e"}, + {file = "ujson-5.7.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18679484e3bf9926342b1c43a3bd640f93a9eeeba19ef3d21993af7b0c44785d"}, + {file = "ujson-5.7.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ee295761e1c6c30400641f0a20d381633d7622633cdf83a194f3c876a0e4b7e"}, + {file = "ujson-5.7.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b738282e12a05f400b291966630a98d622da0938caa4bc93cf65adb5f4281c60"}, + {file = "ujson-5.7.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00343501dbaa5172e78ef0e37f9ebd08040110e11c12420ff7c1f9f0332d939e"}, + {file = "ujson-5.7.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c0d1f7c3908357ee100aa64c4d1cf91edf99c40ac0069422a4fd5fd23b263263"}, + {file = "ujson-5.7.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a5d2f44331cf04689eafac7a6596c71d6657967c07ac700b0ae1c921178645da"}, + {file = "ujson-5.7.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:16b2254a77b310f118717715259a196662baa6b1f63b1a642d12ab1ff998c3d7"}, + {file = "ujson-5.7.0-cp37-cp37m-win32.whl", hash = "sha256:6faf46fa100b2b89e4db47206cf8a1ffb41542cdd34dde615b2fc2288954f194"}, + {file = "ujson-5.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ff0004c3f5a9a6574689a553d1b7819d1a496b4f005a7451f339dc2d9f4cf98c"}, + {file = "ujson-5.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:75204a1dd7ec6158c8db85a2f14a68d2143503f4bafb9a00b63fe09d35762a5e"}, + {file = "ujson-5.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7312731c7826e6c99cdd3ac503cd9acd300598e7a80bcf41f604fee5f49f566c"}, + {file = "ujson-5.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b9dc5a90e2149643df7f23634fe202fed5ebc787a2a1be95cf23632b4d90651"}, + {file = "ujson-5.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6a6961fc48821d84b1198a09516e396d56551e910d489692126e90bf4887d29"}, + {file = "ujson-5.7.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b01a9af52a0d5c46b2c68e3f258fdef2eacaa0ce6ae3e9eb97983f5b1166edb6"}, + {file = "ujson-5.7.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7316d3edeba8a403686cdcad4af737b8415493101e7462a70ff73dd0609eafc"}, + {file = "ujson-5.7.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ee997799a23227e2319a3f8817ce0b058923dbd31904761b788dc8f53bd3e30"}, + {file = "ujson-5.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dda9aa4c33435147262cd2ea87c6b7a1ca83ba9b3933ff7df34e69fee9fced0c"}, + {file = "ujson-5.7.0-cp38-cp38-win32.whl", hash = "sha256:bea8d30e362180aafecabbdcbe0e1f0b32c9fa9e39c38e4af037b9d3ca36f50c"}, + {file = "ujson-5.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:c96e3b872bf883090ddf32cc41957edf819c5336ab0007d0cf3854e61841726d"}, + {file = "ujson-5.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6411aea4c94a8e93c2baac096fbf697af35ba2b2ed410b8b360b3c0957a952d3"}, + {file = "ujson-5.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d3b3499c55911f70d4e074c626acdb79a56f54262c3c83325ffb210fb03e44d"}, + {file = "ujson-5.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:341f891d45dd3814d31764626c55d7ab3fd21af61fbc99d070e9c10c1190680b"}, + {file = "ujson-5.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f242eec917bafdc3f73a1021617db85f9958df80f267db69c76d766058f7b19"}, + {file = "ujson-5.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3af9f9f22a67a8c9466a32115d9073c72a33ae627b11de6f592df0ee09b98b6"}, + {file = "ujson-5.7.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a3d794afbf134df3056a813e5c8a935208cddeae975bd4bc0ef7e89c52f0ce0"}, + {file = "ujson-5.7.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:800bf998e78dae655008dd10b22ca8dc93bdcfcc82f620d754a411592da4bbf2"}, + {file = "ujson-5.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b5ac3d5c5825e30b438ea92845380e812a476d6c2a1872b76026f2e9d8060fc2"}, + {file = "ujson-5.7.0-cp39-cp39-win32.whl", hash = "sha256:cd90027e6d93e8982f7d0d23acf88c896d18deff1903dd96140613389b25c0dd"}, + {file = "ujson-5.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:523ee146cdb2122bbd827f4dcc2a8e66607b3f665186bce9e4f78c9710b6d8ab"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e87cec407ec004cf1b04c0ed7219a68c12860123dfb8902ef880d3d87a71c172"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bab10165db6a7994e67001733f7f2caf3400b3e11538409d8756bc9b1c64f7e8"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b522be14a28e6ac1cf818599aeff1004a28b42df4ed4d7bc819887b9dac915fc"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7592f40175c723c032cdbe9fe5165b3b5903604f774ab0849363386e99e1f253"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ed22f9665327a981f288a4f758a432824dc0314e4195a0eaeb0da56a477da94d"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:adf445a49d9a97a5a4c9bb1d652a1528de09dd1c48b29f79f3d66cea9f826bf6"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64772a53f3c4b6122ed930ae145184ebaed38534c60f3d859d8c3f00911eb122"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35209cb2c13fcb9d76d249286105b4897b75a5e7f0efb0c0f4b90f222ce48910"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90712dfc775b2c7a07d4d8e059dd58636bd6ff1776d79857776152e693bddea6"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0e4e8981c6e7e9e637e637ad8ffe948a09e5434bc5f52ecbb82b4b4cfc092bfb"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:581c945b811a3d67c27566539bfcb9705ea09cb27c4be0002f7a553c8886b817"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d36a807a24c7d44f71686685ae6fbc8793d784bca1adf4c89f5f780b835b6243"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b4257307e3662aa65e2644a277ca68783c5d51190ed9c49efebdd3cbfd5fa44"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea7423d8a2f9e160c5e011119741682414c5b8dce4ae56590a966316a07a4618"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c592eb91a5968058a561d358d0fef59099ed152cfb3e1cd14eee51a7a93879e"}, + {file = "ujson-5.7.0.tar.gz", hash = "sha256:e788e5d5dcae8f6118ac9b45d0b891a0d55f7ac480eddcb7f07263f2bcf37b23"}, +] + +[[package]] +name = "urllib3" +version = "2.0.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.7" +files = [ + {file = "urllib3-2.0.2-py3-none-any.whl", hash = "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e"}, + {file = "urllib3-2.0.2.tar.gz", hash = "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "uvicorn" +version = "0.22.0" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.7" +files = [ + {file = "uvicorn-0.22.0-py3-none-any.whl", hash = "sha256:e9434d3bbf05f310e762147f769c9f21235ee118ba2d2bf1155a7196448bd996"}, + {file = "uvicorn-0.22.0.tar.gz", hash = "sha256:79277ae03db57ce7d9aa0567830bbb51d7a612f54d6e1e3e92da3ef24c2c8ed8"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} +h11 = ">=0.8" +httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} +python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} +uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "uvloop" +version = "0.17.0" +description = "Fast implementation of asyncio event loop on top of libuv" +optional = false +python-versions = ">=3.7" +files = [ + {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce9f61938d7155f79d3cb2ffa663147d4a76d16e08f65e2c66b77bd41b356718"}, + {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:68532f4349fd3900b839f588972b3392ee56042e440dd5873dfbbcd2cc67617c"}, + {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0949caf774b9fcefc7c5756bacbbbd3fc4c05a6b7eebc7c7ad6f825b23998d6d"}, + {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3d00b70ce95adce264462c930fbaecb29718ba6563db354608f37e49e09024"}, + {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a5abddb3558d3f0a78949c750644a67be31e47936042d4f6c888dd6f3c95f4aa"}, + {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8efcadc5a0003d3a6e887ccc1fb44dec25594f117a94e3127954c05cf144d811"}, + {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3378eb62c63bf336ae2070599e49089005771cc651c8769aaad72d1bd9385a7c"}, + {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6aafa5a78b9e62493539456f8b646f85abc7093dd997f4976bb105537cf2635e"}, + {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c686a47d57ca910a2572fddfe9912819880b8765e2f01dc0dd12a9bf8573e539"}, + {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:864e1197139d651a76c81757db5eb199db8866e13acb0dfe96e6fc5d1cf45fc4"}, + {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2a6149e1defac0faf505406259561bc14b034cdf1d4711a3ddcdfbaa8d825a05"}, + {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6708f30db9117f115eadc4f125c2a10c1a50d711461699a0cbfaa45b9a78e376"}, + {file = "uvloop-0.17.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:23609ca361a7fc587031429fa25ad2ed7242941adec948f9d10c045bfecab06b"}, + {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2deae0b0fb00a6af41fe60a675cec079615b01d68beb4cc7b722424406b126a8"}, + {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45cea33b208971e87a31c17622e4b440cac231766ec11e5d22c76fab3bf9df62"}, + {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9b09e0f0ac29eee0451d71798878eae5a4e6a91aa275e114037b27f7db72702d"}, + {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dbbaf9da2ee98ee2531e0c780455f2841e4675ff580ecf93fe5c48fe733b5667"}, + {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a4aee22ece20958888eedbad20e4dbb03c37533e010fb824161b4f05e641f738"}, + {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:307958f9fc5c8bb01fad752d1345168c0abc5d62c1b72a4a8c6c06f042b45b20"}, + {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ebeeec6a6641d0adb2ea71dcfb76017602ee2bfd8213e3fcc18d8f699c5104f"}, + {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1436c8673c1563422213ac6907789ecb2b070f5939b9cbff9ef7113f2b531595"}, + {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8887d675a64cfc59f4ecd34382e5b4f0ef4ae1da37ed665adba0c2badf0d6578"}, + {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3db8de10ed684995a7f34a001f15b374c230f7655ae840964d51496e2f8a8474"}, + {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7d37dccc7ae63e61f7b96ee2e19c40f153ba6ce730d8ba4d3b4e9738c1dccc1b"}, + {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cbbe908fda687e39afd6ea2a2f14c2c3e43f2ca88e3a11964b297822358d0e6c"}, + {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d97672dc709fa4447ab83276f344a165075fd9f366a97b712bdd3fee05efae8"}, + {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1e507c9ee39c61bfddd79714e4f85900656db1aec4d40c6de55648e85c2799c"}, + {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c092a2c1e736086d59ac8e41f9c98f26bbf9b9222a76f21af9dfe949b99b2eb9"}, + {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:30babd84706115626ea78ea5dbc7dd8d0d01a2e9f9b306d24ca4ed5796c66ded"}, + {file = "uvloop-0.17.0.tar.gz", hash = "sha256:0ddf6baf9cf11a1a22c71487f39f15b2cf78eb5bde7e5b45fbb99e8a9d91b9e1"}, +] + +[package.extras] +dev = ["Cython (>=0.29.32,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"] + +[[package]] +name = "watchfiles" +version = "0.19.0" +description = "Simple, modern and high performance file watching and code reload in python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "watchfiles-0.19.0-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:91633e64712df3051ca454ca7d1b976baf842d7a3640b87622b323c55f3345e7"}, + {file = "watchfiles-0.19.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b6577b8c6c8701ba8642ea9335a129836347894b666dd1ec2226830e263909d3"}, + {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:18b28f6ad871b82df9542ff958d0c86bb0d8310bb09eb8e87d97318a3b5273af"}, + {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fac19dc9cbc34052394dbe81e149411a62e71999c0a19e1e09ce537867f95ae0"}, + {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:09ea3397aecbc81c19ed7f025e051a7387feefdb789cf768ff994c1228182fda"}, + {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c0376deac92377817e4fb8f347bf559b7d44ff556d9bc6f6208dd3f79f104aaf"}, + {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c75eff897786ee262c9f17a48886f4e98e6cfd335e011c591c305e5d083c056"}, + {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb5d45c4143c1dd60f98a16187fd123eda7248f84ef22244818c18d531a249d1"}, + {file = "watchfiles-0.19.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:79c533ff593db861ae23436541f481ec896ee3da4e5db8962429b441bbaae16e"}, + {file = "watchfiles-0.19.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3d7d267d27aceeeaa3de0dd161a0d64f0a282264d592e335fff7958cc0cbae7c"}, + {file = "watchfiles-0.19.0-cp37-abi3-win32.whl", hash = "sha256:176a9a7641ec2c97b24455135d58012a5be5c6217fc4d5fef0b2b9f75dbf5154"}, + {file = "watchfiles-0.19.0-cp37-abi3-win_amd64.whl", hash = "sha256:945be0baa3e2440151eb3718fd8846751e8b51d8de7b884c90b17d271d34cae8"}, + {file = "watchfiles-0.19.0-cp37-abi3-win_arm64.whl", hash = "sha256:0089c6dc24d436b373c3c57657bf4f9a453b13767150d17284fc6162b2791911"}, + {file = "watchfiles-0.19.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cae3dde0b4b2078f31527acff6f486e23abed307ba4d3932466ba7cdd5ecec79"}, + {file = "watchfiles-0.19.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f3920b1285a7d3ce898e303d84791b7bf40d57b7695ad549dc04e6a44c9f120"}, + {file = "watchfiles-0.19.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9afd0d69429172c796164fd7fe8e821ade9be983f51c659a38da3faaaaac44dc"}, + {file = "watchfiles-0.19.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68dce92b29575dda0f8d30c11742a8e2b9b8ec768ae414b54f7453f27bdf9545"}, + {file = "watchfiles-0.19.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:5569fc7f967429d4bc87e355cdfdcee6aabe4b620801e2cf5805ea245c06097c"}, + {file = "watchfiles-0.19.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5471582658ea56fca122c0f0d0116a36807c63fefd6fdc92c71ca9a4491b6b48"}, + {file = "watchfiles-0.19.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b538014a87f94d92f98f34d3e6d2635478e6be6423a9ea53e4dd96210065e193"}, + {file = "watchfiles-0.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20b44221764955b1e703f012c74015306fb7e79a00c15370785f309b1ed9aa8d"}, + {file = "watchfiles-0.19.0.tar.gz", hash = "sha256:d9b073073e048081e502b6c6b0b88714c026a1a4c890569238d04aca5f9ca74b"}, +] + +[package.dependencies] +anyio = ">=3.0.0" + +[[package]] +name = "websockets" +version = "11.0.3" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"}, + {file = "websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"}, + {file = "websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"}, + {file = "websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"}, + {file = "websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"}, + {file = "websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"}, + {file = "websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"}, + {file = "websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"}, + {file = "websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"}, + {file = "websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"}, + {file = "websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"}, + {file = "websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"}, + {file = "websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"}, + {file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"}, +] + +[[package]] +name = "werkzeug" +version = "2.1.2" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Werkzeug-2.1.2-py3-none-any.whl", hash = "sha256:72a4b735692dd3135217911cbeaa1be5fa3f62bffb8745c5215420a03dc55255"}, + {file = "Werkzeug-2.1.2.tar.gz", hash = "sha256:1ce08e8093ed67d638d63879fd1ba3735817f7a80de3674d293f5984f25fb6e6"}, +] + +[package.extras] +watchdog = ["watchdog"] + +[[package]] +name = "wsproto" +version = "1.2.0" +description = "WebSockets state-machine based protocol implementation" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, + {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, +] + +[package.dependencies] +h11 = ">=0.9.0,<1" + +[[package]] +name = "zipp" +version = "3.15.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9" +content-hash = "629118cfac10f1dab4c39c6ccd50bd69ca68a7fc05dd2baf1d020082d6b19e4e" diff --git a/docs/pyproject.toml b/docs/pyproject.toml new file mode 100644 index 000000000..d2f47c577 --- /dev/null +++ b/docs/pyproject.toml @@ -0,0 +1,23 @@ +[tool.poetry] +name = "docs" +version = "0.0.0" +description = "docs" +authors = ["rmorshea "] +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" diff --git a/docs/source/_custom_js/package-lock.json b/docs/source/_custom_js/package-lock.json index fc00fcb83..98cbb7014 100644 --- a/docs/source/_custom_js/package-lock.json +++ b/docs/source/_custom_js/package-lock.json @@ -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", diff --git a/docs/source/_custom_js/package.json b/docs/source/_custom_js/package.json index 1d19613f5..78d72b961 100644 --- a/docs/source/_custom_js/package.json +++ b/docs/source/_custom_js/package.json @@ -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" } } diff --git a/docs/source/_exts/autogen_api_docs.py b/docs/source/_exts/autogen_api_docs.py index 1de1a8493..b95d85a99 100644 --- a/docs/source/_exts/autogen_api_docs.py +++ b/docs/source/_exts/autogen_api_docs.py @@ -8,7 +8,7 @@ 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) @@ -81,11 +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 - if len(rel_path_parts) < len(SECTION_SYMBOLS): - msg = "package structure is too deep" + 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)] + return SECTION_SYMBOLS[len(rel_path_parts) - 1] def walk_python_files(root: Path, ignore_dirs: Collection[str]) -> Iterator[Path]: diff --git a/docs/source/_exts/reactpy_example.py b/docs/source/_exts/reactpy_example.py index 2e3ae489c..c6b054c07 100644 --- a/docs/source/_exts/reactpy_example.py +++ b/docs/source/_exts/reactpy_example.py @@ -4,18 +4,17 @@ 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 diff --git a/docs/source/_exts/reactpy_view.py b/docs/source/_exts/reactpy_view.py index 478dbbcae..7a2bf85a4 100644 --- a/docs/source/_exts/reactpy_view.py +++ b/docs/source/_exts/reactpy_view.py @@ -1,12 +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( "/" diff --git a/docs/source/conf.py b/docs/source/conf.py index 8d3e4832f..08addad8d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -13,11 +13,10 @@ 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")) @@ -38,23 +37,23 @@ 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 @@ -318,7 +317,7 @@ # 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), diff --git a/pyproject.toml b/pyproject.toml index d7b81e1b4..c2845ea6f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ dependencies = [ [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}" diff --git a/requirements/build-docs.txt b/requirements/build-docs.txt deleted file mode 100644 index 274cbb56b..000000000 --- a/requirements/build-docs.txt +++ /dev/null @@ -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 diff --git a/requirements/build-pkg.txt b/requirements/build-pkg.txt deleted file mode 100644 index 82f40eafa..000000000 --- a/requirements/build-pkg.txt +++ /dev/null @@ -1,3 +0,0 @@ -twine -wheel -build diff --git a/requirements/check-style.txt b/requirements/check-style.txt deleted file mode 100644 index 035e52138..000000000 --- a/requirements/check-style.txt +++ /dev/null @@ -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 diff --git a/scripts/one_example.py b/scripts/one_example.py index 5c0a89055..5ac6193e2 100644 --- a/scripts/one_example.py +++ b/scripts/one_example.py @@ -1,102 +1,103 @@ -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() +# import sys +# import time +# from os.path import getmtime +# from threading import Event, Thread + +# from app.examples import all_example_names, get_example_files_by_name, load_one_example + +# import reactpy +# 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() diff --git a/scripts/run_docs.py b/scripts/run_docs.py deleted file mode 100644 index 8d42d81ec..000000000 --- a/scripts/run_docs.py +++ /dev/null @@ -1,18 +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 # noqa: E402 - -app = make_app() - -if __name__ == "__main__": - 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"))), - ) diff --git a/src/py/reactpy/README.md b/src/py/reactpy/README.md new file mode 100644 index 000000000..910a573a5 --- /dev/null +++ b/src/py/reactpy/README.md @@ -0,0 +1,23 @@ +# ReactPy + +

    + + + + + + + + + + + + + + + +

    + +--- + +[ReactPy](https://reactpy.dev/) is a library for building user interfaces in Python without Javascript. ReactPy interfaces are made from components that look and behave similar to those found in [ReactJS](https://reactjs.org/). Designed with simplicity in mind, ReactPy can be used by those without web development experience while also being powerful enough to grow with your ambitions. diff --git a/src/py/reactpy/pyproject.toml b/src/py/reactpy/pyproject.toml index 715503051..fac2ce916 100644 --- a/src/py/reactpy/pyproject.toml +++ b/src/py/reactpy/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "hatchling.build" name = "reactpy" dynamic = ["version"] description = 'Reactive user interfaces with pure Python' -readme = "../../../README.md" +readme = "README.md" requires-python = ">=3.9" license = "MIT" keywords = ["react", "javascript", "reactpy", "component"] @@ -65,9 +65,9 @@ testing = [ ] [project.urls] +Source = "https://github.com/reactive-python/reactpy" Documentation = "https://github.com/reactive-python/reactpy#readme" Issues = "https://github.com/reactive-python/reactpy/discussions" -Source = "https://github.com/reactive-python/reactpy" # --- Hatch ---------------------------------------------------------------------------- diff --git a/tasks.py b/tasks.py index f22f6d0d5..8232d15cb 100644 --- a/tasks.py +++ b/tasks.py @@ -47,6 +47,7 @@ def __call__( ROOT = Path(__file__).parent +DOCS_DIR = ROOT / "docs" SRC_DIR = ROOT / "src" JS_DIR = SRC_DIR / "js" PY_DIR = SRC_DIR / "py" @@ -139,7 +140,59 @@ def test_js(context: Context): @task(pre=[env_py]) def test_docs(context: Context): - raise Exit("Not implemented") + with context.cd(DOCS_DIR): + context.run("poetry install") + context.run( + "poetry 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 doctest " + "source " + "build", + ) + context.run("poetry run sphinx-build -b doctest source build") + + context.run("docker build . --file ./docs/Dockerfile") + + +@task +def docs(context: Context, docker: bool = False): + """Build documentation""" + if docker: + _docker_docs(context) + else: + _live_docs(context) + + +def _docker_docs(context: Context) -> None: + context.run("docker build . --file ./docs/Dockerfile --tag reactpy-docs:latest") + context.run( + "docker run -it -p 5000:5000 -e DEBUG=1 --rm reactpy-docs:latest", pty=True + ) + + +def _live_docs(context: Context) -> None: + with context.cd(DOCS_DIR): + context.run("poetry install") + context.run( + "poetry run python main.py " + "--open-browser " + # watch python source too + "--watch=../src/py " + # for some reason this matches absolute paths + "--ignore=**/_auto/* " + "--ignore=**/_static/custom.js " + "--ignore=**/node_modules/* " + "--ignore=**/package-lock.json " + "-a " + "-E " + "-b " + "html " + "source " + "build" + ) @task From 061f5fc16391b9f28a8b2b0c29eb9dd5ea7d1429 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Sat, 3 Jun 2023 15:22:57 -0600 Subject: [PATCH 24/26] remove scripts --- scripts/README.md | 5 -- scripts/one_example.py | 103 ----------------------------------------- 2 files changed, 108 deletions(-) delete mode 100644 scripts/README.md delete mode 100644 scripts/one_example.py diff --git a/scripts/README.md b/scripts/README.md deleted file mode 100644 index a9e8fa35c..000000000 --- a/scripts/README.md +++ /dev/null @@ -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. diff --git a/scripts/one_example.py b/scripts/one_example.py deleted file mode 100644 index 5ac6193e2..000000000 --- a/scripts/one_example.py +++ /dev/null @@ -1,103 +0,0 @@ -# import sys -# import time -# from os.path import getmtime -# from threading import Event, Thread - -# from app.examples import all_example_names, get_example_files_by_name, load_one_example - -# import reactpy -# 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() From 87bfe91e53753eba2985428c97f4ee1f0ddbe1fe Mon Sep 17 00:00:00 2001 From: rmorshea Date: Sat, 3 Jun 2023 15:26:08 -0600 Subject: [PATCH 25/26] fix tests --- tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 8232d15cb..0a0097c78 100644 --- a/tasks.py +++ b/tasks.py @@ -128,7 +128,7 @@ def test_py(context: Context, no_cov: bool = False): """Run test suites""" in_py( context, - f"hatch run {'test' if no_cov else 'cov'} --maxfail=3, --reruns=3", + f"hatch run {'test' if no_cov else 'cov'} --maxfail=3 --reruns=3", ) From 3442de04775368132b31dc158f73bdd27d8a94ba Mon Sep 17 00:00:00 2001 From: rmorshea Date: Sat, 3 Jun 2023 15:47:32 -0600 Subject: [PATCH 26/26] update contributor guide --- docs/source/about/contributor-guide.rst | 78 +++++++++++-------------- src/py/reactpy/pyproject.toml | 2 +- 2 files changed, 34 insertions(+), 46 deletions(-) diff --git a/docs/source/about/contributor-guide.rst b/docs/source/about/contributor-guide.rst index e26d89e52..b44be9b7e 100644 --- a/docs/source/about/contributor-guide.rst +++ b/docs/source/about/contributor-guide.rst @@ -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 `_. 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 ``) 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 `. .. 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 `__) + 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 `__) 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