Compare commits
No commits in common. "main" and "v0.2.5" have entirely different histories.
11
.coveragerc
11
.coveragerc
@ -1,17 +1,11 @@
|
|||||||
[run]
|
[run]
|
||||||
source = reflex
|
source = reflex
|
||||||
branch = true
|
branch = true
|
||||||
omit =
|
|
||||||
*/pyi_generator.py
|
|
||||||
reflex/__main__.py
|
|
||||||
reflex/app_module_for_backend.py
|
|
||||||
reflex/components/chakra/*
|
|
||||||
reflex/experimental/*
|
|
||||||
|
|
||||||
[report]
|
[report]
|
||||||
show_missing = true
|
show_missing = true
|
||||||
# TODO bump back to 79
|
# TODO bump back to 79
|
||||||
fail_under = 70
|
fail_under = 75
|
||||||
precision = 2
|
precision = 2
|
||||||
|
|
||||||
# Regexes for lines to exclude from consideration
|
# Regexes for lines to exclude from consideration
|
||||||
@ -31,9 +25,6 @@ exclude_also =
|
|||||||
# Don't complain about abstract methods, they aren't run:
|
# Don't complain about abstract methods, they aren't run:
|
||||||
@(abc\.)?abstractmethod
|
@(abc\.)?abstractmethod
|
||||||
|
|
||||||
# Don't complain about overloaded methods:
|
|
||||||
@overload
|
|
||||||
|
|
||||||
ignore_errors = True
|
ignore_errors = True
|
||||||
|
|
||||||
[html]
|
[html]
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"image": "mcr.microsoft.com/devcontainers/python:1-3.11-bookworm",
|
|
||||||
"postCreateCommand": "/bin/bash -c 'python -m pip install poetry && python -m poetry install & git clone https://github.com/reflex-dev/reflex-examples; wait'",
|
|
||||||
"forwardPorts": [3000, 8000],
|
|
||||||
"portsAttributes": {
|
|
||||||
"3000": {
|
|
||||||
"label": "Frontend",
|
|
||||||
"onAutoForward": "notify"
|
|
||||||
},
|
|
||||||
"8000": {
|
|
||||||
"label": "Backend"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@ -1 +0,0 @@
|
|||||||
@reflex-dev/reflex-team
|
|
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -2,6 +2,7 @@
|
|||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us improve
|
||||||
title: ''
|
title: ''
|
||||||
|
labels: ''
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
---
|
|
||||||
name: Custom Component Request
|
|
||||||
about: Suggest a new custom component for Reflex
|
|
||||||
title: ''
|
|
||||||
labels: 'custom component request'
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the Custom Component**
|
|
||||||
A clear and concise description of what the custom component does.
|
|
||||||
|
|
||||||
- What is the purpose of the custom component?
|
|
||||||
|
|
||||||
- What is the expected behavior of the custom component?
|
|
||||||
|
|
||||||
- What are the use cases for the custom component?
|
|
||||||
|
|
||||||
**Specifics (please complete the following information):**
|
|
||||||
- Do you have a specific react package in mind? (Optional):
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the custom component here.
|
|
19
.github/ISSUE_TEMPLATE/enhancement_request.md
vendored
19
.github/ISSUE_TEMPLATE/enhancement_request.md
vendored
@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
name: Enhancement Request
|
|
||||||
about: Suggest an enhancement for an existing Reflex feature.
|
|
||||||
title: ''
|
|
||||||
labels: 'enhancement'
|
|
||||||
assignees: ''
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the Enhancement you want**
|
|
||||||
A clear and concise description of what the improvement does.
|
|
||||||
|
|
||||||
- Which feature do you want to improve? (and what problem does it have)
|
|
||||||
|
|
||||||
- What is the benefit of the enhancement?
|
|
||||||
|
|
||||||
- Show an example/usecase were the improvement are needed.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context here.
|
|
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature Request
|
|
||||||
about: Suggest a new feature for Reflex
|
|
||||||
title: ''
|
|
||||||
labels: 'feature request'
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the Features**
|
|
||||||
A clear and concise description of what the features does.
|
|
||||||
|
|
||||||
- What is the purpose of the feature?
|
|
||||||
|
|
||||||
- Show an example / use cases for the new feature.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context here.
|
|
21
.github/actions/setup_build_env/action.yml
vendored
21
.github/actions/setup_build_env/action.yml
vendored
@ -6,7 +6,7 @@
|
|||||||
#
|
#
|
||||||
# Exit conditions:
|
# Exit conditions:
|
||||||
# - Python of version `python-version` is ready to be invoked as `python`.
|
# - Python of version `python-version` is ready to be invoked as `python`.
|
||||||
# - Poetry of version `poetry-version` is ready to be invoked as `poetry`.
|
# - Poetry of version `poetry-version` is ready ot be invoked as `poetry`.
|
||||||
# - If `run-poetry-install` is true, deps as defined in `pyproject.toml` will have been installed into the venv at `create-venv-at-path`.
|
# - If `run-poetry-install` is true, deps as defined in `pyproject.toml` will have been installed into the venv at `create-venv-at-path`.
|
||||||
|
|
||||||
name: 'Setup Reflex build environment'
|
name: 'Setup Reflex build environment'
|
||||||
@ -18,7 +18,7 @@ inputs:
|
|||||||
poetry-version:
|
poetry-version:
|
||||||
description: 'Poetry version to install'
|
description: 'Poetry version to install'
|
||||||
required: false
|
required: false
|
||||||
default: '1.8.3'
|
default: '1.3.1'
|
||||||
run-poetry-install:
|
run-poetry-install:
|
||||||
description: 'Whether to run poetry install on current dir'
|
description: 'Whether to run poetry install on current dir'
|
||||||
required: false
|
required: false
|
||||||
@ -29,10 +29,10 @@ inputs:
|
|||||||
default: '.venv'
|
default: '.venv'
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: 'composite'
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Python ${{ inputs.python-version }}
|
- name: Set up Python ${{ inputs.python-version }}
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: ${{ inputs.python-version }}
|
python-version: ${{ inputs.python-version }}
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ runs:
|
|||||||
|
|
||||||
- name: Restore cached poetry install
|
- name: Restore cached poetry install
|
||||||
id: restore-poetry-cache
|
id: restore-poetry-cache
|
||||||
uses: actions/cache/restore@v4
|
uses: actions/cache/restore@v3
|
||||||
with:
|
with:
|
||||||
path: ~/.local
|
path: ~/.local
|
||||||
key: ${{ runner.os }}-python-${{ inputs.python-version }}-poetry-${{ inputs.poetry-version }}
|
key: ${{ runner.os }}-python-${{ inputs.python-version }}-poetry-${{ inputs.poetry-version }}
|
||||||
@ -67,14 +67,14 @@ runs:
|
|||||||
|
|
||||||
- if: steps.restore-poetry-cache.outputs.cache-hit != 'true'
|
- if: steps.restore-poetry-cache.outputs.cache-hit != 'true'
|
||||||
name: Save poetry install to cache
|
name: Save poetry install to cache
|
||||||
uses: actions/cache/save@v4
|
uses: actions/cache/save@v3
|
||||||
with:
|
with:
|
||||||
path: ~/.local
|
path: ~/.local
|
||||||
key: ${{ steps.restore-poetry-cache.outputs.cache-primary-key }}
|
key: ${{ steps.restore-poetry-cache.outputs.cache-primary-key }}
|
||||||
|
|
||||||
- name: Restore cached project python deps
|
- name: Restore cached project python deps
|
||||||
id: restore-pydeps-cache
|
id: restore-pydeps-cache
|
||||||
uses: actions/cache/restore@v4
|
uses: actions/cache/restore@v3
|
||||||
with:
|
with:
|
||||||
path: ${{ inputs.create-venv-at-path }}
|
path: ${{ inputs.create-venv-at-path }}
|
||||||
key: ${{ runner.os }}-python-${{ inputs.python-version }}-pydeps-${{ hashFiles('**/poetry.lock') }}
|
key: ${{ runner.os }}-python-${{ inputs.python-version }}-pydeps-${{ hashFiles('**/poetry.lock') }}
|
||||||
@ -93,7 +93,7 @@ runs:
|
|||||||
|
|
||||||
- if: steps.restore-pydeps-cache.outputs.cache-hit != 'true'
|
- if: steps.restore-pydeps-cache.outputs.cache-hit != 'true'
|
||||||
name: Save Python deps to cache
|
name: Save Python deps to cache
|
||||||
uses: actions/cache/save@v4
|
uses: actions/cache/save@v3
|
||||||
with:
|
with:
|
||||||
path: ${{ inputs.create-venv-at-path }}
|
path: ${{ inputs.create-venv-at-path }}
|
||||||
key: ${{ steps.restore-pydeps-cache.outputs.cache-primary-key }}
|
key: ${{ steps.restore-pydeps-cache.outputs.cache-primary-key }}
|
||||||
@ -106,8 +106,3 @@ runs:
|
|||||||
run: |
|
run: |
|
||||||
source ${{ inputs.create-venv-at-path }}/*/activate
|
source ${{ inputs.create-venv-at-path }}/*/activate
|
||||||
poetry install --only-root --no-interaction
|
poetry install --only-root --no-interaction
|
||||||
|
|
||||||
- name: Install uv
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
poetry run pip install uv
|
|
||||||
|
2
.github/codeql-config.yml
vendored
2
.github/codeql-config.yml
vendored
@ -1,2 +0,0 @@
|
|||||||
paths-ignore:
|
|
||||||
- "**/tests/**"
|
|
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
@ -1,7 +1,7 @@
|
|||||||
### All Submissions:
|
### All Submissions:
|
||||||
|
|
||||||
- [ ] Have you followed the guidelines stated in [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) file?
|
- [ ] Have you followed the guidelines stated in [CONTRIBUTING.md](https://github.com/pynecone-io/pynecone/blob/main/CONTRIBUTING.md) file?
|
||||||
- [ ] Have you checked to ensure there aren't any other open [Pull Requests](https://github.com/reflex-dev/reflex/pulls ) for the desired changed?
|
- [ ] Have you checked to ensure there aren't any other open [Pull Requests](https://github.com/pynecone-io/pynecone/pulls ) for the desired changed?
|
||||||
|
|
||||||
<!-- You can erase any parts of this template not applicable to your Pull Request. -->
|
<!-- You can erase any parts of this template not applicable to your Pull Request. -->
|
||||||
|
|
||||||
|
143
.github/workflows/benchmarks.yml
vendored
143
.github/workflows/benchmarks.yml
vendored
@ -1,143 +0,0 @@
|
|||||||
name: benchmarking
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types:
|
|
||||||
- closed
|
|
||||||
paths-ignore:
|
|
||||||
- "**/*.md"
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
env:
|
|
||||||
PYTHONIOENCODING: "utf8"
|
|
||||||
TELEMETRY_ENABLED: false
|
|
||||||
NODE_OPTIONS: "--max_old_space_size=8192"
|
|
||||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
reflex-web:
|
|
||||||
# if: github.event.pull_request.merged == true
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
# Show OS combos first in GUI
|
|
||||||
os: [ubuntu-latest]
|
|
||||||
python-version: ["3.12.8"]
|
|
||||||
node-version: ["18.x"]
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
- uses: ./.github/actions/setup_build_env
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python-version }}
|
|
||||||
run-poetry-install: true
|
|
||||||
create-venv-at-path: .venv
|
|
||||||
|
|
||||||
- name: Clone Reflex Website Repo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: reflex-dev/reflex-web
|
|
||||||
ref: main
|
|
||||||
path: reflex-web
|
|
||||||
|
|
||||||
- name: Install Requirements for reflex-web
|
|
||||||
working-directory: ./reflex-web
|
|
||||||
run: poetry run uv pip install -r requirements.txt
|
|
||||||
- name: Init Website for reflex-web
|
|
||||||
working-directory: ./reflex-web
|
|
||||||
run: poetry run reflex init
|
|
||||||
- name: Install LightHouse Pre-reqs / Run LightHouse
|
|
||||||
run: |
|
|
||||||
# Check that npm is home
|
|
||||||
npm -v
|
|
||||||
poetry run bash benchmarks/lighthouse.sh ./reflex-web prod
|
|
||||||
env:
|
|
||||||
LHCI_GITHUB_APP_TOKEN: $
|
|
||||||
- name: Run Benchmarks
|
|
||||||
# Only run if the database creds are available in this context.
|
|
||||||
run: poetry run python benchmarks/benchmark_lighthouse.py "$GITHUB_SHA" ./integration/benchmarks/.lighthouseci
|
|
||||||
env:
|
|
||||||
GITHUB_SHA: ${{ github.sha }}
|
|
||||||
|
|
||||||
reflex-dist-size: # This job is used to calculate the size of the Reflex distribution (wheel file)
|
|
||||||
if: github.event.pull_request.merged == true
|
|
||||||
timeout-minutes: 30
|
|
||||||
strategy:
|
|
||||||
# Prioritize getting more information out of the workflow (even if something fails)
|
|
||||||
fail-fast: false
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: ./.github/actions/setup_build_env
|
|
||||||
with:
|
|
||||||
python-version: 3.12.8
|
|
||||||
run-poetry-install: true
|
|
||||||
create-venv-at-path: .venv
|
|
||||||
- name: Build reflex
|
|
||||||
run: |
|
|
||||||
poetry build
|
|
||||||
- name: Upload benchmark results
|
|
||||||
# Only run if the database creds are available in this context.
|
|
||||||
run:
|
|
||||||
poetry run python benchmarks/benchmark_package_size.py --os ubuntu-latest
|
|
||||||
--python-version 3.12.8 --commit-sha "${{ github.sha }}" --pr-id "${{ github.event.pull_request.id }}"
|
|
||||||
--branch-name "${{ github.head_ref || github.ref_name }}"
|
|
||||||
--path ./dist
|
|
||||||
|
|
||||||
reflex-venv-size: # This job calculates the total size of Reflex and its dependencies
|
|
||||||
if: github.event.pull_request.merged == true
|
|
||||||
timeout-minutes: 30
|
|
||||||
strategy:
|
|
||||||
# Prioritize getting more information out of the workflow (even if something fails)
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
# Show OS combos first in GUI
|
|
||||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
||||||
python-version: ["3.12.8"]
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Set up python
|
|
||||||
id: setup-python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python-version }}
|
|
||||||
- name: Install Poetry
|
|
||||||
uses: snok/install-poetry@v1
|
|
||||||
with:
|
|
||||||
version: 1.3.1
|
|
||||||
virtualenvs-create: true
|
|
||||||
virtualenvs-in-project: true
|
|
||||||
virtualenvs-path: .venv
|
|
||||||
|
|
||||||
- name: Run poetry install
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
python -m venv .venv
|
|
||||||
source .venv/*/activate
|
|
||||||
poetry install --without dev --no-interaction --no-root
|
|
||||||
|
|
||||||
- name: Install uv
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
poetry run pip install uv
|
|
||||||
|
|
||||||
- name: calculate and upload size
|
|
||||||
run:
|
|
||||||
poetry run python benchmarks/benchmark_package_size.py --os "${{ matrix.os }}"
|
|
||||||
--python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
|
|
||||||
--pr-id "${{ github.event.pull_request.id }}"
|
|
||||||
--branch-name "${{ github.head_ref || github.ref_name }}"
|
|
||||||
--path ./.venv
|
|
40
.github/workflows/check_generated_pyi.yml
vendored
40
.github/workflows/check_generated_pyi.yml
vendored
@ -1,40 +0,0 @@
|
|||||||
name: check-generated-pyi
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: ["main"]
|
|
||||||
# We don't just trigger on make_pyi.py and the components dir, because
|
|
||||||
# there are other things that can change the generator output
|
|
||||||
# e.g. black version, reflex.Component, reflex.Var.
|
|
||||||
paths-ignore:
|
|
||||||
- "**/*.md"
|
|
||||||
pull_request:
|
|
||||||
branches: ["main"]
|
|
||||||
paths-ignore:
|
|
||||||
- "**/*.md"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check-generated-pyi-components:
|
|
||||||
timeout-minutes: 30
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: ./.github/actions/setup_build_env
|
|
||||||
with:
|
|
||||||
python-version: "3.12.8"
|
|
||||||
run-poetry-install: true
|
|
||||||
create-venv-at-path: .venv
|
|
||||||
- run: |
|
|
||||||
poetry run python scripts/make_pyi.py
|
|
||||||
if [[ $(git status --porcelain) ]]; then
|
|
||||||
git status
|
|
||||||
git diff
|
|
||||||
echo "ERROR: make_pyi.py output is out of date. Please run scripts/make_pyi.py and commit the changes."
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "No diffs - AOK!"
|
|
||||||
fi
|
|
40
.github/workflows/check_node_latest.yml
vendored
40
.github/workflows/check_node_latest.yml
vendored
@ -1,40 +0,0 @@
|
|||||||
name: integration-node-latest
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
env:
|
|
||||||
TELEMETRY_ENABLED: false
|
|
||||||
REFLEX_USE_SYSTEM_NODE: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check_latest_node:
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
python-version: ["3.12.8"]
|
|
||||||
split_index: [1, 2]
|
|
||||||
node-version: ["node"]
|
|
||||||
fail-fast: false
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: ./.github/actions/setup_build_env
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python-version }}
|
|
||||||
run-poetry-install: true
|
|
||||||
create-venv-at-path: .venv
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
- run: |
|
|
||||||
poetry run uv pip install pyvirtualdisplay pillow pytest-split
|
|
||||||
poetry run playwright install --with-deps
|
|
||||||
- run: |
|
|
||||||
poetry run pytest tests/test_node_version.py
|
|
||||||
poetry run pytest tests/integration --splits 2 --group ${{matrix.split_index}}
|
|
@ -1,86 +0,0 @@
|
|||||||
name: check-outdated-dependencies
|
|
||||||
|
|
||||||
on:
|
|
||||||
push: # This will trigger the action when a pull request is opened or updated.
|
|
||||||
branches:
|
|
||||||
- "release/**" # This will trigger the action when any branch starting with "release/" is created.
|
|
||||||
workflow_dispatch: # Allow manual triggering if needed.
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
backend:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: ./.github/actions/setup_build_env
|
|
||||||
with:
|
|
||||||
python-version: '3.10'
|
|
||||||
run-poetry-install: true
|
|
||||||
create-venv-at-path: .venv
|
|
||||||
|
|
||||||
- name: Check outdated backend dependencies
|
|
||||||
run: |
|
|
||||||
outdated=$(poetry show -oT)
|
|
||||||
echo "Outdated:"
|
|
||||||
echo "$outdated"
|
|
||||||
|
|
||||||
filtered_outdated=$(echo "$outdated" | grep -vE 'pyright|ruff' || true)
|
|
||||||
|
|
||||||
if [ ! -z "$filtered_outdated" ]; then
|
|
||||||
echo "Outdated dependencies found:"
|
|
||||||
echo "$filtered_outdated"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "All dependencies are up to date. (pyright and ruff are ignored)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
frontend:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- uses: ./.github/actions/setup_build_env
|
|
||||||
with:
|
|
||||||
python-version: "3.10.16"
|
|
||||||
run-poetry-install: true
|
|
||||||
create-venv-at-path: .venv
|
|
||||||
- name: Clone Reflex Website Repo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: reflex-dev/reflex-web
|
|
||||||
ref: main
|
|
||||||
path: reflex-web
|
|
||||||
- name: Install Requirements for reflex-web
|
|
||||||
working-directory: ./reflex-web
|
|
||||||
run: poetry run uv pip install $(grep -ivE "reflex " requirements.txt)
|
|
||||||
- name: Install additional dependencies for DB access
|
|
||||||
run: poetry run uv pip install psycopg
|
|
||||||
- name: Init Website for reflex-web
|
|
||||||
working-directory: ./reflex-web
|
|
||||||
run: poetry run reflex init
|
|
||||||
- name: Run Website and Check for errors
|
|
||||||
run: |
|
|
||||||
poetry run bash scripts/integration.sh ./reflex-web dev
|
|
||||||
- name: Check outdated frontend dependencies
|
|
||||||
working-directory: ./reflex-web/.web
|
|
||||||
run: |
|
|
||||||
raw_outdated=$(/home/runner/.local/share/reflex/bun/bin/bun outdated)
|
|
||||||
outdated=$(echo "$raw_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\|' || true)
|
|
||||||
echo "Outdated:"
|
|
||||||
echo "$outdated"
|
|
||||||
|
|
||||||
# Ignore 3rd party dependencies that are not updated.
|
|
||||||
filtered_outdated=$(echo "$outdated" | grep -vE 'Package|@chakra-ui|lucide-react|@splinetool/runtime|ag-grid-react|framer-motion|react-markdown|remark-math|remark-gfm|rehype-katex|rehype-raw|remark-unwrap-images|ag-grid' || true)
|
|
||||||
no_extra=$(echo "$filtered_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-' || true)
|
|
||||||
|
|
||||||
|
|
||||||
if [ ! -z "$no_extra" ]; then
|
|
||||||
echo "Outdated dependencies found:"
|
|
||||||
echo "$filtered_outdated"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "All dependencies are up to date. (3rd party packages are ignored)"
|
|
||||||
fi
|
|
103
.github/workflows/codeql.yml
vendored
103
.github/workflows/codeql.yml
vendored
@ -1,103 +0,0 @@
|
|||||||
# For most projects, this workflow file will not need changing; you simply need
|
|
||||||
# to commit it to your repository.
|
|
||||||
#
|
|
||||||
# You may wish to alter this file to override the set of languages analyzed,
|
|
||||||
# or to provide custom queries or build logic.
|
|
||||||
#
|
|
||||||
# ******** NOTE ********
|
|
||||||
# We have attempted to detect the languages in your repository. Please check
|
|
||||||
# the `language` matrix defined below to confirm you have the correct set of
|
|
||||||
# supported CodeQL languages.
|
|
||||||
#
|
|
||||||
name: "CodeQL Advanced"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: ["main"]
|
|
||||||
pull_request:
|
|
||||||
branches: ["main"]
|
|
||||||
schedule:
|
|
||||||
- cron: "36 7 * * 4"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze (${{ matrix.language }})
|
|
||||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
|
||||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
|
||||||
# - https://gh.io/supported-runners-and-hardware-resources
|
|
||||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
|
||||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
|
||||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
|
||||||
permissions:
|
|
||||||
# required for all workflows
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
# required to fetch internal or private CodeQL packs
|
|
||||||
packages: read
|
|
||||||
|
|
||||||
# only required for workflows in private repositories
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- language: javascript-typescript
|
|
||||||
build-mode: none
|
|
||||||
- language: python
|
|
||||||
build-mode: none
|
|
||||||
- language: actions
|
|
||||||
build-mode: none
|
|
||||||
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
|
||||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
|
||||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
|
||||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
|
||||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
|
||||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
|
||||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
|
||||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
# Add any setup steps before running the `github/codeql-action/init` action.
|
|
||||||
# This includes steps like installing compilers or runtimes (`actions/setup-node`
|
|
||||||
# or others). This is typically only required for manual builds.
|
|
||||||
# - name: Setup runtime (example)
|
|
||||||
# uses: actions/setup-example@v1
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v3
|
|
||||||
with:
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
config-file: .github/codeql-config.yml
|
|
||||||
build-mode: ${{ matrix.build-mode }}
|
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
||||||
# By default, queries listed here will override any specified in a config file.
|
|
||||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
|
||||||
|
|
||||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
|
||||||
# queries: security-extended,security-and-quality
|
|
||||||
|
|
||||||
# If the analyze step fails for one of the languages you are analyzing with
|
|
||||||
# "We were unable to automatically build your code", modify the matrix above
|
|
||||||
# to set the build mode to "manual" for that language. Then modify this step
|
|
||||||
# to build your code.
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
|
||||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
|
||||||
- if: matrix.build-mode == 'manual'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
|
||||||
'languages you are analyzing, replace this with the commands to build' \
|
|
||||||
'your code, for example:'
|
|
||||||
echo ' make bootstrap'
|
|
||||||
echo ' make release'
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v3
|
|
||||||
with:
|
|
||||||
category: "/language:${{matrix.language}}"
|
|
17
.github/workflows/dependency-review.yml
vendored
17
.github/workflows/dependency-review.yml
vendored
@ -1,17 +0,0 @@
|
|||||||
name: 'Dependency Review'
|
|
||||||
on: [pull_request]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
dependency-review:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: 'Checkout Repository'
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: 'Dependency Review'
|
|
||||||
uses: actions/dependency-review-action@v4
|
|
||||||
with:
|
|
||||||
allow-licenses: Apache-2.0, BSD-2-Clause, BSD-3-Clause, HPND, ISC, MIT, MPL-2.0, Unlicense, Python-2.0, Python-2.0.1, Apache-2.0 AND MIT, BSD-2-Clause AND BSD-3-Clause, Apache-2.0 AND BSD-3-Clause
|
|
||||||
allow-dependencies-licenses: 'pkg:pypi/lazy-loader'
|
|
61
.github/workflows/integration_app_harness.yml
vendored
61
.github/workflows/integration_app_harness.yml
vendored
@ -1,56 +1,37 @@
|
|||||||
name: integration-app-harness
|
name: integration-app-harness
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["main"]
|
branches: [ "main" ]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- "**/*.md"
|
- '**/*.md'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ["main"]
|
branches: [ "main" ]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- "**/*.md"
|
- '**/*.md'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
integration-app-harness:
|
integration-app-harness:
|
||||||
timeout-minutes: 30
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
state_manager: ["redis", "memory"]
|
|
||||||
python-version: ["3.11.11", "3.12.8", "3.13.1"]
|
|
||||||
split_index: [1, 2]
|
|
||||||
fail-fast: false
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
services:
|
|
||||||
# Label used to access the service container
|
|
||||||
redis:
|
|
||||||
image: ${{ matrix.state_manager == 'redis' && 'redis' || '' }}
|
|
||||||
# Set health checks to wait until redis has started
|
|
||||||
options: >-
|
|
||||||
--health-cmd "redis-cli ping"
|
|
||||||
--health-interval 10s
|
|
||||||
--health-timeout 5s
|
|
||||||
--health-retries 5
|
|
||||||
ports:
|
|
||||||
# Maps port 6379 on service container to the host
|
|
||||||
- 6379:6379
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- uses: ./.github/actions/setup_build_env
|
- uses: ./.github/actions/setup_build_env
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: "3.11"
|
||||||
run-poetry-install: true
|
run-poetry-install: true
|
||||||
create-venv-at-path: .venv
|
create-venv-at-path: .venv
|
||||||
- run: poetry run uv pip install pyvirtualdisplay pillow pytest-split pytest-retry
|
- run: poetry run pip install pyvirtualdisplay pillow
|
||||||
- name: Run app harness tests
|
- name: Run app harness tests
|
||||||
env:
|
env:
|
||||||
REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }}
|
SCREENSHOT_DIR: /tmp/screenshots
|
||||||
run: |
|
run: |
|
||||||
poetry run playwright install chromium
|
poetry run pytest integration
|
||||||
poetry run pytest tests/integration --retries 3 --maxfail=5 --splits 2 --group ${{matrix.split_index}}
|
- uses: actions/upload-artifact@v3
|
||||||
|
name: Upload failed test screenshots
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: failed_test_screenshots
|
||||||
|
path: /tmp/screenshots
|
153
.github/workflows/integration_tests.yml
vendored
153
.github/workflows/integration_tests.yml
vendored
@ -2,17 +2,13 @@ name: integration-tests
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["main"]
|
branches: [ main ]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- "**/*.md"
|
- '**/*.md'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ["main"]
|
branches: [ main ]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- "**/*.md"
|
- '**/*.md'
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@ -28,52 +24,41 @@ env:
|
|||||||
# - Catch encoding errors when printing logs
|
# - Catch encoding errors when printing logs
|
||||||
# - Best effort print lines that contain illegal chars (map to some default char, etc.)
|
# - Best effort print lines that contain illegal chars (map to some default char, etc.)
|
||||||
PYTHONIOENCODING: "utf8"
|
PYTHONIOENCODING: "utf8"
|
||||||
TELEMETRY_ENABLED: false
|
|
||||||
NODE_OPTIONS: "--max_old_space_size=8192"
|
|
||||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
example-counter-and-nba-proxy:
|
example-counter:
|
||||||
env:
|
|
||||||
OUTPUT_FILE: import_benchmark.json
|
|
||||||
timeout-minutes: 30
|
|
||||||
strategy:
|
strategy:
|
||||||
# Prioritize getting more information out of the workflow (even if something fails)
|
# Prioritize getting more information out of the workflow (even if something fails)
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
# Show OS combos first in GUI
|
# Show OS combos first in GUI
|
||||||
os: [ubuntu-latest, windows-latest]
|
os: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||||
python-version: ['3.10.16', '3.11.11', '3.12.8', '3.13.1']
|
python-version: [ "3.8", "3.9", "3.10", "3.11" ]
|
||||||
exclude:
|
node-version: [ "16.x" ]
|
||||||
- os: windows-latest
|
|
||||||
python-version: "3.11.11"
|
|
||||||
- os: windows-latest
|
|
||||||
python-version: '3.10.16'
|
|
||||||
include:
|
|
||||||
- os: windows-latest
|
|
||||||
python-version: "3.11.9"
|
|
||||||
- os: windows-latest
|
|
||||||
python-version: '3.10.11'
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
- uses: ./.github/actions/setup_build_env
|
- uses: ./.github/actions/setup_build_env
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
run-poetry-install: true
|
run-poetry-install: true
|
||||||
create-venv-at-path: .venv
|
create-venv-at-path: .venv
|
||||||
|
|
||||||
- name: Clone Reflex Examples Repo
|
- name: Clone Reflex Examples Repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: reflex-dev/reflex-examples
|
repository: reflex-dev/reflex-examples
|
||||||
path: reflex-examples
|
path: reflex-examples
|
||||||
|
|
||||||
- name: Install requirements for counter example
|
- name: Install requirements for counter example
|
||||||
working-directory: ./reflex-examples/counter
|
working-directory: ./reflex-examples/counter
|
||||||
run: |
|
run: |
|
||||||
poetry run uv pip install -r requirements.txt
|
poetry run pip install -r requirements.txt
|
||||||
- name: Install additional dependencies for DB access
|
|
||||||
run: poetry run uv pip install psycopg
|
|
||||||
- name: Check export --backend-only before init for counter example
|
- name: Check export --backend-only before init for counter example
|
||||||
working-directory: ./reflex-examples/counter
|
working-directory: ./reflex-examples/counter
|
||||||
run: |
|
run: |
|
||||||
@ -94,40 +79,23 @@ jobs:
|
|||||||
# Check that npm is home
|
# Check that npm is home
|
||||||
npm -v
|
npm -v
|
||||||
poetry run bash scripts/integration.sh ./reflex-examples/counter dev
|
poetry run bash scripts/integration.sh ./reflex-examples/counter dev
|
||||||
- name: Install requirements for nba proxy example
|
|
||||||
working-directory: ./reflex-examples/nba-proxy
|
|
||||||
run: |
|
|
||||||
poetry run uv pip install -r requirements.txt
|
|
||||||
- name: Install additional dependencies for DB access
|
|
||||||
run: poetry run uv pip install psycopg
|
|
||||||
- name: Check export --backend-only before init for nba-proxy example
|
|
||||||
working-directory: ./reflex-examples/nba-proxy
|
|
||||||
run: |
|
|
||||||
poetry run reflex export --backend-only
|
|
||||||
- name: Init Website for nba-proxy example
|
|
||||||
working-directory: ./reflex-examples/nba-proxy
|
|
||||||
run: |
|
|
||||||
poetry run reflex init --loglevel debug
|
|
||||||
- name: Run Website and Check for errors
|
|
||||||
run: |
|
|
||||||
# Check that npm is home
|
|
||||||
npm -v
|
|
||||||
poetry run bash scripts/integration.sh ./reflex-examples/nba-proxy dev
|
|
||||||
|
|
||||||
|
|
||||||
reflex-web:
|
reflex-web:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
# Show OS combos first in GUI
|
# Show OS combos first in GUI
|
||||||
os: [ubuntu-latest]
|
os: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||||
python-version: ["3.11.11", "3.12.8"]
|
python-version: [ "3.10", "3.11" ]
|
||||||
|
node-version: [ "16.x" ]
|
||||||
|
|
||||||
env:
|
|
||||||
REFLEX_WEB_WINDOWS_OVERRIDE: "1"
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
- uses: ./.github/actions/setup_build_env
|
- uses: ./.github/actions/setup_build_env
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
@ -135,80 +103,15 @@ jobs:
|
|||||||
create-venv-at-path: .venv
|
create-venv-at-path: .venv
|
||||||
|
|
||||||
- name: Clone Reflex Website Repo
|
- name: Clone Reflex Website Repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: reflex-dev/reflex-web
|
repository: reflex-dev/reflex-web
|
||||||
ref: main
|
ref: reflex-ci
|
||||||
path: reflex-web
|
path: reflex-web
|
||||||
|
|
||||||
- name: Install Requirements for reflex-web
|
- name: Install Requirements for reflex-web
|
||||||
working-directory: ./reflex-web
|
working-directory: ./reflex-web
|
||||||
run: poetry run uv pip install $(grep -ivE "reflex " requirements.txt)
|
run: poetry run pip install -r requirements.txt
|
||||||
- name: Install additional dependencies for DB access
|
|
||||||
run: poetry run uv pip install psycopg
|
|
||||||
- name: Init Website for reflex-web
|
|
||||||
working-directory: ./reflex-web
|
|
||||||
run: poetry run reflex init
|
|
||||||
- name: Run Website and Check for errors
|
|
||||||
run: |
|
|
||||||
# Check that npm is home
|
|
||||||
npm -v
|
|
||||||
poetry run bash scripts/integration.sh ./reflex-web prod
|
|
||||||
|
|
||||||
rx-shout-from-template:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: ./.github/actions/setup_build_env
|
|
||||||
with:
|
|
||||||
python-version: "3.11.11"
|
|
||||||
run-poetry-install: true
|
|
||||||
create-venv-at-path: .venv
|
|
||||||
- name: Create app directory
|
|
||||||
run: mkdir rx-shout-from-template
|
|
||||||
- name: Init reflex-web from template
|
|
||||||
run: poetry run reflex init --template https://github.com/masenf/rx_shout
|
|
||||||
working-directory: ./rx-shout-from-template
|
|
||||||
- name: ignore reflex pin in requirements
|
|
||||||
run: sed -i -e '/reflex==/d' requirements.txt
|
|
||||||
working-directory: ./rx-shout-from-template
|
|
||||||
- name: Install additional dependencies
|
|
||||||
run: poetry run uv pip install -r requirements.txt
|
|
||||||
working-directory: ./rx-shout-from-template
|
|
||||||
- name: Run Website and Check for errors
|
|
||||||
run: |
|
|
||||||
# Check that npm is home
|
|
||||||
npm -v
|
|
||||||
poetry run bash scripts/integration.sh ./rx-shout-from-template prod
|
|
||||||
|
|
||||||
reflex-web-macos:
|
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
# Note: py311 version chosen due to available arm64 darwin builds.
|
|
||||||
python-version: ["3.11.9", "3.12.8"]
|
|
||||||
runs-on: macos-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: ./.github/actions/setup_build_env
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python-version }}
|
|
||||||
run-poetry-install: true
|
|
||||||
create-venv-at-path: .venv
|
|
||||||
- name: Clone Reflex Website Repo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: reflex-dev/reflex-web
|
|
||||||
ref: main
|
|
||||||
path: reflex-web
|
|
||||||
- name: Install Requirements for reflex-web
|
|
||||||
working-directory: ./reflex-web
|
|
||||||
run: poetry run uv pip install -r requirements.txt
|
|
||||||
- name: Install additional dependencies for DB access
|
|
||||||
run: poetry run uv pip install psycopg
|
|
||||||
- name: Init Website for reflex-web
|
- name: Init Website for reflex-web
|
||||||
working-directory: ./reflex-web
|
working-directory: ./reflex-web
|
||||||
run: poetry run reflex init
|
run: poetry run reflex init
|
||||||
|
33
.github/workflows/integration_tests_wsl.yml
vendored
33
.github/workflows/integration_tests_wsl.yml
vendored
@ -1,44 +1,33 @@
|
|||||||
name: integration-tests-wsl
|
name: integration-tests-wsl
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ['main']
|
branches: [ "main" ]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**/*.md'
|
- '**/*.md'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ['main']
|
branches: [ main ]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**/*.md'
|
- '**/*.md'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
env:
|
|
||||||
TELEMETRY_ENABLED: false
|
|
||||||
NODE_OPTIONS: '--max_old_space_size=4096'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
example-counter-wsl:
|
example-counter-wsl:
|
||||||
timeout-minutes: 30
|
|
||||||
# 2019 is more stable with WSL in GH actions
|
# 2019 is more stable with WSL in GH actions
|
||||||
# https://github.com/actions/runner-images/issues/5151
|
# https://github.com/actions/runner-images/issues/5151
|
||||||
# Confirmed through trial and error. 2022 has >80% failure rate (probably BSOD)
|
# Confirmed through trial and error. 2022 has >80% failure rate (probably BSOD)
|
||||||
runs-on: windows-2019
|
runs-on: windows-2019
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- name: Clone Reflex Examples Repo
|
- name: Clone Reflex Examples Repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: reflex-dev/reflex-examples
|
repository: reflex-dev/reflex-examples
|
||||||
path: reflex-examples
|
path: reflex-examples
|
||||||
|
|
||||||
- uses: Vampire/setup-wsl@v3
|
- uses: Vampire/setup-wsl@v2
|
||||||
with:
|
|
||||||
distribution: Ubuntu-24.04
|
|
||||||
|
|
||||||
- name: Install Python
|
- name: Install Python
|
||||||
shell: wsl-bash {0}
|
shell: wsl-bash {0}
|
||||||
@ -58,42 +47,32 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
poetry install
|
poetry install
|
||||||
|
|
||||||
- name: Install uv
|
|
||||||
shell: wsl-bash {0}
|
|
||||||
run: |
|
|
||||||
poetry run pip install uv
|
|
||||||
|
|
||||||
- name: Install requirements for counter example
|
- name: Install requirements for counter example
|
||||||
working-directory: ./reflex-examples/counter
|
working-directory: ./reflex-examples/counter
|
||||||
shell: wsl-bash {0}
|
shell: wsl-bash {0}
|
||||||
run: |
|
run: |
|
||||||
poetry run uv pip install -r requirements.txt
|
poetry run pip install -r requirements.txt
|
||||||
- name: Check export --backend-only before init for counter example
|
- name: Check export --backend-only before init for counter example
|
||||||
working-directory: ./reflex-examples/counter
|
working-directory: ./reflex-examples/counter
|
||||||
shell: wsl-bash {0}
|
shell: wsl-bash {0}
|
||||||
run: |
|
run: |
|
||||||
export TELEMETRY_ENABLED=false
|
|
||||||
poetry run reflex export --backend-only
|
poetry run reflex export --backend-only
|
||||||
- name: Check run --backend-only before init for counter example
|
- name: Check run --backend-only before init for counter example
|
||||||
shell: wsl-bash {0}
|
shell: wsl-bash {0}
|
||||||
run: |
|
run: |
|
||||||
export TELEMETRY_ENABLED=false
|
|
||||||
dos2unix scripts/integration.sh
|
dos2unix scripts/integration.sh
|
||||||
poetry run bash scripts/integration.sh ./reflex-examples/counter dev 8001 --backend-only --backend-port 8001
|
poetry run bash scripts/integration.sh ./reflex-examples/counter dev 8001 --backend-only --backend-port 8001
|
||||||
- name: Init Website for counter example
|
- name: Init Website for counter example
|
||||||
working-directory: ./reflex-examples/counter
|
working-directory: ./reflex-examples/counter
|
||||||
shell: wsl-bash {0}
|
shell: wsl-bash {0}
|
||||||
run: |
|
run: |
|
||||||
export TELEMETRY_ENABLED=false
|
|
||||||
poetry run reflex init --loglevel debug
|
poetry run reflex init --loglevel debug
|
||||||
- name: Check export for counter example
|
- name: Check export for counter example
|
||||||
working-directory: ./reflex-examples/counter
|
working-directory: ./reflex-examples/counter
|
||||||
shell: wsl-bash {0}
|
shell: wsl-bash {0}
|
||||||
run: |
|
run: |
|
||||||
export TELEMETRY_ENABLED=false
|
|
||||||
poetry run reflex export --frontend-only --loglevel debug
|
poetry run reflex export --frontend-only --loglevel debug
|
||||||
- name: Run Website and Check for errors
|
- name: Run Website and Check for errors
|
||||||
shell: wsl-bash {0}
|
shell: wsl-bash {0}
|
||||||
run: |
|
run: |
|
||||||
export TELEMETRY_ENABLED=false
|
|
||||||
poetry run bash scripts/integration.sh ./reflex-examples/counter dev
|
poetry run bash scripts/integration.sh ./reflex-examples/counter dev
|
||||||
|
34
.github/workflows/performance.yml
vendored
34
.github/workflows/performance.yml
vendored
@ -1,34 +0,0 @@
|
|||||||
name: performance-tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "main" # or "master"
|
|
||||||
paths-ignore:
|
|
||||||
- "**/*.md"
|
|
||||||
pull_request:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
TELEMETRY_ENABLED: false
|
|
||||||
NODE_OPTIONS: "--max_old_space_size=8192"
|
|
||||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
||||||
APP_HARNESS_HEADLESS: 1
|
|
||||||
PYTHONUNBUFFERED: 1
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
benchmarks:
|
|
||||||
name: Run benchmarks
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: ./.github/actions/setup_build_env
|
|
||||||
with:
|
|
||||||
python-version: 3.12.8
|
|
||||||
run-poetry-install: true
|
|
||||||
create-venv-at-path: .venv
|
|
||||||
- name: Run benchmarks
|
|
||||||
uses: CodSpeedHQ/action@v3
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
|
||||||
run: poetry run pytest tests/benchmarks --codspeed
|
|
18
.github/workflows/pre-commit.yml
vendored
18
.github/workflows/pre-commit.yml
vendored
@ -1,34 +1,28 @@
|
|||||||
name: pre-commit
|
name: pre-commit
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ["main"]
|
branches: [main]
|
||||||
push:
|
push:
|
||||||
# Note even though this job is called "pre-commit" and runs "pre-commit", this job will run
|
# Note even though this job is called "pre-commit" and runs "pre-commit", this job will run
|
||||||
# also POST-commit on main also! In case there are mishandled merge conflicts / bad auto-resolves
|
# also POST-commit on main also! In case there are mishandled merge conflicts / bad auto-resolves
|
||||||
# when merging into main branch.
|
# when merging into main branch.
|
||||||
branches: ["main"]
|
branches: [main]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre-commit:
|
pre-commit:
|
||||||
timeout-minutes: 30
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- uses: ./.github/actions/setup_build_env
|
- uses: ./.github/actions/setup_build_env
|
||||||
with:
|
with:
|
||||||
# running vs. one version of Python is OK
|
# running vs. one version of Python is OK
|
||||||
# i.e. ruff, black, etc.
|
# i.e. ruff, black, etc.
|
||||||
python-version: 3.12.8
|
python-version: 3.11
|
||||||
run-poetry-install: true
|
run-poetry-install: true
|
||||||
|
shell: bash
|
||||||
create-venv-at-path: .venv
|
create-venv-at-path: .venv
|
||||||
# TODO pre-commit related stuff can be cached too (not a bottleneck yet)
|
# TODO pre-commit related stuff can be cached too (not a bottleneck yet)
|
||||||
- run: |
|
- run: |
|
||||||
poetry run uv pip install pre-commit
|
poetry run pip install pre-commit
|
||||||
poetry run pre-commit run --all-files
|
poetry run pre-commit run --all-files
|
||||||
env:
|
|
||||||
SKIP: update-pyi-files
|
|
||||||
|
24
.github/workflows/reflex_init_in_docker_test.yml
vendored
24
.github/workflows/reflex_init_in_docker_test.yml
vendored
@ -1,16 +1,13 @@
|
|||||||
name: reflex-init-in-docker-test
|
name: reflex-init-in-docker-test
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ['main']
|
branches: [ "main" ]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**/*.md'
|
- '**/*.md'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ['main']
|
branches:
|
||||||
|
- main
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**/*.md'
|
- '**/*.md'
|
||||||
|
|
||||||
@ -18,15 +15,14 @@ jobs:
|
|||||||
# TODO we can extend to various starting points (e.g. Ubuntu with node, without node, with unzip, without unzip, etc.)
|
# TODO we can extend to various starting points (e.g. Ubuntu with node, without node, with unzip, without unzip, etc.)
|
||||||
# Currently starting point is: Ubuntu + unzip, xz-utils, Python suite. No node.
|
# Currently starting point is: Ubuntu + unzip, xz-utils, Python suite. No node.
|
||||||
reflex-install-and-init:
|
reflex-install-and-init:
|
||||||
timeout-minutes: 30
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- shell: bash
|
- shell: bash
|
||||||
run: |
|
run: |
|
||||||
# Run reflex init in a docker container
|
# Run reflex init in a docker container
|
||||||
|
|
||||||
# cwd is repo root
|
# cwd is repo root
|
||||||
docker build -f tests/integration/init-test/Dockerfile -t reflex-init-test tests/integration/init-test
|
docker build -f integration/init-test/Dockerfile -t reflex-init-test integration/init-test
|
||||||
docker run --rm -v $(pwd):/reflex-repo/ reflex-init-test /reflex-repo/tests/integration/init-test/in_docker_test_script.sh
|
docker run --rm -v $(pwd):/reflex-repo/ reflex-init-test /reflex-repo/integration/init-test/in_docker_test_script.sh
|
||||||
|
103
.github/workflows/unit_tests.yml
vendored
103
.github/workflows/unit_tests.yml
vendored
@ -1,109 +1,40 @@
|
|||||||
name: unit-tests
|
name: unit-tests
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["main"]
|
branches: [ "main" ]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- "**/*.md"
|
- '**/*.md'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ["main"]
|
branches: [ "main" ]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- "**/*.md"
|
- '**/*.md'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
unit-tests:
|
unit-tests:
|
||||||
timeout-minutes: 30
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, windows-latest]
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
python-version: ["3.10.16", "3.11.11", "3.12.8", "3.13.1"]
|
# TODO consider bringing back 3.7. Note Windows x 3.7 fails from some sqlalchemy related problem
|
||||||
# Windows is a bit behind on Python version availability in Github
|
python-version: ["3.8", "3.9", "3.10", "3.11"]
|
||||||
exclude:
|
|
||||||
- os: windows-latest
|
|
||||||
python-version: "3.11.11"
|
|
||||||
- os: windows-latest
|
|
||||||
python-version: "3.10.16"
|
|
||||||
include:
|
|
||||||
- os: windows-latest
|
|
||||||
python-version: "3.11.9"
|
|
||||||
- os: windows-latest
|
|
||||||
python-version: "3.10.11"
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
# Service containers to run with `runner-job`
|
|
||||||
services:
|
|
||||||
# Label used to access the service container
|
|
||||||
redis:
|
|
||||||
image: ${{ matrix.os == 'ubuntu-latest' && 'redis' || '' }}
|
|
||||||
# Set health checks to wait until redis has started
|
|
||||||
options: >-
|
|
||||||
--health-cmd "redis-cli ping"
|
|
||||||
--health-interval 10s
|
|
||||||
--health-timeout 5s
|
|
||||||
--health-retries 5
|
|
||||||
ports:
|
|
||||||
# Maps port 6379 on service container to the host
|
|
||||||
- 6379:6379
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- uses: ./.github/actions/setup_build_env
|
- uses: ./.github/actions/setup_build_env
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
run-poetry-install: true
|
run-poetry-install: true
|
||||||
create-venv-at-path: .venv
|
create-venv-at-path: .venv
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
run: |
|
run: |
|
||||||
export PYTHONUNBUFFERED=1
|
export PYTHONUNBUFFERED=1
|
||||||
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
|
poetry run pytest tests --cov --no-cov-on-fail --cov-report=
|
||||||
- name: Run unit tests w/ redis
|
- run: poetry run coverage html
|
||||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
|
||||||
run: |
|
|
||||||
export PYTHONUNBUFFERED=1
|
|
||||||
export REDIS_URL=redis://localhost:6379
|
|
||||||
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
|
|
||||||
# Change to explicitly install v1 when reflex-hosting-cli is compatible with v2
|
|
||||||
- name: Run unit tests w/ pydantic v1
|
|
||||||
run: |
|
|
||||||
export PYTHONUNBUFFERED=1
|
|
||||||
poetry run uv pip install "pydantic~=1.10"
|
|
||||||
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
|
|
||||||
- name: Generate coverage report
|
|
||||||
run: poetry run coverage html
|
|
||||||
|
|
||||||
unit-tests-macos:
|
|
||||||
timeout-minutes: 30
|
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
# Note: py310, py311 versions chosen due to available arm64 darwin builds.
|
|
||||||
python-version: ["3.10.11", "3.11.9", "3.12.8", "3.13.1"]
|
|
||||||
runs-on: macos-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: ./.github/actions/setup_build_env
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python-version }}
|
|
||||||
run-poetry-install: true
|
|
||||||
create-venv-at-path: .venv
|
|
||||||
- name: Run unit tests
|
|
||||||
run: |
|
|
||||||
export PYTHONUNBUFFERED=1
|
|
||||||
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
|
|
||||||
- name: Run unit tests w/ pydantic v1
|
|
||||||
run: |
|
|
||||||
export PYTHONUNBUFFERED=1
|
|
||||||
poetry run uv pip install "pydantic~=1.10"
|
|
||||||
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
|
|
||||||
|
9
.gitignore
vendored
9
.gitignore
vendored
@ -1,18 +1,9 @@
|
|||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
**/*.pyc
|
**/*.pyc
|
||||||
assets/external/*
|
|
||||||
dist/*
|
dist/*
|
||||||
examples/
|
examples/
|
||||||
.web
|
|
||||||
.states
|
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
.coverage
|
.coverage
|
||||||
.coverage.*
|
.coverage.*
|
||||||
.venv
|
|
||||||
venv
|
venv
|
||||||
requirements.txt
|
|
||||||
.pyi_generator_last_run
|
|
||||||
.pyi_generator_diff
|
|
||||||
reflex.db
|
|
||||||
.codspeed
|
|
@ -1,43 +1,25 @@
|
|||||||
fail_fast: true
|
|
||||||
|
|
||||||
repos:
|
repos:
|
||||||
|
|
||||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||||
rev: v0.9.6
|
rev: v0.0.244
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff-format
|
- id: ruff
|
||||||
args: [reflex, tests]
|
args: [--fix, --exit-non-zero-on-fix]
|
||||||
- id: ruff
|
|
||||||
args: ["--fix", "--exit-non-zero-on-fix"]
|
|
||||||
exclude: '^integration/benchmarks/'
|
|
||||||
|
|
||||||
- repo: https://github.com/codespell-project/codespell
|
|
||||||
rev: v2.3.0
|
|
||||||
hooks:
|
|
||||||
- id: codespell
|
|
||||||
args: ["reflex"]
|
|
||||||
|
|
||||||
# Run pyi check before pyright because pyright can fail if pyi files are wrong.
|
|
||||||
- repo: local
|
|
||||||
hooks:
|
|
||||||
- id: update-pyi-files
|
|
||||||
name: update-pyi-files
|
|
||||||
always_run: true
|
|
||||||
language: system
|
|
||||||
require_serial: true
|
|
||||||
description: 'Update pyi files as needed'
|
|
||||||
entry: python3 scripts/make_pyi.py
|
|
||||||
|
|
||||||
- repo: https://github.com/RobertCraigie/pyright-python
|
- repo: https://github.com/RobertCraigie/pyright-python
|
||||||
rev: v1.1.393
|
rev: v1.1.313
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyright
|
- id: pyright
|
||||||
args: [reflex, tests]
|
args: [integration, reflex, tests]
|
||||||
language: system
|
language: system
|
||||||
|
|
||||||
- repo: https://github.com/terrencepreilly/darglint
|
- repo: https://github.com/terrencepreilly/darglint
|
||||||
rev: v1.8.1
|
rev: v1.8.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: darglint
|
- id: darglint
|
||||||
exclude: '^reflex/reflex.py'
|
exclude: '^reflex/reflex.py'
|
||||||
|
|
||||||
|
- repo: https://github.com/psf/black
|
||||||
|
rev: 22.10.0
|
||||||
|
hooks:
|
||||||
|
- id: black
|
||||||
|
args: [integration, reflex, tests]
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
We as members, contributors, and leaders pledge to make participation in our
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
community a harassment-free experience for everyone, regardless of age, body
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
identity and expression, level of experience, education, socioeconomic status,
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
nationality, personal appearance, race, religion, or sexual identity
|
nationality, personal appearance, race, religion, or sexual identity
|
||||||
and orientation.
|
and orientation.
|
||||||
|
|
||||||
|
@ -2,50 +2,58 @@
|
|||||||
|
|
||||||
For an extensive guide on the different ways to contribute to Reflex see our [Contributing Guide on Notion](https://www.notion.so/reflex-dev/2107ab2bc166497db951b8d742748284?v=f0eaff78fa984b5ab15d204af58907d7).
|
For an extensive guide on the different ways to contribute to Reflex see our [Contributing Guide on Notion](https://www.notion.so/reflex-dev/2107ab2bc166497db951b8d742748284?v=f0eaff78fa984b5ab15d204af58907d7).
|
||||||
|
|
||||||
## Running a Local Build of Reflex
|
|
||||||
|
|
||||||
Here is a quick guide on how to run Reflex repo locally so you can start contributing to the project.
|
|
||||||
|
## Running a Local Build of Reflex
|
||||||
|
Here is a quick guide to how the run Reflex repo locally so you can start contributing to the project.
|
||||||
|
|
||||||
**Prerequisites:**
|
**Prerequisites:**
|
||||||
|
- Python >= 3.7
|
||||||
- Python >= 3.10
|
|
||||||
- Poetry version >= 1.4.0 and add it to your path (see [Poetry Docs](https://python-poetry.org/docs/#installation) for more info).
|
- Poetry version >= 1.4.0 and add it to your path (see [Poetry Docs](https://python-poetry.org/docs/#installation) for more info).
|
||||||
|
|
||||||
**1. Fork this repository:**
|
|
||||||
Fork this repository by clicking on the `Fork` button on the top right.
|
|
||||||
|
|
||||||
**2. Clone Reflex and navigate into the repo:**
|
|
||||||
|
|
||||||
|
**1. Clone Reflex and navigate into the repo:**
|
||||||
``` bash
|
``` bash
|
||||||
git clone https://github.com/<YOUR-USERNAME>/reflex.git
|
git clone https://github.com/reflex-dev/reflex.git
|
||||||
cd reflex
|
cd reflex
|
||||||
```
|
```
|
||||||
|
|
||||||
**3. Install your local Reflex build:**
|
**2. Install your local Reflex build:**
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
poetry install
|
poetry install
|
||||||
```
|
```
|
||||||
|
**3. Now create an examples folder so you can test the local Python build in this repository.**
|
||||||
**4. Now create an examples folder so you can test the local Python build in this repository.**
|
* We have the `examples` folder in the `.gitignore`, so your changes in `reflex/examples` won't be reflected in your commit.
|
||||||
|
|
||||||
- We have the `examples` folder in the `.gitignore`, so your changes in `reflex/examples` won't be reflected in your commit.
|
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
mkdir examples
|
mkdir examples
|
||||||
cd examples
|
cd examples
|
||||||
```
|
```
|
||||||
|
|
||||||
**5. Init and Run**
|
**4. Init and Run**
|
||||||
|
``` bash
|
||||||
|
poetry run reflex init
|
||||||
|
poetry run reflex run
|
||||||
|
```
|
||||||
|
All the changes you make to the repository will be reflected in your running app.
|
||||||
|
|
||||||
|
|
||||||
|
## 🧪 Testing and QA
|
||||||
|
|
||||||
|
Within the 'test' directory of Reflex you can add to a test file already there or create a new test python file if it doesn't fit into the existing layout.
|
||||||
|
|
||||||
|
#### What to unit test?
|
||||||
|
- Any feature or significant change that has been added.
|
||||||
|
- Any edge cases or potential problem areas.
|
||||||
|
- Any interactions between different parts of the code.
|
||||||
|
|
||||||
|
Now Init/Run
|
||||||
``` bash
|
``` bash
|
||||||
poetry run reflex init
|
poetry run reflex init
|
||||||
poetry run reflex run
|
poetry run reflex run
|
||||||
```
|
```
|
||||||
|
|
||||||
All the changes you make to the repository will be reflected in your running app.
|
All the changes you make to the repository will be reflected in your running app.
|
||||||
|
* We have the examples folder in the .gitignore, so your changes in reflex/examples won't be reflected in your commit.
|
||||||
- We have the examples folder in the .gitignore, so your changes in reflex/examples won't be reflected in your commit.
|
|
||||||
|
|
||||||
## 🧪 Testing and QA
|
## 🧪 Testing and QA
|
||||||
|
|
||||||
@ -53,64 +61,38 @@ Any feature or significant change added should be accompanied with unit tests.
|
|||||||
|
|
||||||
Within the 'test' directory of Reflex you can add to a test file already there or create a new test python file if it doesn't fit into the existing layout.
|
Within the 'test' directory of Reflex you can add to a test file already there or create a new test python file if it doesn't fit into the existing layout.
|
||||||
|
|
||||||
#### What to unit test?
|
What to unit test?
|
||||||
|
|
||||||
- Any feature or significant change that has been added.
|
- Any feature or significant change that has been added.
|
||||||
- Any edge cases or potential problem areas.
|
- Any edge cases or potential problem areas.
|
||||||
- Any interactions between different parts of the code.
|
-Any interactions between different parts of the code.
|
||||||
|
|
||||||
|
|
||||||
## ✅ Making a PR
|
## ✅ Making a PR
|
||||||
|
|
||||||
Once you solve a current issue or improvement to Reflex, you can make a PR, and we will review the changes.
|
Once you solve a current issue or improvement to Reflex, you can make a pr, and we will review the changes.
|
||||||
|
|
||||||
Before submitting, a pull request, ensure the following steps are taken and test passing.
|
Before submitting, a pull request, ensure the following steps are taken and test passing.
|
||||||
|
|
||||||
In your `reflex` directory run make sure all the unit tests are still passing using the following command.
|
In your `reflex` directory run make sure all the unit tests are still passing using the following command.
|
||||||
This will fail if code coverage is below 70%.
|
This will fail if code coverage is below 80%.
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
|
poetry run pytest tests --cov --no-cov-on-fail --cov-report=
|
||||||
```
|
```
|
||||||
|
|
||||||
Next make sure all the following tests pass. This ensures that every new change has proper documentation and type checking.
|
Next make sure all the following tests pass. This ensures that every new change has proper documentation and type checking.
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
poetry run ruff check .
|
poetry run ruff check .
|
||||||
poetry run pyright reflex tests
|
poetry run pyright reflex tests
|
||||||
find reflex tests -name "*.py" -not -path reflex/reflex.py | xargs poetry run darglint
|
find reflex tests -name "*.py" -not -path reflex/reflex.py | xargs poetry run darglint
|
||||||
```
|
```
|
||||||
|
Finally, run `black` to format your code.
|
||||||
Finally, run `ruff` to format your code.
|
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
poetry run ruff format .
|
poetry run black reflex tests
|
||||||
```
|
```
|
||||||
|
|
||||||
Consider installing git pre-commit hooks so Ruff, Pyright, Darglint and `make_pyi` will run automatically before each commit.
|
Consider installing git pre-commit hooks so Ruff, Pyright, Darglint and Black will run automatically before each commit.
|
||||||
Note that pre-commit will only be installed when you use a Python version >= 3.10.
|
Note that pre-commit will only be installed when you use a Python version >= 3.8.
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
pre-commit install
|
pre-commit install
|
||||||
```
|
```
|
||||||
|
|
||||||
That's it you can now submit your PR. Thanks for contributing to Reflex!
|
That's it you can now submit your pr. Thanks for contributing to Reflex!
|
||||||
|
|
||||||
|
|
||||||
## Editing Templates
|
|
||||||
|
|
||||||
To edit the templates in Reflex you can do so in two way.
|
|
||||||
|
|
||||||
Change to the basic `blank` template can be done in the `reflex/.templates/apps/blank` directory.
|
|
||||||
|
|
||||||
Others templates can be edited in their own repository. For example the `sidebar` template can be found in the [`reflex-sidebar`](https://github.com/reflex-dev/sidebar-template) repository.
|
|
||||||
|
|
||||||
|
|
||||||
## Other Notes
|
|
||||||
|
|
||||||
For some pull requests when adding new components you will have to generate a pyi file for the new component. This is done by running the following command in the `reflex` directory.
|
|
||||||
|
|
||||||
(Please check in with the team before adding a new component to Reflex we are cautious about adding new components to Reflex's core.)
|
|
||||||
|
|
||||||
``` bash
|
|
||||||
poetry run python scripts/make_pyi.py
|
|
||||||
```
|
|
||||||
|
103
README.md
103
README.md
@ -10,31 +10,18 @@
|
|||||||
|
|
||||||
### **✨ Performant, customizable web apps in pure Python. Deploy in seconds. ✨**
|
### **✨ Performant, customizable web apps in pure Python. Deploy in seconds. ✨**
|
||||||
[](https://badge.fury.io/py/reflex)
|
[](https://badge.fury.io/py/reflex)
|
||||||
|

|
||||||

|

|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
[](https://reflex.dev/docs/getting-started/introduction)
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
[](https://discord.gg/T5WSbC2YtQ)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md)
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md) | [Tiếng Việt](https://github.com/reflex-dev/reflex/blob/main/docs/vi/README.md)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Reflex
|
|
||||||
|
|
||||||
Reflex is a library to build full-stack web apps in pure Python.
|
|
||||||
|
|
||||||
Key features:
|
|
||||||
* **Pure Python** - Write your app's frontend and backend all in Python, no need to learn Javascript.
|
|
||||||
* **Full Flexibility** - Reflex is easy to get started with, but can also scale to complex apps.
|
|
||||||
* **Deploy Instantly** - After building, deploy your app with a [single command](https://reflex.dev/docs/hosting/deploy-quick-start/) or host it on your own server.
|
|
||||||
|
|
||||||
See our [architecture page](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) to learn how Reflex works under the hood.
|
|
||||||
|
|
||||||
## ⚙️ Installation
|
## ⚙️ Installation
|
||||||
|
|
||||||
Open a terminal and run (Requires Python 3.10+):
|
Open a terminal and run (Requires Python 3.7+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
@ -67,7 +54,7 @@ Now you can modify the source code in `my_app_name/my_app_name.py`. Reflex has f
|
|||||||
|
|
||||||
## 🫧 Example App
|
## 🫧 Example App
|
||||||
|
|
||||||
Let's go over an example: creating an image generation UI around [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node). For simplicity, we just call the [OpenAI API](https://platform.openai.com/docs/api-reference/authentication), but you could replace this with an ML model run locally.
|
Let's go over an example: creating an image generation UI around DALL·E. For simplicity, we just call the OpenAI API, but you could replace this with an ML model run locally.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -79,18 +66,14 @@ Let's go over an example: creating an image generation UI around [DALL·E](https
|
|||||||
|
|
||||||
Here is the complete code to create this. This is all done in one Python file!
|
Here is the complete code to create this. This is all done in one Python file!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
import openai
|
import openai
|
||||||
|
|
||||||
openai_client = openai.OpenAI()
|
openai.api_key = "YOUR_API_KEY"
|
||||||
|
|
||||||
|
|
||||||
class State(rx.State):
|
class State(rx.State):
|
||||||
"""The app state."""
|
"""The app state."""
|
||||||
|
|
||||||
prompt = ""
|
prompt = ""
|
||||||
image_url = ""
|
image_url = ""
|
||||||
processing = False
|
processing = False
|
||||||
@ -103,33 +86,33 @@ class State(rx.State):
|
|||||||
|
|
||||||
self.processing, self.complete = True, False
|
self.processing, self.complete = True, False
|
||||||
yield
|
yield
|
||||||
response = openai_client.images.generate(
|
response = openai.Image.create(prompt=self.prompt, n=1, size="1024x1024")
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
self.image_url = response["data"][0]["url"]
|
||||||
)
|
|
||||||
self.image_url = response.data[0].url
|
|
||||||
self.processing, self.complete = False, True
|
self.processing, self.complete = False, True
|
||||||
|
|
||||||
|
|
||||||
def index():
|
def index():
|
||||||
return rx.center(
|
return rx.center(
|
||||||
rx.vstack(
|
rx.vstack(
|
||||||
rx.heading("DALL-E", font_size="1.5em"),
|
rx.heading("DALL·E"),
|
||||||
rx.input(
|
rx.input(placeholder="Enter a prompt", on_blur=State.set_prompt),
|
||||||
placeholder="Enter a prompt..",
|
|
||||||
on_blur=State.set_prompt,
|
|
||||||
width="25em",
|
|
||||||
),
|
|
||||||
rx.button(
|
rx.button(
|
||||||
"Generate Image",
|
"Generate Image",
|
||||||
on_click=State.get_image,
|
on_click=State.get_image,
|
||||||
width="25em",
|
is_loading=State.processing,
|
||||||
loading=State.processing
|
width="100%",
|
||||||
),
|
),
|
||||||
rx.cond(
|
rx.cond(
|
||||||
State.complete,
|
State.complete,
|
||||||
rx.image(src=State.image_url, width="20em"),
|
rx.image(
|
||||||
|
src=State.image_url,
|
||||||
|
height="25em",
|
||||||
|
width="25em",
|
||||||
|
)
|
||||||
),
|
),
|
||||||
align="center",
|
padding="2em",
|
||||||
|
shadow="lg",
|
||||||
|
border_radius="lg",
|
||||||
),
|
),
|
||||||
width="100%",
|
width="100%",
|
||||||
height="100vh",
|
height="100vh",
|
||||||
@ -137,20 +120,12 @@ def index():
|
|||||||
|
|
||||||
# Add state and page to the app.
|
# Add state and page to the app.
|
||||||
app = rx.App()
|
app = rx.App()
|
||||||
app.add_page(index, title="Reflex:DALL-E")
|
app.add_page(index, title="reflex:DALL·E")
|
||||||
|
app.compile()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Let's break this down.
|
## Let's break this down.
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="docs/images/dalle_colored_code_example.png" alt="Explaining the differences between backend and frontend parts of the DALL-E app." width="900" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
### **Reflex UI**
|
### **Reflex UI**
|
||||||
|
|
||||||
Let's start with the UI.
|
Let's start with the UI.
|
||||||
@ -167,7 +142,7 @@ This `index` function defines the frontend of the app.
|
|||||||
We use different components such as `center`, `vstack`, `input`, and `button` to build the frontend. Components can be nested within each other
|
We use different components such as `center`, `vstack`, `input`, and `button` to build the frontend. Components can be nested within each other
|
||||||
to create complex layouts. And you can use keyword args to style them with the full power of CSS.
|
to create complex layouts. And you can use keyword args to style them with the full power of CSS.
|
||||||
|
|
||||||
Reflex comes with [60+ built-in components](https://reflex.dev/docs/library) to help you get started. We are actively adding more components, and it's easy to [create your own components](https://reflex.dev/docs/wrapping-react/overview/).
|
Reflex comes with [60+ built-in components](https://reflex.dev/docs/library) to help you get started. We are actively adding more components, and it's easy to [create your own components](https://reflex.dev/docs/advanced-guide/wrapping-react).
|
||||||
|
|
||||||
### **State**
|
### **State**
|
||||||
|
|
||||||
@ -180,12 +155,11 @@ class State(rx.State):
|
|||||||
image_url = ""
|
image_url = ""
|
||||||
processing = False
|
processing = False
|
||||||
complete = False
|
complete = False
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The state defines all the variables (called vars) in an app that can change and the functions that change them.
|
The state defines all the variables (called vars) in an app that can change and the functions that change them.
|
||||||
|
|
||||||
Here the state is comprised of a `prompt` and `image_url`. There are also the booleans `processing` and `complete` to indicate when to disable the button (during image generation) and when to show the resulting image.
|
Here the state is comprised of a `prompt` and `image_url`. There are also the booleans `processing` and `complete` to indicate when to show the circular progress and image.
|
||||||
|
|
||||||
### **Event Handlers**
|
### **Event Handlers**
|
||||||
|
|
||||||
@ -197,10 +171,8 @@ def get_image(self):
|
|||||||
|
|
||||||
self.processing, self.complete = True, False
|
self.processing, self.complete = True, False
|
||||||
yield
|
yield
|
||||||
response = openai_client.images.generate(
|
response = openai.Image.create(prompt=self.prompt, n=1, size="1024x1024")
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
self.image_url = response["data"][0]["url"]
|
||||||
)
|
|
||||||
self.image_url = response.data[0].url
|
|
||||||
self.processing, self.complete = False, True
|
self.processing, self.complete = False, True
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -220,6 +192,7 @@ We add a page from the root of the app to the index component. We also add a tit
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
app.add_page(index, title="DALL-E")
|
app.add_page(index, title="DALL-E")
|
||||||
|
app.compile()
|
||||||
```
|
```
|
||||||
|
|
||||||
You can create a multi-page app by adding more pages.
|
You can create a multi-page app by adding more pages.
|
||||||
@ -228,16 +201,24 @@ You can create a multi-page app by adding more pages.
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Component Library](https://reflex.dev/docs/library) | 🖼️ [Templates](https://reflex.dev/templates/) | 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start)
|
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Component Library](https://reflex.dev/docs/library) | 🖼️ [Gallery](https://reflex.dev/docs/gallery) | 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## ✅ Status
|
## ✅ Status
|
||||||
|
|
||||||
Reflex launched in December 2022 with the name Pynecone.
|
Reflex launched in December 2022 with the name Pynecone.
|
||||||
|
|
||||||
As of February 2024, our hosting service is in alpha! During this time anyone can deploy their apps for free. See our [roadmap](https://github.com/reflex-dev/reflex/issues/2727) to see what's planned.
|
As of July 2023, we are in the **Public Beta** stage.
|
||||||
|
|
||||||
|
- :white_check_mark: **Public Alpha**: Anyone can install and use Reflex. There may be issues, but we are working to resolve them actively.
|
||||||
|
- :large_orange_diamond: **Public Beta**: Stable enough for non-enterprise use-cases.
|
||||||
|
- **Public Hosting Beta**: _Optionally_, deploy and host your apps on Reflex!
|
||||||
|
- **Public**: Reflex is production ready.
|
||||||
|
|
||||||
Reflex has new releases and features coming every week! Make sure to :star: star and :eyes: watch this repository to stay up to date.
|
Reflex has new releases and features coming every week! Make sure to :star: star and :eyes: watch this repository to stay up to date.
|
||||||
|
|
||||||
@ -247,15 +228,9 @@ We welcome contributions of any size! Below are some good ways to get started in
|
|||||||
|
|
||||||
- **Join Our Discord**: Our [Discord](https://discord.gg/T5WSbC2YtQ) is the best place to get help on your Reflex project and to discuss how you can contribute.
|
- **Join Our Discord**: Our [Discord](https://discord.gg/T5WSbC2YtQ) is the best place to get help on your Reflex project and to discuss how you can contribute.
|
||||||
- **GitHub Discussions**: A great way to talk about features you want added or things that are confusing/need clarification.
|
- **GitHub Discussions**: A great way to talk about features you want added or things that are confusing/need clarification.
|
||||||
- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues) are an excellent way to report bugs. Additionally, you can try and solve an existing issue and submit a PR.
|
- **GitHub Issues**: These are an excellent way to report bugs. Additionally, you can try and solve an existing issue and submit a PR.
|
||||||
|
|
||||||
We are actively looking for contributors, no matter your skill level or experience. To contribute check out [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
|
We are actively looking for contributors, no matter your skill level or experience.
|
||||||
|
|
||||||
|
|
||||||
## All Thanks To Our Contributors:
|
|
||||||
<a href="https://github.com/reflex-dev/reflex/graphs/contributors">
|
|
||||||
<img src="https://contrib.rocks/image?repo=reflex-dev/reflex" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
"""Reflex benchmarks."""
|
|
||||||
|
|
||||||
WINDOWS_SKIP_REASON = "Takes too much time as a result of npm"
|
|
@ -1,147 +0,0 @@
|
|||||||
"""Extracts the compile times from the JSON files in the specified directory and inserts them into the database."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from utils import send_data_to_posthog
|
|
||||||
|
|
||||||
|
|
||||||
def extract_stats_from_json(json_file: str) -> list[dict]:
|
|
||||||
"""Extracts the stats from the JSON data and returns them as a list of dictionaries.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
json_file: The JSON file to extract the stats data from.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list[dict]: The stats for each test.
|
|
||||||
"""
|
|
||||||
with Path(json_file).open() as file:
|
|
||||||
json_data = json.load(file)
|
|
||||||
|
|
||||||
# Load the JSON data if it is a string, otherwise assume it's already a dictionary
|
|
||||||
data = json.loads(json_data) if isinstance(json_data, str) else json_data
|
|
||||||
|
|
||||||
# Initialize an empty list to store the stats for each test
|
|
||||||
test_stats = []
|
|
||||||
|
|
||||||
# Iterate over each test in the 'benchmarks' list
|
|
||||||
for test in data.get("benchmarks", []):
|
|
||||||
group = test.get("group", None)
|
|
||||||
stats = test.get("stats", {})
|
|
||||||
full_name = test.get("fullname")
|
|
||||||
file_name = (
|
|
||||||
full_name.split("/")[-1].split("::")[0].strip(".py") if full_name else None
|
|
||||||
)
|
|
||||||
test_name = test.get("name", "Unknown Test")
|
|
||||||
|
|
||||||
test_stats.append(
|
|
||||||
{
|
|
||||||
"test_name": test_name,
|
|
||||||
"group": group,
|
|
||||||
"stats": stats,
|
|
||||||
"full_name": full_name,
|
|
||||||
"file_name": file_name,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return test_stats
|
|
||||||
|
|
||||||
|
|
||||||
def insert_benchmarking_data(
|
|
||||||
os_type_version: str,
|
|
||||||
python_version: str,
|
|
||||||
performance_data: list[dict],
|
|
||||||
commit_sha: str,
|
|
||||||
pr_title: str,
|
|
||||||
branch_name: str,
|
|
||||||
event_type: str,
|
|
||||||
pr_id: str,
|
|
||||||
):
|
|
||||||
"""Insert the benchmarking data into the database.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
os_type_version: The OS type and version to insert.
|
|
||||||
python_version: The Python version to insert.
|
|
||||||
performance_data: The performance data of reflex web to insert.
|
|
||||||
commit_sha: The commit SHA to insert.
|
|
||||||
pr_title: The PR title to insert.
|
|
||||||
branch_name: The name of the branch.
|
|
||||||
event_type: Type of github event(push, pull request, etc).
|
|
||||||
pr_id: Id of the PR.
|
|
||||||
"""
|
|
||||||
# Prepare the event data
|
|
||||||
properties = {
|
|
||||||
"os": os_type_version,
|
|
||||||
"python_version": python_version,
|
|
||||||
"distinct_id": commit_sha,
|
|
||||||
"pr_title": pr_title,
|
|
||||||
"branch_name": branch_name,
|
|
||||||
"event_type": event_type,
|
|
||||||
"performance": performance_data,
|
|
||||||
"pr_id": pr_id,
|
|
||||||
}
|
|
||||||
|
|
||||||
send_data_to_posthog("simple_app_benchmark", properties)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Runs the benchmarks and inserts the results."""
|
|
||||||
# Get the commit SHA and JSON directory from the command line arguments
|
|
||||||
parser = argparse.ArgumentParser(description="Run benchmarks and process results.")
|
|
||||||
parser.add_argument(
|
|
||||||
"--os", help="The OS type and version to insert into the database."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--python-version", help="The Python version to insert into the database."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--commit-sha", help="The commit SHA to insert into the database."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--benchmark-json",
|
|
||||||
help="The JSON file containing the benchmark results.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--pr-title",
|
|
||||||
help="The PR title to insert into the database.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--branch-name",
|
|
||||||
help="The current branch",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--event-type",
|
|
||||||
help="The github event type",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--pr-id",
|
|
||||||
help="ID of the PR.",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# Get the PR title from env or the args. For the PR merge or push event, there is no PR title, leaving it empty.
|
|
||||||
pr_title = args.pr_title or os.getenv("PR_TITLE", "")
|
|
||||||
|
|
||||||
# Get the results of pytest benchmarks
|
|
||||||
cleaned_benchmark_results = extract_stats_from_json(args.benchmark_json)
|
|
||||||
# Insert the data into the database
|
|
||||||
insert_benchmarking_data(
|
|
||||||
os_type_version=args.os,
|
|
||||||
python_version=args.python_version,
|
|
||||||
performance_data=cleaned_benchmark_results,
|
|
||||||
commit_sha=args.commit_sha,
|
|
||||||
pr_title=pr_title,
|
|
||||||
branch_name=args.branch_name,
|
|
||||||
event_type=args.event_type,
|
|
||||||
pr_id=args.pr_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,128 +0,0 @@
|
|||||||
"""Extract and upload benchmarking data to PostHog."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from utils import send_data_to_posthog
|
|
||||||
|
|
||||||
|
|
||||||
def extract_stats_from_json(json_file: str) -> dict:
|
|
||||||
"""Extracts the stats from the JSON data and returns them as dictionaries.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
json_file: The JSON file to extract the stats data from.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: The stats for each test.
|
|
||||||
"""
|
|
||||||
with Path(json_file).open() as file:
|
|
||||||
json_data = json.load(file)
|
|
||||||
|
|
||||||
# Load the JSON data if it is a string, otherwise assume it's already a dictionary
|
|
||||||
data = json.loads(json_data) if isinstance(json_data, str) else json_data
|
|
||||||
|
|
||||||
result = data.get("results", [{}])[0]
|
|
||||||
return {
|
|
||||||
k: v
|
|
||||||
for k, v in result.items()
|
|
||||||
if k in ("mean", "stddev", "median", "min", "max")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def insert_benchmarking_data(
|
|
||||||
os_type_version: str,
|
|
||||||
python_version: str,
|
|
||||||
performance_data: dict,
|
|
||||||
commit_sha: str,
|
|
||||||
pr_title: str,
|
|
||||||
branch_name: str,
|
|
||||||
pr_id: str,
|
|
||||||
app_name: str,
|
|
||||||
):
|
|
||||||
"""Insert the benchmarking data into the database.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
os_type_version: The OS type and version to insert.
|
|
||||||
python_version: The Python version to insert.
|
|
||||||
performance_data: The imports performance data to insert.
|
|
||||||
commit_sha: The commit SHA to insert.
|
|
||||||
pr_title: The PR title to insert.
|
|
||||||
branch_name: The name of the branch.
|
|
||||||
pr_id: Id of the PR.
|
|
||||||
app_name: The name of the app being measured.
|
|
||||||
"""
|
|
||||||
properties = {
|
|
||||||
"os": os_type_version,
|
|
||||||
"python_version": python_version,
|
|
||||||
"distinct_id": commit_sha,
|
|
||||||
"pr_title": pr_title,
|
|
||||||
"branch_name": branch_name,
|
|
||||||
"pr_id": pr_id,
|
|
||||||
"performance": performance_data,
|
|
||||||
"app_name": app_name,
|
|
||||||
}
|
|
||||||
|
|
||||||
send_data_to_posthog("import_benchmark", properties)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Runs the benchmarks and inserts the results."""
|
|
||||||
# Get the commit SHA and JSON directory from the command line arguments
|
|
||||||
parser = argparse.ArgumentParser(description="Run benchmarks and process results.")
|
|
||||||
parser.add_argument(
|
|
||||||
"--os", help="The OS type and version to insert into the database."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--python-version", help="The Python version to insert into the database."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--commit-sha", help="The commit SHA to insert into the database."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--benchmark-json",
|
|
||||||
help="The JSON file containing the benchmark results.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--pr-title",
|
|
||||||
help="The PR title to insert into the database.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--branch-name",
|
|
||||||
help="The current branch",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--app-name",
|
|
||||||
help="The name of the app measured.",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--pr-id",
|
|
||||||
help="ID of the PR.",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# Get the PR title from env or the args. For the PR merge or push event, there is no PR title, leaving it empty.
|
|
||||||
pr_title = args.pr_title or os.getenv("PR_TITLE", "")
|
|
||||||
|
|
||||||
cleaned_benchmark_results = extract_stats_from_json(args.benchmark_json)
|
|
||||||
# Insert the data into the database
|
|
||||||
insert_benchmarking_data(
|
|
||||||
os_type_version=args.os,
|
|
||||||
python_version=args.python_version,
|
|
||||||
performance_data=cleaned_benchmark_results,
|
|
||||||
commit_sha=args.commit_sha,
|
|
||||||
pr_title=pr_title,
|
|
||||||
branch_name=args.branch_name,
|
|
||||||
app_name=args.app_name,
|
|
||||||
pr_id=args.pr_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,75 +0,0 @@
|
|||||||
"""Extracts the Lighthouse scores from the JSON files in the specified directory and inserts them into the database."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from utils import send_data_to_posthog
|
|
||||||
|
|
||||||
|
|
||||||
def insert_benchmarking_data(
|
|
||||||
lighthouse_data: dict,
|
|
||||||
commit_sha: str,
|
|
||||||
):
|
|
||||||
"""Insert the benchmarking data into the database.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
lighthouse_data: The Lighthouse data to insert.
|
|
||||||
commit_sha: The commit SHA to insert.
|
|
||||||
"""
|
|
||||||
properties = {
|
|
||||||
"distinct_id": commit_sha,
|
|
||||||
"lighthouse_data": lighthouse_data,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Send the data to PostHog
|
|
||||||
send_data_to_posthog("lighthouse_benchmark", properties)
|
|
||||||
|
|
||||||
|
|
||||||
def get_lighthouse_scores(directory_path: str | Path) -> dict:
|
|
||||||
"""Extracts the Lighthouse scores from the JSON files in the specified directory.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
directory_path (str): The path to the directory containing the JSON files.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: The Lighthouse scores.
|
|
||||||
"""
|
|
||||||
scores = {}
|
|
||||||
directory_path = Path(directory_path)
|
|
||||||
try:
|
|
||||||
for filename in directory_path.iterdir():
|
|
||||||
if filename.suffix == ".json" and filename.stem != "manifest":
|
|
||||||
data = json.loads(filename.read_text())
|
|
||||||
# Extract scores and add them to the dictionary with the filename as key
|
|
||||||
scores[data["finalUrl"].replace("http://localhost:3000/", "/")] = {
|
|
||||||
"performance_score": data["categories"]["performance"]["score"],
|
|
||||||
"accessibility_score": data["categories"]["accessibility"]["score"],
|
|
||||||
"best_practices_score": data["categories"]["best-practices"][
|
|
||||||
"score"
|
|
||||||
],
|
|
||||||
"seo_score": data["categories"]["seo"]["score"],
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
return {"error": e}
|
|
||||||
|
|
||||||
return scores
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Runs the benchmarks and inserts the results into the database."""
|
|
||||||
# Get the commit SHA and JSON directory from the command line arguments
|
|
||||||
commit_sha = sys.argv[1]
|
|
||||||
json_dir = sys.argv[2]
|
|
||||||
|
|
||||||
# Get the Lighthouse scores
|
|
||||||
lighthouse_scores = get_lighthouse_scores(json_dir)
|
|
||||||
|
|
||||||
# Insert the data into the database
|
|
||||||
insert_benchmarking_data(lighthouse_scores, commit_sha)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,135 +0,0 @@
|
|||||||
"""Checks the size of a specific directory and uploads result to Posthog."""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from utils import get_directory_size, get_python_version, send_data_to_posthog
|
|
||||||
|
|
||||||
|
|
||||||
def get_package_size(venv_path: Path, os_name):
|
|
||||||
"""Get the size of a specified package.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
venv_path: The path to the venv.
|
|
||||||
os_name: Name of os.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The total size of the package in bytes.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: when venv does not exist or python version is None.
|
|
||||||
"""
|
|
||||||
python_version = get_python_version(venv_path, os_name)
|
|
||||||
print("Python version:", python_version)
|
|
||||||
if python_version is None:
|
|
||||||
raise ValueError("Error: Failed to determine Python version.")
|
|
||||||
|
|
||||||
is_windows = "windows" in os_name
|
|
||||||
|
|
||||||
package_dir: Path = (
|
|
||||||
venv_path / "lib" / f"python{python_version}" / "site-packages"
|
|
||||||
if not is_windows
|
|
||||||
else venv_path / "Lib" / "site-packages"
|
|
||||||
)
|
|
||||||
if not package_dir.exists():
|
|
||||||
raise ValueError(
|
|
||||||
"Error: Virtual environment does not exist or is not activated."
|
|
||||||
)
|
|
||||||
|
|
||||||
total_size = get_directory_size(package_dir)
|
|
||||||
return total_size
|
|
||||||
|
|
||||||
|
|
||||||
def insert_benchmarking_data(
|
|
||||||
os_type_version: str,
|
|
||||||
python_version: str,
|
|
||||||
commit_sha: str,
|
|
||||||
pr_title: str,
|
|
||||||
branch_name: str,
|
|
||||||
pr_id: str,
|
|
||||||
path: str,
|
|
||||||
):
|
|
||||||
"""Insert the benchmarking data into PostHog.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
os_type_version: The OS type and version to insert.
|
|
||||||
python_version: The Python version to insert.
|
|
||||||
commit_sha: The commit SHA to insert.
|
|
||||||
pr_title: The PR title to insert.
|
|
||||||
branch_name: The name of the branch.
|
|
||||||
pr_id: The id of the PR.
|
|
||||||
path: The path to the dir or file to check size.
|
|
||||||
"""
|
|
||||||
if "./dist" in path:
|
|
||||||
size = get_directory_size(Path(path))
|
|
||||||
else:
|
|
||||||
size = get_package_size(Path(path), os_type_version)
|
|
||||||
|
|
||||||
# Prepare the event data
|
|
||||||
properties = {
|
|
||||||
"path": path,
|
|
||||||
"os": os_type_version,
|
|
||||||
"python_version": python_version,
|
|
||||||
"distinct_id": commit_sha,
|
|
||||||
"pr_title": pr_title,
|
|
||||||
"branch_name": branch_name,
|
|
||||||
"pr_id": pr_id,
|
|
||||||
"size_mb": round(
|
|
||||||
size / (1024 * 1024), 3
|
|
||||||
), # save size in MB and round to 3 places
|
|
||||||
}
|
|
||||||
|
|
||||||
send_data_to_posthog("package_size", properties)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Runs the benchmarks and inserts the results."""
|
|
||||||
parser = argparse.ArgumentParser(description="Run benchmarks and process results.")
|
|
||||||
parser.add_argument(
|
|
||||||
"--os", help="The OS type and version to insert into the database."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--python-version", help="The Python version to insert into the database."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--commit-sha", help="The commit SHA to insert into the database."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--pr-title",
|
|
||||||
help="The PR title to insert into the database.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--branch-name",
|
|
||||||
help="The current branch",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--pr-id",
|
|
||||||
help="The pr id",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--path",
|
|
||||||
help="The path to the vnenv.",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# Get the PR title from env or the args. For the PR merge or push event, there is no PR title, leaving it empty.
|
|
||||||
pr_title = args.pr_title or os.getenv("PR_TITLE", "")
|
|
||||||
|
|
||||||
# Insert the data into the database
|
|
||||||
insert_benchmarking_data(
|
|
||||||
os_type_version=args.os,
|
|
||||||
python_version=args.python_version,
|
|
||||||
commit_sha=args.commit_sha,
|
|
||||||
pr_title=pr_title,
|
|
||||||
branch_name=args.branch_name,
|
|
||||||
pr_id=args.pr_id,
|
|
||||||
path=args.path,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,106 +0,0 @@
|
|||||||
"""Checks the size of a specific directory and uploads result to Posthog."""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from utils import get_directory_size, send_data_to_posthog
|
|
||||||
|
|
||||||
|
|
||||||
def insert_benchmarking_data(
|
|
||||||
os_type_version: str,
|
|
||||||
python_version: str,
|
|
||||||
app_name: str,
|
|
||||||
commit_sha: str,
|
|
||||||
pr_title: str,
|
|
||||||
branch_name: str,
|
|
||||||
pr_id: str,
|
|
||||||
path: str,
|
|
||||||
):
|
|
||||||
"""Insert the benchmarking data into PostHog.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
app_name: The name of the app being measured.
|
|
||||||
os_type_version: The OS type and version to insert.
|
|
||||||
python_version: The Python version to insert.
|
|
||||||
commit_sha: The commit SHA to insert.
|
|
||||||
pr_title: The PR title to insert.
|
|
||||||
branch_name: The name of the branch.
|
|
||||||
pr_id: The id of the PR.
|
|
||||||
path: The path to the dir or file to check size.
|
|
||||||
"""
|
|
||||||
size = get_directory_size(Path(path))
|
|
||||||
|
|
||||||
# Prepare the event data
|
|
||||||
properties = {
|
|
||||||
"app_name": app_name,
|
|
||||||
"os": os_type_version,
|
|
||||||
"python_version": python_version,
|
|
||||||
"distinct_id": commit_sha,
|
|
||||||
"pr_title": pr_title,
|
|
||||||
"branch_name": branch_name,
|
|
||||||
"pr_id": pr_id,
|
|
||||||
"size_mb": round(
|
|
||||||
size / (1024 * 1024), 3
|
|
||||||
), # save size in MB and round to 3 places
|
|
||||||
}
|
|
||||||
|
|
||||||
send_data_to_posthog("web-size", properties)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Runs the benchmarks and inserts the results."""
|
|
||||||
parser = argparse.ArgumentParser(description="Run benchmarks and process results.")
|
|
||||||
parser.add_argument(
|
|
||||||
"--os", help="The OS type and version to insert into the database."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--python-version", help="The Python version to insert into the database."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--commit-sha", help="The commit SHA to insert into the database."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--pr-title",
|
|
||||||
help="The PR title to insert into the database.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--branch-name",
|
|
||||||
help="The current branch",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--app-name",
|
|
||||||
help="The name of the app measured.",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--pr-id",
|
|
||||||
help="The pr id",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--path",
|
|
||||||
help="The current path to app to check.",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# Get the PR title from env or the args. For the PR merge or push event, there is no PR title, leaving it empty.
|
|
||||||
pr_title = args.pr_title or os.getenv("PR_TITLE", "")
|
|
||||||
|
|
||||||
# Insert the data into the database
|
|
||||||
insert_benchmarking_data(
|
|
||||||
app_name=args.app_name,
|
|
||||||
os_type_version=args.os,
|
|
||||||
python_version=args.python_version,
|
|
||||||
commit_sha=args.commit_sha,
|
|
||||||
pr_title=pr_title,
|
|
||||||
branch_name=args.branch_name,
|
|
||||||
pr_id=args.pr_id,
|
|
||||||
path=args.path,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,20 +0,0 @@
|
|||||||
"""Shared conftest for all benchmark tests."""
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from reflex.testing import AppHarness, AppHarnessProd
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(
|
|
||||||
scope="session", params=[AppHarness, AppHarnessProd], ids=["dev", "prod"]
|
|
||||||
)
|
|
||||||
def app_harness_env(request):
|
|
||||||
"""Parametrize the AppHarness class to use for the test, either dev or prod.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request: The pytest fixture request object.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The AppHarness class to use for the test.
|
|
||||||
"""
|
|
||||||
return request.param
|
|
@ -1,77 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Change directory to the first argument passed to the script
|
|
||||||
project_dir=$1
|
|
||||||
shift
|
|
||||||
pushd "$project_dir" || exit 1
|
|
||||||
echo "Changed directory to $project_dir"
|
|
||||||
|
|
||||||
|
|
||||||
# So we get stdout / stderr from Python ASAP. Without this, delays can be very long (e.g. on Windows, Github Actions)
|
|
||||||
export PYTHONUNBUFFERED=1
|
|
||||||
|
|
||||||
env_mode=$1
|
|
||||||
shift
|
|
||||||
check_ports=${1:-3000 8000}
|
|
||||||
shift
|
|
||||||
|
|
||||||
# Start the server in the background
|
|
||||||
export TELEMETRY_ENABLED=false
|
|
||||||
reflex run --env "$env_mode" "$@" & pid=$!
|
|
||||||
|
|
||||||
# Within the context of this bash, $pid_in_bash is what we need to pass to "kill" on exit
|
|
||||||
# This is true on all platforms.
|
|
||||||
pid_in_bash=$pid
|
|
||||||
trap "kill -INT $pid_in_bash ||:" EXIT
|
|
||||||
|
|
||||||
echo "Started server with PID $pid"
|
|
||||||
|
|
||||||
# Assume we run from the root of the repo
|
|
||||||
popd
|
|
||||||
|
|
||||||
# In Windows, our Python script below needs to work with the WINPID
|
|
||||||
if [ -f /proc/$pid/winpid ]; then
|
|
||||||
pid=$(cat /proc/$pid/winpid)
|
|
||||||
echo "Windows detected, passing winpid $pid to port waiter"
|
|
||||||
fi
|
|
||||||
|
|
||||||
python scripts/wait_for_listening_port.py $check_ports --timeout=600 --server-pid "$pid"
|
|
||||||
|
|
||||||
|
|
||||||
# Check if something is running on port 3000
|
|
||||||
if curl --output /dev/null --silent --head --fail "http://localhost:3000"; then
|
|
||||||
echo "URL exists: http://localhost:3000"
|
|
||||||
else
|
|
||||||
echo "URL does not exist: https://localhost:3000"
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p ./tests/benchmarks/.lighthouseci
|
|
||||||
|
|
||||||
# Create a lighthouserc.js file
|
|
||||||
cat << EOF > lighthouserc.js
|
|
||||||
module.exports = {
|
|
||||||
ci: {
|
|
||||||
collect: {
|
|
||||||
isSinglePageApplication: true,
|
|
||||||
numberOfRuns: 1,
|
|
||||||
url: ['http://localhost:3000', "http://localhost:3000/docs/getting-started/introduction/", "http://localhost:3000/blog/2023-08-02-seed-annoucement/"]
|
|
||||||
},
|
|
||||||
upload: {
|
|
||||||
target: 'filesystem',
|
|
||||||
"outputDir": "./integration/benchmarks/.lighthouseci"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Install and Run LHCI
|
|
||||||
npm install -g @lhci/cli
|
|
||||||
lhci autorun
|
|
||||||
|
|
||||||
# Check to see if the LHCI report is generated
|
|
||||||
if [ -d "./integration/benchmarks/.lighthouseci" ] && [ "$(ls -A ./integration/benchmarks/.lighthouseci)" ]; then
|
|
||||||
echo "LHCI report generated"
|
|
||||||
else
|
|
||||||
echo "LHCI report not generated"
|
|
||||||
exit 1 # Exits the script with a status of 1, which will cause the GitHub Action to stop
|
|
||||||
fi
|
|
@ -1,74 +0,0 @@
|
|||||||
"""Utility functions for the benchmarks."""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
from httpx import HTTPError
|
|
||||||
|
|
||||||
|
|
||||||
def get_python_version(venv_path: Path, os_name):
|
|
||||||
"""Get the python version of python in a virtual env.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
venv_path: Path to virtual environment.
|
|
||||||
os_name: Name of os.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The python version.
|
|
||||||
"""
|
|
||||||
python_executable = (
|
|
||||||
venv_path / "bin" / "python"
|
|
||||||
if "windows" not in os_name
|
|
||||||
else venv_path / "Scripts" / "python.exe"
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
output = subprocess.check_output(
|
|
||||||
[str(python_executable), "--version"], stderr=subprocess.STDOUT
|
|
||||||
)
|
|
||||||
python_version = output.decode("utf-8").strip().split()[1]
|
|
||||||
return ".".join(python_version.split(".")[:-1])
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_directory_size(directory: Path):
|
|
||||||
"""Get the size of a directory in bytes.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
directory: The directory to check.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The size of the dir in bytes.
|
|
||||||
"""
|
|
||||||
total_size = 0
|
|
||||||
for dirpath, _, filenames in os.walk(directory):
|
|
||||||
for f in filenames:
|
|
||||||
fp = Path(dirpath) / f
|
|
||||||
total_size += fp.stat().st_size
|
|
||||||
return total_size
|
|
||||||
|
|
||||||
|
|
||||||
def send_data_to_posthog(event, properties):
|
|
||||||
"""Send data to PostHog.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
event: The event to send.
|
|
||||||
properties: The properties to send.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
HTTPError: When there is an error sending data to PostHog.
|
|
||||||
"""
|
|
||||||
event_data = {
|
|
||||||
"api_key": "phc_JoMo0fOyi0GQAooY3UyO9k0hebGkMyFJrrCw1Gt5SGb",
|
|
||||||
"event": event,
|
|
||||||
"properties": properties,
|
|
||||||
}
|
|
||||||
|
|
||||||
with httpx.Client() as client:
|
|
||||||
response = client.post("https://app.posthog.com/capture/", json=event_data)
|
|
||||||
if response.status_code != 200:
|
|
||||||
raise HTTPError(
|
|
||||||
f"Error sending data to PostHog: {response.status_code} - {response.text}"
|
|
||||||
)
|
|
2
docker-example/.dockerignore
Normal file
2
docker-example/.dockerignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.web
|
||||||
|
__pycache__/*
|
18
docker-example/Caddyfile
Normal file
18
docker-example/Caddyfile
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{$DOMAIN}
|
||||||
|
|
||||||
|
encode gzip
|
||||||
|
|
||||||
|
@backend_routes path /event/* /upload /ping
|
||||||
|
handle @backend_routes {
|
||||||
|
reverse_proxy app:8000
|
||||||
|
}
|
||||||
|
|
||||||
|
route {
|
||||||
|
try_files {path} {path}.html
|
||||||
|
file_server {
|
||||||
|
root /srv
|
||||||
|
pass_thru
|
||||||
|
}
|
||||||
|
# proxy dynamic routes to nextjs server
|
||||||
|
reverse_proxy app:3000
|
||||||
|
}
|
38
docker-example/Dockerfile
Normal file
38
docker-example/Dockerfile
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Stage 1: init
|
||||||
|
FROM python:3.11 as init
|
||||||
|
|
||||||
|
# Pass `--build-arg API_URL=http://app.example.com:8000` during build
|
||||||
|
ARG API_URL
|
||||||
|
|
||||||
|
# Copy local context to `/app` inside container (see .dockerignore)
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Reflex will install bun, nvm, and node to `$HOME/.reflex` (/app/.reflex)
|
||||||
|
ENV HOME=/app
|
||||||
|
|
||||||
|
# Create virtualenv which will be copied into final container
|
||||||
|
ENV VIRTUAL_ENV=/app/.venv
|
||||||
|
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||||
|
RUN python3 -m venv $VIRTUAL_ENV
|
||||||
|
|
||||||
|
# Install app requirements and reflex inside virtualenv
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Deploy templates and prepare app
|
||||||
|
RUN reflex init
|
||||||
|
|
||||||
|
# Export static copy of frontend to /app/.web/_static (and pre-install frontend packages)
|
||||||
|
RUN reflex export --frontend-only --no-zip
|
||||||
|
|
||||||
|
|
||||||
|
# Stage 2: copy artifacts into slim image
|
||||||
|
FROM python:3.11-slim
|
||||||
|
ARG API_URL
|
||||||
|
WORKDIR /app
|
||||||
|
RUN adduser --disabled-password --home /app reflex
|
||||||
|
COPY --chown=reflex --from=init /app /app
|
||||||
|
USER reflex
|
||||||
|
ENV PATH="/app/.venv/bin:$PATH" API_URL=$API_URL
|
||||||
|
|
||||||
|
CMD reflex db migrate && reflex run --env prod
|
@ -1,30 +1,66 @@
|
|||||||
# Reflex Docker Examples
|
# Reflex Docker Container
|
||||||
|
|
||||||
This directory contains several examples of how to deploy Reflex apps using docker.
|
This example describes how to create and use a container image for Reflex with your own code.
|
||||||
|
|
||||||
In all cases, ensure that your `requirements.txt` file is up to date and
|
## Update Requirements
|
||||||
includes the `reflex` package.
|
|
||||||
|
|
||||||
## `simple-two-port`
|
The `requirements.txt` includes the reflex package which is needed to install
|
||||||
|
Reflex framework. If you use additional packages in your project you have to add
|
||||||
|
this in the `requirements.txt` first. Copy the `Dockerfile`, `.dockerignore` and
|
||||||
|
the `requirements.txt` file in your project folder.
|
||||||
|
|
||||||
The most basic production deployment exposes two HTTP ports and relies on an
|
## Build Reflex Container Image
|
||||||
existing load balancer to forward the traffic appropriately.
|
|
||||||
|
|
||||||
## `simple-one-port`
|
To build your container image run the following command:
|
||||||
|
|
||||||
This deployment exports the frontend statically and serves it via a single HTTP
|
```bash
|
||||||
port using Caddy. This is useful for platforms that only support a single port
|
docker build -t reflex-app:latest . --build-arg API_URL=http://app.example.com:8000
|
||||||
or where running a node server in the container is undesirable.
|
```
|
||||||
|
|
||||||
## `production-compose`
|
Ensure that `API_URL` is set to the publicly accessible hostname or IP where the app
|
||||||
|
will be hosted.
|
||||||
|
|
||||||
This deployment is intended for use with a standalone VPS that is only hosting a
|
## Start Container Service
|
||||||
single Reflex app. It provides the entire stack in a single `compose.yaml`
|
|
||||||
including a webserver, one or more backend instances, redis, and a postgres
|
|
||||||
database.
|
|
||||||
|
|
||||||
## `production-app-platform`
|
Finally, you can start your Reflex container service as follows:
|
||||||
|
|
||||||
This example deployment is intended for use with App hosting platforms, like
|
```bash
|
||||||
Azure, AWS, or Google Cloud Run. It is the backend of the deployment, which
|
docker run -p 3000:3000 -p 8000:8000 --name app reflex-app:latest
|
||||||
depends on a separately hosted redis instance and static frontend deployment.
|
```
|
||||||
|
|
||||||
|
It may take a few seconds for the service to become available.
|
||||||
|
|
||||||
|
# Production Service with Docker Compose and Caddy
|
||||||
|
|
||||||
|
An example production deployment uses automatic TLS with Caddy serving static files
|
||||||
|
for the frontend and proxying requests to both the frontend and backend.
|
||||||
|
|
||||||
|
Copy `compose.yaml`, `Caddy.Dockerfile` and `Caddyfile` to your project directory. The production
|
||||||
|
build leverages the same `Dockerfile` described above.
|
||||||
|
|
||||||
|
## Customize `Caddyfile`
|
||||||
|
|
||||||
|
If the app uses additional backend API routes, those should be added to the
|
||||||
|
`@backend_routes` path matcher to ensure they are forwarded to the backend.
|
||||||
|
|
||||||
|
## Build Reflex Production Service
|
||||||
|
|
||||||
|
During build, set `DOMAIN` environment variable to the domain where the app will
|
||||||
|
be hosted! (Do not include http or https, it will always use https)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DOMAIN=example.com docker compose build
|
||||||
|
```
|
||||||
|
|
||||||
|
This will build both the `app` service from the existing `Dockerfile` and the `webserver`
|
||||||
|
service via `Caddy.Dockerfile` that copies the `Caddyfile` and static frontend export
|
||||||
|
from the `app` service into the container.
|
||||||
|
|
||||||
|
## Run Reflex Production Service
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DOMAIN=example.com docker compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
The app should be available at the specified domain via HTTPS. Certificate
|
||||||
|
provisioning will occur automatically and may take a few minutes.
|
||||||
|
21
docker-example/compose.yaml
Normal file
21
docker-example/compose.yaml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# During build and run, set environment DOMAIN pointing
|
||||||
|
# to publicly accessible domain where app will be hosted
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: local/reflex-app
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
args:
|
||||||
|
API_URL: https://${DOMAIN:-localhost}
|
||||||
|
|
||||||
|
webserver:
|
||||||
|
environment:
|
||||||
|
DOMAIN: ${DOMAIN:-localhost}
|
||||||
|
ports:
|
||||||
|
- 443:443
|
||||||
|
- 80:80 # for acme-challenge via HTTP
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Caddy.Dockerfile
|
||||||
|
depends_on:
|
||||||
|
- app
|
@ -1,5 +0,0 @@
|
|||||||
.web
|
|
||||||
.git
|
|
||||||
__pycache__/*
|
|
||||||
Dockerfile
|
|
||||||
uploaded_files
|
|
@ -1,65 +0,0 @@
|
|||||||
# This docker file is intended to be used with container hosting services
|
|
||||||
#
|
|
||||||
# After deploying this image, get the URL pointing to the backend service
|
|
||||||
# and run API_URL=https://path-to-my-container.example.com reflex export frontend
|
|
||||||
# then copy the contents of `frontend.zip` to your static file server (github pages, s3, etc).
|
|
||||||
#
|
|
||||||
# Azure Static Web App example:
|
|
||||||
# npx @azure/static-web-apps-cli deploy --env production --app-location .web/_static
|
|
||||||
#
|
|
||||||
# For dynamic routes to function properly, ensure that 404s are redirected to /404 on the
|
|
||||||
# static file host (for github pages, this works out of the box; remember to create .nojekyll).
|
|
||||||
#
|
|
||||||
# For azure static web apps, add `staticwebapp.config.json` to to `.web/_static` with the following:
|
|
||||||
# {
|
|
||||||
# "responseOverrides": {
|
|
||||||
# "404": {
|
|
||||||
# "rewrite": "/404.html"
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
# Note: many container hosting platforms require amd64 images, so when building on an M1 Mac
|
|
||||||
# for example, pass `docker build --platform=linux/amd64 ...`
|
|
||||||
|
|
||||||
# Stage 1: init
|
|
||||||
FROM python:3.13 as init
|
|
||||||
|
|
||||||
ARG uv=/root/.local/bin/uv
|
|
||||||
|
|
||||||
# Install `uv` for faster package bootstrapping
|
|
||||||
ADD --chmod=755 https://astral.sh/uv/install.sh /install.sh
|
|
||||||
RUN /install.sh && rm /install.sh
|
|
||||||
|
|
||||||
# Copy local context to `/app` inside container (see .dockerignore)
|
|
||||||
WORKDIR /app
|
|
||||||
COPY . .
|
|
||||||
RUN mkdir -p /app/data /app/uploaded_files
|
|
||||||
|
|
||||||
# Create virtualenv which will be copied into final container
|
|
||||||
ENV VIRTUAL_ENV=/app/.venv
|
|
||||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
|
||||||
RUN $uv venv
|
|
||||||
|
|
||||||
# Install app requirements and reflex inside virtualenv
|
|
||||||
RUN $uv pip install -r requirements.txt
|
|
||||||
|
|
||||||
# Deploy templates and prepare app
|
|
||||||
RUN reflex init
|
|
||||||
|
|
||||||
# Stage 2: copy artifacts into slim image
|
|
||||||
FROM python:3.13-slim
|
|
||||||
WORKDIR /app
|
|
||||||
RUN adduser --disabled-password --home /app reflex
|
|
||||||
COPY --chown=reflex --from=init /app /app
|
|
||||||
# Install libpq-dev for psycopg (skip if not using postgres).
|
|
||||||
RUN apt-get update -y && apt-get install -y libpq-dev && rm -rf /var/lib/apt/lists/*
|
|
||||||
USER reflex
|
|
||||||
ENV PATH="/app/.venv/bin:$PATH" PYTHONUNBUFFERED=1
|
|
||||||
|
|
||||||
# Needed until Reflex properly passes SIGTERM on backend.
|
|
||||||
STOPSIGNAL SIGKILL
|
|
||||||
|
|
||||||
# Always apply migrations before starting the backend.
|
|
||||||
CMD [ -d alembic ] && reflex db migrate; \
|
|
||||||
exec reflex run --env prod --backend-only --backend-port ${PORT:-8000}
|
|
@ -1,113 +0,0 @@
|
|||||||
# production-app-platform
|
|
||||||
|
|
||||||
This example deployment is intended for use with App hosting platforms, like
|
|
||||||
Azure, AWS, or Google Cloud Run.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
The production deployment consists of a few pieces:
|
|
||||||
* Backend container - built by `Dockerfile` Runs the Reflex backend
|
|
||||||
service on port 8000 and is scalable to multiple instances.
|
|
||||||
* Redis container - A single instance the standard `redis` docker image should
|
|
||||||
share private networking with the backend
|
|
||||||
* Static frontend - HTML/CSS/JS files that are hosted via a CDN or static file
|
|
||||||
server. This is not included in the docker image.
|
|
||||||
|
|
||||||
## Deployment
|
|
||||||
|
|
||||||
These general steps do not cover the specifics of each platform, but all platforms should
|
|
||||||
support the concepts described here.
|
|
||||||
|
|
||||||
### Vnet
|
|
||||||
|
|
||||||
All containers in the deployment should be hooked up to the same virtual private
|
|
||||||
network so they can access the redis service and optionally the database server.
|
|
||||||
The vnet should not be exposed to the internet, use an ingress rule to terminate
|
|
||||||
TLS at the load balancer and forward the traffic to a backend service replica.
|
|
||||||
|
|
||||||
### Redis
|
|
||||||
|
|
||||||
Deploy a `redis` instance on the vnet.
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
|
|
||||||
The backend is built by the `Dockerfile` in this directory. When deploying the
|
|
||||||
backend, be sure to set REDIS_URL=redis://internal-redis-hostname to connect to
|
|
||||||
the redis service.
|
|
||||||
|
|
||||||
### Ingress
|
|
||||||
|
|
||||||
Configure the load balancer for the app to forward traffic to port 8000 on the
|
|
||||||
backend service replicas. Most platforms will generate an ingress hostname
|
|
||||||
automatically. Make sure when you access the ingress endpoint on `/ping` that it
|
|
||||||
returns "pong", indicating that the backend is up an available.
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
|
|
||||||
The frontend should be hosted on a static file server or CDN.
|
|
||||||
|
|
||||||
**Important**: when exporting the frontend, set the API_URL environment variable
|
|
||||||
to the ingress hostname of the backend service.
|
|
||||||
|
|
||||||
If you will host the frontend from a path other than the root, set the
|
|
||||||
`FRONTEND_PATH` environment variable appropriately when exporting the frontend.
|
|
||||||
|
|
||||||
Most static hosts will automatically use the `/404.html` file to handle 404
|
|
||||||
errors. _This is essential for dynamic routes to work correctly._ Ensure that
|
|
||||||
missing routes return the `/404.html` content to the user if this is not the
|
|
||||||
default behavior.
|
|
||||||
|
|
||||||
_For Github Pages_: ensure the file `.nojekyll` is present in the root of the repo
|
|
||||||
to avoid special processing of underscore-prefix directories, like `_next`.
|
|
||||||
|
|
||||||
## Platform Notes
|
|
||||||
|
|
||||||
The following sections are currently a work in progress and may be incomplete.
|
|
||||||
|
|
||||||
### Azure
|
|
||||||
|
|
||||||
In the Azure load balancer, per-message deflate is not supported. Add the following
|
|
||||||
to your `rxconfig.py` to workaround this issue.
|
|
||||||
|
|
||||||
```python
|
|
||||||
import uvicorn.workers
|
|
||||||
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
|
|
||||||
class NoWSPerMessageDeflate(uvicorn.workers.UvicornH11Worker):
|
|
||||||
CONFIG_KWARGS = {
|
|
||||||
**uvicorn.workers.UvicornH11Worker.CONFIG_KWARGS,
|
|
||||||
"ws_per_message_deflate": False,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
config = rx.Config(
|
|
||||||
app_name="my_app",
|
|
||||||
gunicorn_worker_class="rxconfig.NoWSPerMessageDeflate",
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Persistent Storage
|
|
||||||
|
|
||||||
If you need to use a database or upload files, you cannot save them to the
|
|
||||||
container volume. Use Azure Files and mount it into the container at /app/uploaded_files.
|
|
||||||
|
|
||||||
#### Resource Types
|
|
||||||
|
|
||||||
* Create a new vnet with 10.0.0.0/16
|
|
||||||
* Create a new subnet for redis, database, and containers
|
|
||||||
* Deploy redis as a Container Instances
|
|
||||||
* Deploy database server as "Azure Database for PostgreSQL"
|
|
||||||
* Create a new database for the app
|
|
||||||
* Set db-url as a secret containing the db user/password connection string
|
|
||||||
* Deploy Storage account for uploaded files
|
|
||||||
* Enable access from the vnet and container subnet
|
|
||||||
* Create a new file share
|
|
||||||
* In the environment, create a new files share (get the storage key)
|
|
||||||
* Deploy the backend as a Container App
|
|
||||||
* Create a custom Container App Environment linked up to the same vnet as the redis container.
|
|
||||||
* Set REDIS_URL and DB_URL environment variables
|
|
||||||
* Add the volume from the environment
|
|
||||||
* Add the volume mount to the container
|
|
||||||
* Deploy the frontend as a Static Web App
|
|
@ -1,8 +0,0 @@
|
|||||||
.web
|
|
||||||
.git
|
|
||||||
__pycache__/*
|
|
||||||
Dockerfile
|
|
||||||
Caddy.Dockerfile
|
|
||||||
compose.yaml
|
|
||||||
compose.*.yaml
|
|
||||||
uploaded_files
|
|
@ -1,14 +0,0 @@
|
|||||||
{$DOMAIN}
|
|
||||||
|
|
||||||
encode gzip
|
|
||||||
|
|
||||||
@backend_routes path /_event/* /ping /_upload /_upload/*
|
|
||||||
handle @backend_routes {
|
|
||||||
reverse_proxy app:8000
|
|
||||||
}
|
|
||||||
|
|
||||||
root * /srv
|
|
||||||
route {
|
|
||||||
try_files {path} {path}/ /404.html
|
|
||||||
file_server
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
# This docker file is intended to be used with docker compose to deploy a production
|
|
||||||
# instance of a Reflex app.
|
|
||||||
|
|
||||||
# Stage 1: init
|
|
||||||
FROM python:3.13 as init
|
|
||||||
|
|
||||||
ARG uv=/root/.local/bin/uv
|
|
||||||
|
|
||||||
# Install `uv` for faster package bootstrapping
|
|
||||||
ADD --chmod=755 https://astral.sh/uv/install.sh /install.sh
|
|
||||||
RUN /install.sh && rm /install.sh
|
|
||||||
|
|
||||||
# Copy local context to `/app` inside container (see .dockerignore)
|
|
||||||
WORKDIR /app
|
|
||||||
COPY . .
|
|
||||||
RUN mkdir -p /app/data /app/uploaded_files
|
|
||||||
|
|
||||||
# Create virtualenv which will be copied into final container
|
|
||||||
ENV VIRTUAL_ENV=/app/.venv
|
|
||||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
|
||||||
RUN $uv venv
|
|
||||||
|
|
||||||
# Install app requirements and reflex inside virtualenv
|
|
||||||
RUN $uv pip install -r requirements.txt
|
|
||||||
|
|
||||||
# Deploy templates and prepare app
|
|
||||||
RUN reflex init
|
|
||||||
|
|
||||||
# Export static copy of frontend to /app/.web/_static
|
|
||||||
RUN reflex export --frontend-only --no-zip
|
|
||||||
|
|
||||||
# Copy static files out of /app to save space in backend image
|
|
||||||
RUN mv .web/_static /tmp/_static
|
|
||||||
RUN rm -rf .web && mkdir .web
|
|
||||||
RUN mv /tmp/_static .web/_static
|
|
||||||
|
|
||||||
# Stage 2: copy artifacts into slim image
|
|
||||||
FROM python:3.13-slim
|
|
||||||
WORKDIR /app
|
|
||||||
RUN adduser --disabled-password --home /app reflex
|
|
||||||
COPY --chown=reflex --from=init /app /app
|
|
||||||
# Install libpq-dev for psycopg (skip if not using postgres).
|
|
||||||
RUN apt-get update -y && apt-get install -y libpq-dev && rm -rf /var/lib/apt/lists/*
|
|
||||||
USER reflex
|
|
||||||
ENV PATH="/app/.venv/bin:$PATH" PYTHONUNBUFFERED=1
|
|
||||||
|
|
||||||
# Needed until Reflex properly passes SIGTERM on backend.
|
|
||||||
STOPSIGNAL SIGKILL
|
|
||||||
|
|
||||||
# Always apply migrations before starting the backend.
|
|
||||||
CMD [ -d alembic ] && reflex db migrate; \
|
|
||||||
exec reflex run --env prod --backend-only
|
|
@ -1,75 +0,0 @@
|
|||||||
# production-compose
|
|
||||||
|
|
||||||
This example production deployment uses automatic TLS with Caddy serving static
|
|
||||||
files for the frontend and proxying requests to both the frontend and backend.
|
|
||||||
It is intended for use with a standalone VPS that is only hosting a single
|
|
||||||
Reflex app.
|
|
||||||
|
|
||||||
The production app container (`Dockerfile`), builds and exports the frontend
|
|
||||||
statically (to be served by Caddy). The resulting image only runs the backend
|
|
||||||
service.
|
|
||||||
|
|
||||||
The `webserver` service, based on `Caddy.Dockerfile`, copies the static frontend
|
|
||||||
and `Caddyfile` into the container to configure the reverse proxy routes that will
|
|
||||||
forward requests to the backend service. Caddy will automatically provision TLS
|
|
||||||
for localhost or the domain specified in the environment variable `DOMAIN`.
|
|
||||||
|
|
||||||
This type of deployment should use less memory and be more performant since
|
|
||||||
nodejs is not required at runtime.
|
|
||||||
|
|
||||||
## Customize `Caddyfile` (optional)
|
|
||||||
|
|
||||||
If the app uses additional backend API routes, those should be added to the
|
|
||||||
`@backend_routes` path matcher to ensure they are forwarded to the backend.
|
|
||||||
|
|
||||||
## Build Reflex Production Service
|
|
||||||
|
|
||||||
During build, set `DOMAIN` environment variable to the domain where the app will
|
|
||||||
be hosted! (Do not include http or https, it will always use https).
|
|
||||||
|
|
||||||
**If `DOMAIN` is not provided, the service will default to `localhost`.**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
DOMAIN=example.com docker compose build
|
|
||||||
```
|
|
||||||
|
|
||||||
This will build both the `app` service from the `prod.Dockerfile` and the `webserver`
|
|
||||||
service via `Caddy.Dockerfile`.
|
|
||||||
|
|
||||||
## Run Reflex Production Service
|
|
||||||
|
|
||||||
```bash
|
|
||||||
DOMAIN=example.com docker compose up
|
|
||||||
```
|
|
||||||
|
|
||||||
The app should be available at the specified domain via HTTPS. Certificate
|
|
||||||
provisioning will occur automatically and may take a few minutes.
|
|
||||||
|
|
||||||
### Data Persistence
|
|
||||||
|
|
||||||
Named docker volumes are used to persist the app database (`db-data`),
|
|
||||||
uploaded_files (`upload-data`), and caddy TLS keys and certificates
|
|
||||||
(`caddy-data`).
|
|
||||||
|
|
||||||
## More Robust Deployment
|
|
||||||
|
|
||||||
For a more robust deployment, consider bringing the service up with
|
|
||||||
`compose.prod.yaml` which includes postgres database and redis cache, allowing
|
|
||||||
the backend to run with multiple workers and service more requests.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
DOMAIN=example.com docker compose -f compose.yaml -f compose.prod.yaml up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Postgres uses its own named docker volume for data persistence.
|
|
||||||
|
|
||||||
## Admin Tools
|
|
||||||
|
|
||||||
When needed, the services in `compose.tools.yaml` can be brought up, providing
|
|
||||||
graphical database administration (Adminer on http://localhost:8080) and a
|
|
||||||
redis cache browser (redis-commander on http://localhost:8081). It is not recommended
|
|
||||||
to deploy these services if they are not in active use.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
DOMAIN=example.com docker compose -f compose.yaml -f compose.prod.yaml -f compose.tools.yaml up -d
|
|
||||||
```
|
|
@ -1,25 +0,0 @@
|
|||||||
# Use this override file to run the app in prod mode with postgres and redis
|
|
||||||
# docker compose -f compose.yaml -f compose.prod.yaml up -d
|
|
||||||
services:
|
|
||||||
db:
|
|
||||||
image: postgres
|
|
||||||
restart: always
|
|
||||||
environment:
|
|
||||||
POSTGRES_PASSWORD: secret
|
|
||||||
volumes:
|
|
||||||
- postgres-data:/var/lib/postgresql/data
|
|
||||||
|
|
||||||
redis:
|
|
||||||
image: redis
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
app:
|
|
||||||
environment:
|
|
||||||
DB_URL: postgresql+psycopg://postgres:secret@db/postgres
|
|
||||||
REDIS_URL: redis://redis:6379
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
- redis
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
postgres-data:
|
|
@ -1,18 +0,0 @@
|
|||||||
# Use this override file with `compose.prod.yaml` to run admin tools
|
|
||||||
# for production services.
|
|
||||||
# docker compose -f compose.yaml -f compose.prod.yaml -f compose.tools.yaml up -d
|
|
||||||
services:
|
|
||||||
adminer:
|
|
||||||
image: adminer
|
|
||||||
ports:
|
|
||||||
- 8080:8080
|
|
||||||
|
|
||||||
redis-commander:
|
|
||||||
image: ghcr.io/joeferner/redis-commander:latest
|
|
||||||
environment:
|
|
||||||
- REDIS_HOSTS=local:redis:6379
|
|
||||||
ports:
|
|
||||||
- "8081:8081"
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
redis-ui-settings:
|
|
@ -1,41 +0,0 @@
|
|||||||
# Base compose file production deployment of reflex app with Caddy webserver
|
|
||||||
# providing TLS termination and reverse proxying.
|
|
||||||
#
|
|
||||||
# See `compose.prod.yaml` for more robust and performant deployment option.
|
|
||||||
#
|
|
||||||
# During build and run, set environment DOMAIN pointing
|
|
||||||
# to publicly accessible domain where app will be hosted
|
|
||||||
services:
|
|
||||||
app:
|
|
||||||
image: local/reflex-app
|
|
||||||
environment:
|
|
||||||
DB_URL: sqlite:///data/reflex.db
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
volumes:
|
|
||||||
- db-data:/app/data
|
|
||||||
- upload-data:/app/uploaded_files
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
webserver:
|
|
||||||
environment:
|
|
||||||
DOMAIN: ${DOMAIN:-localhost}
|
|
||||||
ports:
|
|
||||||
- 443:443
|
|
||||||
- 80:80 # For acme-challenge via HTTP.
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Caddy.Dockerfile
|
|
||||||
volumes:
|
|
||||||
- caddy-data:/root/.caddy
|
|
||||||
restart: always
|
|
||||||
depends_on:
|
|
||||||
- app
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
# SQLite data
|
|
||||||
db-data:
|
|
||||||
# Uploaded files
|
|
||||||
upload-data:
|
|
||||||
# TLS keys and certificates
|
|
||||||
caddy-data:
|
|
@ -1,3 +0,0 @@
|
|||||||
.web
|
|
||||||
!.web/bun.lockb
|
|
||||||
!.web/package.json
|
|
@ -1,14 +0,0 @@
|
|||||||
:{$PORT}
|
|
||||||
|
|
||||||
encode gzip
|
|
||||||
|
|
||||||
@backend_routes path /_event/* /ping /_upload /_upload/*
|
|
||||||
handle @backend_routes {
|
|
||||||
reverse_proxy localhost:8000
|
|
||||||
}
|
|
||||||
|
|
||||||
root * /srv
|
|
||||||
route {
|
|
||||||
try_files {path} {path}/ /404.html
|
|
||||||
file_server
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
# This Dockerfile is used to deploy a single-container Reflex app instance
|
|
||||||
# to services like Render, Railway, Heroku, GCP, and others.
|
|
||||||
|
|
||||||
# If the service expects a different port, provide it here (f.e Render expects port 10000)
|
|
||||||
ARG PORT=8080
|
|
||||||
# Only set for local/direct access. When TLS is used, the API_URL is assumed to be the same as the frontend.
|
|
||||||
ARG API_URL
|
|
||||||
|
|
||||||
# It uses a reverse proxy to serve the frontend statically and proxy to backend
|
|
||||||
# from a single exposed port, expecting TLS termination to be handled at the
|
|
||||||
# edge by the given platform.
|
|
||||||
FROM python:3.13 as builder
|
|
||||||
|
|
||||||
RUN mkdir -p /app/.web
|
|
||||||
RUN python -m venv /app/.venv
|
|
||||||
ENV PATH="/app/.venv/bin:$PATH"
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Install python app requirements and reflex in the container
|
|
||||||
COPY requirements.txt .
|
|
||||||
RUN pip install -r requirements.txt
|
|
||||||
|
|
||||||
# Install reflex helper utilities like bun/fnm/node
|
|
||||||
COPY rxconfig.py ./
|
|
||||||
RUN reflex init
|
|
||||||
|
|
||||||
# Install pre-cached frontend dependencies (if exist)
|
|
||||||
COPY *.web/bun.lockb *.web/package.json .web/
|
|
||||||
RUN if [ -f .web/bun.lockb ]; then cd .web && ~/.local/share/reflex/bun/bin/bun install --frozen-lockfile; fi
|
|
||||||
|
|
||||||
# Copy local context to `/app` inside container (see .dockerignore)
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
ARG PORT API_URL
|
|
||||||
# Download other npm dependencies and compile frontend
|
|
||||||
RUN API_URL=${API_URL:-http://localhost:$PORT} reflex export --loglevel debug --frontend-only --no-zip && mv .web/_static/* /srv/ && rm -rf .web
|
|
||||||
|
|
||||||
|
|
||||||
# Final image with only necessary files
|
|
||||||
FROM python:3.13-slim
|
|
||||||
|
|
||||||
# Install Caddy and redis server inside image
|
|
||||||
RUN apt-get update -y && apt-get install -y caddy redis-server && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
ARG PORT API_URL
|
|
||||||
ENV PATH="/app/.venv/bin:$PATH" PORT=$PORT API_URL=${API_URL:-http://localhost:$PORT} REDIS_URL=redis://localhost PYTHONUNBUFFERED=1
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
COPY --from=builder /app /app
|
|
||||||
COPY --from=builder /srv /srv
|
|
||||||
|
|
||||||
# Needed until Reflex properly passes SIGTERM on backend.
|
|
||||||
STOPSIGNAL SIGKILL
|
|
||||||
|
|
||||||
EXPOSE $PORT
|
|
||||||
|
|
||||||
# Apply migrations before starting the backend.
|
|
||||||
CMD [ -d alembic ] && reflex db migrate; \
|
|
||||||
caddy start && \
|
|
||||||
redis-server --daemonize yes && \
|
|
||||||
exec reflex run --env prod --backend-only
|
|
@ -1,37 +0,0 @@
|
|||||||
# production-one-port
|
|
||||||
|
|
||||||
This docker deployment runs Reflex in prod mode, exposing a single HTTP port:
|
|
||||||
* `8080` (`$PORT`) - Caddy server hosting the frontend statically and proxying requests to the backend.
|
|
||||||
|
|
||||||
The deployment also runs a local Redis server to store state for each user.
|
|
||||||
|
|
||||||
Conceptually it is similar to the `simple-one-port` example except it:
|
|
||||||
* has layer caching for python, reflex, and node dependencies
|
|
||||||
* uses multi-stage build to reduce the size of the final image
|
|
||||||
|
|
||||||
Using this method may be preferable for deploying in memory constrained
|
|
||||||
environments, because it serves a static frontend export, rather than running
|
|
||||||
the NextJS server via node.
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
```console
|
|
||||||
docker build -t reflex-production-one-port .
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run
|
|
||||||
|
|
||||||
```console
|
|
||||||
docker run -p 8080:8080 reflex-production-one-port
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that this container has _no persistence_ and will lose all data when
|
|
||||||
stopped. You can use bind mounts or named volumes to persist the database and
|
|
||||||
uploaded_files directories as needed.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
This container should be used with an existing load balancer or reverse proxy to
|
|
||||||
terminate TLS.
|
|
||||||
|
|
||||||
It is also useful for deploying to simple app platforms, such as Render or Heroku.
|
|
1
docker-example/requirements.txt
Normal file
1
docker-example/requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
reflex
|
@ -1,5 +0,0 @@
|
|||||||
.web
|
|
||||||
.git
|
|
||||||
__pycache__/*
|
|
||||||
Dockerfile
|
|
||||||
uploaded_files
|
|
@ -1,14 +0,0 @@
|
|||||||
:{$PORT}
|
|
||||||
|
|
||||||
encode gzip
|
|
||||||
|
|
||||||
@backend_routes path /_event/* /ping /_upload /_upload/*
|
|
||||||
handle @backend_routes {
|
|
||||||
reverse_proxy localhost:8000
|
|
||||||
}
|
|
||||||
|
|
||||||
root * /srv
|
|
||||||
route {
|
|
||||||
try_files {path} {path}/ /404.html
|
|
||||||
file_server
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
# This Dockerfile is used to deploy a single-container Reflex app instance
|
|
||||||
# to services like Render, Railway, Heroku, GCP, and others.
|
|
||||||
|
|
||||||
# It uses a reverse proxy to serve the frontend statically and proxy to backend
|
|
||||||
# from a single exposed port, expecting TLS termination to be handled at the
|
|
||||||
# edge by the given platform.
|
|
||||||
FROM python:3.13
|
|
||||||
|
|
||||||
# If the service expects a different port, provide it here (f.e Render expects port 10000)
|
|
||||||
ARG PORT=8080
|
|
||||||
# Only set for local/direct access. When TLS is used, the API_URL is assumed to be the same as the frontend.
|
|
||||||
ARG API_URL
|
|
||||||
ENV PORT=$PORT API_URL=${API_URL:-http://localhost:$PORT} REDIS_URL=redis://localhost PYTHONUNBUFFERED=1
|
|
||||||
|
|
||||||
# Install Caddy and redis server inside image
|
|
||||||
RUN apt-get update -y && apt-get install -y caddy redis-server && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy local context to `/app` inside container (see .dockerignore)
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Install app requirements and reflex in the container
|
|
||||||
RUN pip install -r requirements.txt
|
|
||||||
|
|
||||||
# Deploy templates and prepare app
|
|
||||||
RUN reflex init
|
|
||||||
|
|
||||||
# Download all npm dependencies and compile frontend
|
|
||||||
RUN reflex export --frontend-only --no-zip && mv .web/_static/* /srv/ && rm -rf .web
|
|
||||||
|
|
||||||
# Needed until Reflex properly passes SIGTERM on backend.
|
|
||||||
STOPSIGNAL SIGKILL
|
|
||||||
|
|
||||||
EXPOSE $PORT
|
|
||||||
|
|
||||||
# Apply migrations before starting the backend.
|
|
||||||
CMD [ -d alembic ] && reflex db migrate; \
|
|
||||||
caddy start && \
|
|
||||||
redis-server --daemonize yes && \
|
|
||||||
exec reflex run --env prod --backend-only
|
|
@ -1,36 +0,0 @@
|
|||||||
# simple-one-port
|
|
||||||
|
|
||||||
This docker deployment runs Reflex in prod mode, exposing a single HTTP port:
|
|
||||||
* `8080` (`$PORT`) - Caddy server hosting the frontend statically and proxying requests to the backend.
|
|
||||||
|
|
||||||
The deployment also runs a local Redis server to store state for each user.
|
|
||||||
|
|
||||||
Using this method may be preferable for deploying in memory constrained
|
|
||||||
environments, because it serves a static frontend export, rather than running
|
|
||||||
the NextJS server via node.
|
|
||||||
|
|
||||||
For platforms which only terminate TLS to a single port, this container can be
|
|
||||||
deployed instead of the `simple-two-port` example.
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
```console
|
|
||||||
docker build -t reflex-simple-one-port .
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run
|
|
||||||
|
|
||||||
```console
|
|
||||||
docker run -p 8080:8080 reflex-simple-one-port
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that this container has _no persistence_ and will lose all data when
|
|
||||||
stopped. You can use bind mounts or named volumes to persist the database and
|
|
||||||
uploaded_files directories as needed.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
This container should be used with an existing load balancer or reverse proxy to
|
|
||||||
terminate TLS.
|
|
||||||
|
|
||||||
It is also useful for deploying to simple app platforms, such as Render or Heroku.
|
|
@ -1,5 +0,0 @@
|
|||||||
.web
|
|
||||||
.git
|
|
||||||
__pycache__/*
|
|
||||||
Dockerfile
|
|
||||||
uploaded_files
|
|
@ -1,26 +0,0 @@
|
|||||||
# This Dockerfile is used to deploy a simple single-container Reflex app instance.
|
|
||||||
FROM python:3.13
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y redis-server && rm -rf /var/lib/apt/lists/*
|
|
||||||
ENV REDIS_URL=redis://localhost PYTHONUNBUFFERED=1
|
|
||||||
|
|
||||||
# Copy local context to `/app` inside container (see .dockerignore)
|
|
||||||
WORKDIR /app
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Install app requirements and reflex in the container
|
|
||||||
RUN pip install -r requirements.txt
|
|
||||||
|
|
||||||
# Deploy templates and prepare app
|
|
||||||
RUN reflex init
|
|
||||||
|
|
||||||
# Download all npm dependencies and compile frontend
|
|
||||||
RUN reflex export --frontend-only --no-zip
|
|
||||||
|
|
||||||
# Needed until Reflex properly passes SIGTERM on backend.
|
|
||||||
STOPSIGNAL SIGKILL
|
|
||||||
|
|
||||||
# Always apply migrations before starting the backend.
|
|
||||||
CMD [ -d alembic ] && reflex db migrate; \
|
|
||||||
redis-server --daemonize yes && \
|
|
||||||
exec reflex run --env prod
|
|
@ -1,44 +0,0 @@
|
|||||||
# simple-two-port
|
|
||||||
|
|
||||||
This docker deployment runs Reflex in prod mode, exposing two HTTP ports:
|
|
||||||
* `3000` - node NextJS server using optimized production build
|
|
||||||
* `8000` - python gunicorn server hosting the Reflex backend
|
|
||||||
|
|
||||||
The deployment also runs a local Redis server to store state for each user.
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
```console
|
|
||||||
docker build -t reflex-simple-two-port .
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run
|
|
||||||
|
|
||||||
```console
|
|
||||||
docker run -p 3000:3000 -p 8000:8000 reflex-simple-two-port
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that this container has _no persistence_ and will lose all data when
|
|
||||||
stopped. You can use bind mounts or named volumes to persist the database and
|
|
||||||
uploaded_files directories as needed.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
This container should be used with an existing load balancer or reverse proxy to
|
|
||||||
route traffic to the appropriate port inside the container.
|
|
||||||
|
|
||||||
For example, the following Caddyfile can be used to terminate TLS and forward
|
|
||||||
traffic to the frontend and backend from outside the container.
|
|
||||||
|
|
||||||
```
|
|
||||||
my-domain.com
|
|
||||||
|
|
||||||
encode gzip
|
|
||||||
|
|
||||||
@backend_routes path /_event/* /ping /_upload /_upload/*
|
|
||||||
handle @backend_routes {
|
|
||||||
reverse_proxy localhost:8000
|
|
||||||
}
|
|
||||||
|
|
||||||
reverse_proxy localhost:3000
|
|
||||||
```
|
|
@ -1,28 +0,0 @@
|
|||||||
# Debugging
|
|
||||||
|
|
||||||
It is possible to run Reflex apps in dev mode under a debugger.
|
|
||||||
|
|
||||||
1. Run Reflex as a module: `python -m reflex run --env dev`
|
|
||||||
2. Set current working directory to the dir containing `rxconfig.py`
|
|
||||||
|
|
||||||
## VSCode
|
|
||||||
|
|
||||||
The following launch configuration can be used to interactively debug a Reflex
|
|
||||||
app with breakpoints.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Reflex App",
|
|
||||||
"type": "python",
|
|
||||||
"request": "launch",
|
|
||||||
"module": "reflex",
|
|
||||||
"args": "run --env dev",
|
|
||||||
"justMyCode": true,
|
|
||||||
"cwd": "${fileDirname}/.."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,261 +0,0 @@
|
|||||||
```diff
|
|
||||||
+ Suchst du nach Pynecone? Dann bist du hier in der richtigen Repository. Pynecone wurde in Reflex umbenannt. +
|
|
||||||
```
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_dark.svg#gh-light-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_light.svg#gh-dark-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
### **✨ Performante, anpassbare Web-Apps in purem Python. Bereitstellung in Sekunden. ✨**
|
|
||||||
[](https://badge.fury.io/py/reflex)
|
|
||||||

|
|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Reflex
|
|
||||||
|
|
||||||
Reflex ist eine Bibliothek, mit der man Full-Stack-Web-Applikationen in purem Python erstellen kann.
|
|
||||||
|
|
||||||
Wesentliche Merkmale:
|
|
||||||
* **Pures Python** - Schreibe dein Front- und Backend in Python, es gibt also keinen Grund, JavaScript zu lernen.
|
|
||||||
* **Volle Flexibilität** - Reflex ist einfach zu handhaben, kann aber auch für komplexe Anwendungen skaliert werden.
|
|
||||||
* **Sofortige Bereitstellung** - Nach dem Erstellen kannst du deine App mit einem [einzigen Befehl](https://reflex.dev/docs/hosting/deploy-quick-start/) bereitstellen oder auf deinem eigenen Server hosten.
|
|
||||||
|
|
||||||
Auf unserer [Architektur-Seite](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) erfahren Sie, wie Reflex unter der Haube funktioniert.
|
|
||||||
|
|
||||||
## ⚙️ Installation
|
|
||||||
|
|
||||||
Öffne ein Terminal und führe den folgenden Befehl aus (benötigt Python 3.10+):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install reflex
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🥳 Erstelle deine erste App
|
|
||||||
|
|
||||||
Die Installation von `reflex` installiert auch das `reflex`-Kommandozeilen-Tool.
|
|
||||||
|
|
||||||
Teste, ob die Installation erfolgreich war, indem du ein neues Projekt erstellst. (Ersetze `my_app_name` durch deinen Projektnamen):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir my_app_name
|
|
||||||
cd my_app_name
|
|
||||||
reflex init
|
|
||||||
```
|
|
||||||
|
|
||||||
Dieser Befehl initialisiert eine Vorlage in deinem neuen Verzeichnis.
|
|
||||||
|
|
||||||
Du kannst diese App im Entwicklungsmodus ausführen:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
reflex run
|
|
||||||
```
|
|
||||||
|
|
||||||
Du solltest deine App unter http://localhost:3000 laufen sehen.
|
|
||||||
|
|
||||||
Nun kannst du den Quellcode in `my_app_name/my_app_name.py` ändern. Reflex hat schnelle Aktualisierungen, sodass du deine Änderungen sofort siehst, wenn du deinen Code speicherst.
|
|
||||||
|
|
||||||
|
|
||||||
## 🫧 Beispiel-App
|
|
||||||
|
|
||||||
Lass uns ein Beispiel durchgehen: die Erstellung einer Benutzeroberfläche für die Bildgenerierung mit [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node). Zur Vereinfachung rufen wir einfach die [OpenAI-API](https://platform.openai.com/docs/api-reference/authentication) auf, aber du könntest dies auch durch ein lokal ausgeführtes ML-Modell ersetzen.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/dalle.gif" alt="Eine Benutzeroberfläche für DALL·E, die im Prozess der Bildgenerierung gezeigt wird." width="550" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Hier ist der komplette Code, um dies zu erstellen. Das alles wird in einer Python-Datei gemacht!
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
import reflex as rx
|
|
||||||
import openai
|
|
||||||
|
|
||||||
openai_client = openai.OpenAI()
|
|
||||||
|
|
||||||
|
|
||||||
class State(rx.State):
|
|
||||||
"""Der Zustand der App."""
|
|
||||||
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
|
|
||||||
def get_image(self):
|
|
||||||
"""Hole das Bild aus dem Prompt."""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Empty")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai_client.images.generate(
|
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
|
||||||
)
|
|
||||||
self.image_url = response.data[0].url
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
|
|
||||||
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
rx.vstack(
|
|
||||||
rx.heading("DALL-E", font_size="1.5em"),
|
|
||||||
rx.input(
|
|
||||||
placeholder="Enter a prompt..",
|
|
||||||
on_blur=State.set_prompt,
|
|
||||||
width="25em",
|
|
||||||
),
|
|
||||||
rx.button(
|
|
||||||
"Generate Image",
|
|
||||||
on_click=State.get_image,
|
|
||||||
width="25em",
|
|
||||||
loading=State.processing
|
|
||||||
),
|
|
||||||
rx.cond(
|
|
||||||
State.complete,
|
|
||||||
rx.image(src=State.image_url, width="20em"),
|
|
||||||
),
|
|
||||||
align="center",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
height="100vh",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Füge Zustand und Seite zur App hinzu.
|
|
||||||
app = rx.App()
|
|
||||||
app.add_page(index, title="Reflex:DALL-E")
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Schauen wir uns das mal genauer an.
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="docs/images/dalle_colored_code_example.png" alt="Erläuterung der Unterschiede zwischen Backend- und Frontend-Teilen der DALL-E-App." width="900" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
### **Reflex-UI**
|
|
||||||
|
|
||||||
Fangen wir mit der Benutzeroberfläche an.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
...
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Diese `index`-Funktion definiert das Frontend der App.
|
|
||||||
|
|
||||||
Wir verwenden verschiedene Komponenten wie `center`, `vstack`, `input` und `button`, um das Frontend zu erstellen. Komponenten können ineinander verschachtelt werden, um komplexe Layouts zu erstellen. Und du kannst Schlüsselwortargumente verwenden, um sie mit der vollen Kraft von CSS zu stylen.
|
|
||||||
|
|
||||||
Reflex wird mit [über 60 eingebauten Komponenten](https://reflex.dev/docs/library) geliefert, die dir den Einstieg erleichtern. Wir fügen aktiv weitere Komponenten hinzu, und es ist einfach, [eigene Komponenten zu erstellen](https://reflex.dev/docs/wrapping-react/overview/).
|
|
||||||
|
|
||||||
### **State**
|
|
||||||
|
|
||||||
Reflex stellt deine Benutzeroberfläche als Funktion deines Zustands dar.
|
|
||||||
|
|
||||||
```python
|
|
||||||
class State(rx.State):
|
|
||||||
"""Der Zustand der App."""
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Der Zustand definiert alle Variablen (genannt Vars) in einer App, die sich ändern können, und die Funktionen, die sie ändern.
|
|
||||||
|
|
||||||
Hier besteht der Zustand aus einem `prompt` und einer `image_url`. Es gibt auch die Booleans `processing` und `complete`, um anzuzeigen, wann der Button deaktiviert werden soll (während der Bildgenerierung) und wann das resultierende Bild angezeigt werden soll.
|
|
||||||
|
|
||||||
### **Event-Handler**
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_image(self):
|
|
||||||
"""Hole das Bild aus dem Prompt."""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Empty")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai_client.images.generate(
|
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
|
||||||
)
|
|
||||||
self.image_url = response.data[0].url
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
```
|
|
||||||
|
|
||||||
Innerhalb des Zustands definieren wir Funktionen, die als Event-Handler bezeichnet werden und die Zustand-Variablen ändern. Event-Handler sind die Art und Weise, wie wir den Zustand in Reflex ändern können. Sie können als Reaktion auf Benutzeraktionen aufgerufen werden, z.B. beim Klicken auf eine Schaltfläche oder bei der Eingabe in ein Textfeld. Diese Aktionen werden als Ereignisse bezeichnet.
|
|
||||||
|
|
||||||
Unsere DALL-E.-App hat einen Event-Handler, `get_image`, der dieses Bild von der OpenAI-API abruft. Die Verwendung von `yield` in der Mitte eines Event-Handlers führt zu einer Aktualisierung der Benutzeroberfläche. Andernfalls wird die Benutzeroberfläche am Ende des Ereignishandlers aktualisiert.
|
|
||||||
|
|
||||||
### **Routing**
|
|
||||||
|
|
||||||
Schließlich definieren wir unsere App.
|
|
||||||
|
|
||||||
```python
|
|
||||||
app = rx.App()
|
|
||||||
```
|
|
||||||
|
|
||||||
Wir fügen der Indexkomponente eine Seite aus dem Stammverzeichnis der Anwendung hinzu. Wir fügen auch einen Titel hinzu, der in der Seitenvorschau/Browser-Registerkarte angezeigt wird.
|
|
||||||
|
|
||||||
```python
|
|
||||||
app.add_page(index, title="DALL-E")
|
|
||||||
```
|
|
||||||
|
|
||||||
Du kannst eine mehrseitige App erstellen, indem du weitere Seiten hinzufügst.
|
|
||||||
|
|
||||||
## 📑 Ressourcen
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Komponentenbibliothek](https://reflex.dev/docs/library) | 🖼️ [Galerie](https://reflex.dev/docs/gallery) | 🛸 [Bereitstellung](https://reflex.dev/docs/hosting/deploy-quick-start)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
## ✅ Status
|
|
||||||
|
|
||||||
Reflex wurde im Dezember 2022 unter dem Namen Pynecone gestartet.
|
|
||||||
|
|
||||||
Ab Februar 2024 befindet sich unser Hosting-Service in der Alpha-Phase! In dieser Zeit kann jeder seine Apps kostenlos bereitstellen. Siehe unsere [Roadmap](https://github.com/reflex-dev/reflex/issues/2727), um zu sehen, was geplant ist.
|
|
||||||
|
|
||||||
Reflex hat wöchentliche Veröffentlichungen und neue Features! Stelle sicher, dass du dieses Repository mit einem :star: Stern markierst und :eyes: beobachtest, um auf dem Laufenden zu bleiben.
|
|
||||||
|
|
||||||
## Beitragende
|
|
||||||
|
|
||||||
Wir begrüßen Beiträge jeder Größe! Hier sind einige gute Möglichkeiten, um in der Reflex-Community zu starten.
|
|
||||||
|
|
||||||
- **Tritt unserem Discord bei**: Unser [Discord](https://discord.gg/T5WSbC2YtQ) ist der beste Ort, um Hilfe für dein Reflex-Projekt zu bekommen und zu besprechen, wie du beitragen kannst.
|
|
||||||
- **GitHub-Diskussionen**: Eine großartige Möglichkeit, über Funktionen zu sprechen, die du hinzugefügt haben möchtest oder Dinge, die verwirrend sind/geklärt werden müssen.
|
|
||||||
- **GitHub-Issues**: [Issues](https://github.com/reflex-dev/reflex/issues) sind eine ausgezeichnete Möglichkeit, Bugs zu melden. Außerdem kannst du versuchen, ein bestehendes Problem zu lösen und eine PR einzureichen.
|
|
||||||
|
|
||||||
Wir suchen aktiv nach Mitwirkenden, unabhängig von deinem Erfahrungslevel oder deiner Erfahrung. Um beizutragen, sieh dir [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) an.
|
|
||||||
|
|
||||||
|
|
||||||
## Vielen Dank an unsere Mitwirkenden:
|
|
||||||
<a href="https://github.com/reflex-dev/reflex/graphs/contributors">
|
|
||||||
<img src="https://contrib.rocks/image?repo=reflex-dev/reflex" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
## Lizenz
|
|
||||||
|
|
||||||
Reflex ist Open-Source und lizenziert unter der [Apache License 2.0](LICENSE).
|
|
@ -1,246 +0,0 @@
|
|||||||
```diff
|
|
||||||
+ ¿Buscando Pynecone? Estás en el repositorio correcto. Pynecone ha sido renombrado a Reflex. +
|
|
||||||
```
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_dark.svg#gh-light-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_light.svg#gh-dark-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
### **✨ Aplicaciones web personalizables y eficaces en Python puro. Despliega tu aplicación en segundos. ✨**
|
|
||||||
[](https://badge.fury.io/py/reflex)
|
|
||||||

|
|
||||||

|
|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Reflex
|
|
||||||
|
|
||||||
Reflex es una biblioteca para construir aplicaciones web full-stack en Python puro.
|
|
||||||
|
|
||||||
Características clave:
|
|
||||||
* **Python puro** - Escribe el frontend y backend de tu aplicación en Python, sin necesidad de aprender JavaScript.
|
|
||||||
* **Flexibilidad total** - Reflex es fácil para empezar, pero también puede escalar a aplicaciones complejas.
|
|
||||||
* **Despliegue instantáneo** - Después de construir, despliega tu aplicación con un [solo comando](https://reflex.dev/docs/hosting/deploy-quick-start/) u hospédala en tu propio servidor.
|
|
||||||
|
|
||||||
Consulta nuestra [página de arquitectura](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) para aprender cómo funciona Reflex en detalle.
|
|
||||||
|
|
||||||
## ⚙️ Instalación
|
|
||||||
|
|
||||||
Abra un terminal y ejecute (Requiere Python 3.10+):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install reflex
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🥳 Crea tu primera aplicación
|
|
||||||
|
|
||||||
Al instalar `reflex` también se instala la herramienta de línea de comandos `reflex`.
|
|
||||||
|
|
||||||
Compruebe que la instalación se ha realizado correctamente creando un nuevo proyecto. (Sustituye `my_app_name` por el nombre de tu proyecto):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir my_app_name
|
|
||||||
cd my_app_name
|
|
||||||
reflex init
|
|
||||||
```
|
|
||||||
|
|
||||||
Este comando inicializa una plantilla en tu nuevo directorio.
|
|
||||||
|
|
||||||
Puedes iniciar esta aplicación en modo de desarrollo:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
reflex run
|
|
||||||
```
|
|
||||||
|
|
||||||
Debería ver su aplicación ejecutándose en http://localhost:3000.
|
|
||||||
|
|
||||||
Ahora puede modificar el código fuente en `my_app_name/my_app_name.py`. Reflex se actualiza rápidamente para que pueda ver los cambios al instante cuando guarde el código.
|
|
||||||
|
|
||||||
|
|
||||||
## 🫧 Ejemplo de una Aplicación
|
|
||||||
|
|
||||||
Veamos un ejemplo: crearemos una UI de generación de imágenes en torno a [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node). Para simplificar, solo llamamos a la [API de OpenAI](https://platform.openai.com/docs/api-reference/authentication), pero podrías reemplazar esto con un modelo ML ejecutado localmente.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/dalle.gif" alt="Un envoltorio frontend para DALL·E, mostrado en el proceso de generar una imagen." width="550" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Aquí está el código completo para crear esto. ¡Todo esto se hace en un archivo de Python!
|
|
||||||
|
|
||||||
```python
|
|
||||||
import reflex as rx
|
|
||||||
import openai
|
|
||||||
|
|
||||||
openai_client = openai.OpenAI()
|
|
||||||
|
|
||||||
class State(rx.State):
|
|
||||||
"""El estado de la aplicación"""
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
|
|
||||||
def get_image(self):
|
|
||||||
"""Obtiene la imagen desde la consulta."""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Empty")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai_client.images.generate(
|
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
|
||||||
)
|
|
||||||
self.image_url = response.data[0].url
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
|
|
||||||
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
rx.vstack(
|
|
||||||
rx.heading("DALL-E", font_size="1.5em"),
|
|
||||||
rx.input(
|
|
||||||
placeholder="Enter a prompt..",
|
|
||||||
on_blur=State.set_prompt,
|
|
||||||
width="25em",
|
|
||||||
),
|
|
||||||
rx.button(
|
|
||||||
"Generate Image",
|
|
||||||
on_click=State.get_image,
|
|
||||||
width="25em",
|
|
||||||
loading=State.processing
|
|
||||||
),
|
|
||||||
rx.cond(
|
|
||||||
State.complete,
|
|
||||||
rx.image(src=State.image_url, width="20em"),
|
|
||||||
),
|
|
||||||
align="center",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
height="100vh",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Agrega el estado y la pagina a la aplicación
|
|
||||||
app = rx.App()
|
|
||||||
app.add_page(index, title="Reflex:DALL-E")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Vamos a analizarlo.
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://github.com/reflex-dev/reflex/blob/main/docs/images/dalle_colored_code_example.png?raw=true" alt="Explicando las diferencias entre las partes del backend y frontend de la aplicación DALL-E." width="900" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### **Reflex UI**
|
|
||||||
|
|
||||||
Empezemos por la interfaz de usuario (UI).
|
|
||||||
|
|
||||||
```python
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
...
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Esta función `index` define el frontend de la aplicación.
|
|
||||||
|
|
||||||
Utilizamos diferentes componentes como `center`, `vstack`, `input`, y `button` para construir el frontend. Los componentes pueden anidarse unos dentro de otros para crear diseños complejos. Además, puedes usar argumentos de tipo keyword para darles estilo con toda la potencia de CSS.
|
|
||||||
|
|
||||||
Reflex viene con [mas de 60 componentes incorporados](https://reflex.dev/docs/library) para ayudarle a empezar. Estamos añadiendo activamente más componentes y es fácil [crear sus propios componentes](https://reflex.dev/docs/wrapping-react/overview/).
|
|
||||||
|
|
||||||
### **Estado**
|
|
||||||
|
|
||||||
Reflex representa su UI como una función de su estado (State).
|
|
||||||
|
|
||||||
```python
|
|
||||||
class State(rx.State):
|
|
||||||
"""El estado de la aplicación"""
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
```
|
|
||||||
|
|
||||||
El estado (State) define todas las variables (llamadas vars) de una aplicación que pueden cambiar y las funciones que las modifican.
|
|
||||||
|
|
||||||
Aquí el estado se compone de `prompt` e `image_url`. También están los booleanos `processing` y `complete` para indicar cuando se deshabilite el botón (durante la generación de la imagen) y cuando se muestre la imagen resultante.
|
|
||||||
|
|
||||||
### **Manejadores de Evento**
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_image(self):
|
|
||||||
"""Obtiene la imagen desde la consulta."""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Empty")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai_client.images.generate(
|
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
|
||||||
)
|
|
||||||
self.image_url = response.data[0].url
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
```
|
|
||||||
|
|
||||||
Dentro del estado, definimos funciones llamadas manejadores de eventos que cambian las variables de estado. Los Manejadores de Evento son la manera que podemos modificar el estado en Reflex. Pueden ser activados en respuesta a las acciones del usuario, como hacer clic en un botón o escribir en un cuadro de texto. Estas acciones se llaman eventos.
|
|
||||||
|
|
||||||
Nuestra aplicación DALL·E tiene un manipulador de eventos, `get_image` que recibe esta imagen del OpenAI API. El uso de `yield` en medio de un manipulador de eventos hará que la UI se actualice. De lo contrario, la interfaz se actualizará al final del manejador de eventos.
|
|
||||||
|
|
||||||
### **Enrutamiento**
|
|
||||||
|
|
||||||
Por último, definimos nuestra app.
|
|
||||||
|
|
||||||
```python
|
|
||||||
app = rx.App()
|
|
||||||
```
|
|
||||||
|
|
||||||
Añadimos una página desde la raíz (root) de la aplicación al componente de índice (index). También agregamos un título que se mostrará en la vista previa de la página/pestaña del navegador.
|
|
||||||
|
|
||||||
```python
|
|
||||||
app.add_page(index, title="DALL-E")
|
|
||||||
```
|
|
||||||
|
|
||||||
Puedes crear una aplicación multipágina añadiendo más páginas.
|
|
||||||
|
|
||||||
## 📑 Recursos
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Librería de componentes](https://reflex.dev/docs/library) | 🖼️ [Galería](https://reflex.dev/docs/gallery) | 🛸 [Despliegue](https://reflex.dev/docs/hosting/deploy-quick-start)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
## ✅ Estado
|
|
||||||
|
|
||||||
Reflex se lanzó en diciembre de 2022 con el nombre de Pynecone.
|
|
||||||
|
|
||||||
¡Desde febrero de 2024, nuestro servicio de alojamiento está en fase alfa! Durante este tiempo, cualquiera puede implementar sus aplicaciones de forma gratuita. Consulta nuestra [hoja de ruta](https://github.com/reflex-dev/reflex/issues/2727) para ver qué está planeado.
|
|
||||||
|
|
||||||
¡Reflex tiene nuevas versiones y características cada semana! Asegúrate de :star: marcar como favorito y :eyes: seguir este repositorio para mantenerte actualizado.
|
|
||||||
|
|
||||||
## Contribuciones
|
|
||||||
|
|
||||||
¡Aceptamos contribuciones de cualquier tamaño! A continuación encontrará algunas buenas formas de iniciarse en la comunidad Reflex.
|
|
||||||
|
|
||||||
- **Únete a nuestro Discord**: Nuestro [Discord](https://discord.gg/T5WSbC2YtQ) es el mejor lugar para obtener ayuda en su proyecto Reflex y discutir cómo puedes contribuir.
|
|
||||||
- **Discusiones de GitHub**: Una excelente manera de hablar sobre las características que deseas agregar o las cosas que te resultan confusas o necesitan aclaración.
|
|
||||||
- **GitHub Issues**: Las incidencias son una forma excelente de informar de errores. Además, puedes intentar resolver un problema existente y enviar un PR.
|
|
||||||
|
|
||||||
Buscamos colaboradores, sin importar su nivel o experiencia. Para contribuir consulta [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
|
|
||||||
|
|
||||||
## Licencia
|
|
||||||
|
|
||||||
Reflex es de código abierto y está licenciado bajo la [Apache License 2.0](LICENSE).
|
|
Binary file not shown.
Before Width: | Height: | Size: 370 KiB |
@ -1,252 +0,0 @@
|
|||||||
```diff
|
|
||||||
Pynecone की तलाश हैं? आप सही रेपो में हैं। Pynecone का नाम Reflex में बदल दिया गया है। + +
|
|
||||||
```
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_dark.svg#gh-light-mode-only" alt="Reflex लोगो" width="300px">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_light.svg#gh-dark-mode-only" alt="Reflex लोगो" width="300px">
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
### **✨ प्रदर्शनकारी, अनुकूलित वेब ऐप्स, शुद्ध Python में। सेकंडों में तैनात करें। ✨**
|
|
||||||
|
|
||||||
[](https://badge.fury.io/py/reflex)
|
|
||||||

|
|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
|
||||||
|
|
||||||
# Reflex
|
|
||||||
|
|
||||||
Reflex शुद्ध पायथन में पूर्ण-स्टैक वेब ऐप्स बनाने के लिए एक लाइब्रेरी है।
|
|
||||||
|
|
||||||
मुख्य विशेषताएँ:
|
|
||||||
|
|
||||||
- **शुद्ध पायथन** - अपने ऐप के फ्रंटएंड और बैकएंड को पायथन में लिखें, जावास्क्रिप्ट सीखने की जरूरत नहीं है।
|
|
||||||
- **पूर्ण लचीलापन** - Reflex के साथ शुरुआत करना आसान है, लेकिन यह जटिल ऐप्स के लिए भी स्केल कर सकता है।
|
|
||||||
- **तुरंत तैनाती** - बिल्डिंग के बाद, अपने ऐप को [एकल कमांड](https://reflex.dev/docs/hosting/deploy-quick-start/) के साथ तैनात करें या इसे अपने सर्वर पर होस्ट करें।
|
|
||||||
|
|
||||||
Reflex के अंदर के कामकाज को जानने के लिए हमारे [आर्किटेक्चर पेज](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) को देखें।
|
|
||||||
|
|
||||||
## ⚙️ इंस्टॉलेशन (Installation)
|
|
||||||
|
|
||||||
एक टर्मिनल खोलें और चलाएं (Python 3.10+ की आवश्यकता है):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install reflex
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🥳 अपना पहला ऐप बनाएं (Create your first App)
|
|
||||||
|
|
||||||
reflex को इंस्टॉल करने से ही reflex कमांड लाइन टूल भी इंस्टॉल हो जाता है।
|
|
||||||
|
|
||||||
सुनिश्चित करें कि इंस्टॉलेशन सफल थी, एक नया प्रोजेक्ट बनाकर इसे टेस्ट करें। ('my_app_name' की जगह अपने प्रोजेक्ट का नाम रखें):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir my_app_name
|
|
||||||
cd my_app_name
|
|
||||||
reflex init
|
|
||||||
```
|
|
||||||
|
|
||||||
यह कमांड आपकी नयी डायरेक्टरी में एक टेम्पलेट ऐप को प्रारंभ करता है।
|
|
||||||
|
|
||||||
आप इस ऐप को development मोड में चला सकते हैं:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
reflex run
|
|
||||||
```
|
|
||||||
|
|
||||||
आपको http://localhost:3000 पर अपने ऐप को चलते हुए देखना चाहिए।
|
|
||||||
|
|
||||||
अब आप my_app_name/my_app_name.py में source कोड को संशोधित कर सकते हैं। Reflex में तेज रिफ्रेश की सुविधा है, इसलिए जब आप अपनी कोड को सहेजते हैं, तो आप अपने बदलावों को तुरंत देख सकते हैं।
|
|
||||||
|
|
||||||
## 🫧 उदाहरण ऐप (Example App)
|
|
||||||
|
|
||||||
एक उदाहरण पर चलते हैं: DALL·E से एक इमेज उत्पन्न करने के लिए UI। सरलता के लिए, हम सिर्फ OpenAI API को बुलाते हैं, लेकिन आप इसे ML मॉडल से बदल सकते हैं locally।
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/dalle.gif" alt="DALL·E के लिए एक फ्रंटएंड रैपर, छवि उत्पन्न करने की प्रक्रिया में दिखाया गया।" width="550" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
यहाँ पर इसका पूरा कोड है जिससे यह बनाया जा सकता है। यह सब एक ही Python फ़ाइल में किया गया है!
|
|
||||||
|
|
||||||
```python
|
|
||||||
import reflex as rx
|
|
||||||
import openai
|
|
||||||
|
|
||||||
openai_client = openai.OpenAI()
|
|
||||||
|
|
||||||
|
|
||||||
class State(rx.State):
|
|
||||||
"""The app state."""
|
|
||||||
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
|
|
||||||
def get_image(self):
|
|
||||||
"""Get the image from the prompt."""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Empty")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai_client.images.generate(
|
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
|
||||||
)
|
|
||||||
self.image_url = response.data[0].url
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
|
|
||||||
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
rx.vstack(
|
|
||||||
rx.heading("DALL-E", font_size="1.5em"),
|
|
||||||
rx.input(
|
|
||||||
placeholder="Enter a prompt..",
|
|
||||||
on_blur=State.set_prompt,
|
|
||||||
width="25em",
|
|
||||||
),
|
|
||||||
rx.button(
|
|
||||||
"Generate Image",
|
|
||||||
on_click=State.get_image,
|
|
||||||
width="25em",
|
|
||||||
loading=State.processing
|
|
||||||
),
|
|
||||||
rx.cond(
|
|
||||||
State.complete,
|
|
||||||
rx.image(src=State.image_url, width="20em"),
|
|
||||||
),
|
|
||||||
align="center",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
height="100vh",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add state and page to the app.
|
|
||||||
app = rx.App()
|
|
||||||
app.add_page(index, title="Reflex:DALL-E")
|
|
||||||
```
|
|
||||||
|
|
||||||
## इसे समझते हैं।
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://github.com/reflex-dev/reflex/blob/main/docs/images/dalle_colored_code_example.png?raw=true" alt="DALL-E ऐप के बैकएंड और फ्रंटएंड भागों के बीच के अंतर की व्याख्या करता है।" width="900" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### **Reflex UI**
|
|
||||||
|
|
||||||
हम UI के साथ शुरू करेंगे।
|
|
||||||
|
|
||||||
```python
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
...
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
यह `index` फ़ंक्शन एप्लिकेशन की फ़्रंटएंड को परिभाषित करता है।
|
|
||||||
|
|
||||||
हम फ़्रंटएंड बनाने के लिए `center`, `vstack`, `input`, और `button` जैसे विभिन्न components का उपयोग करते हैं। Components को एक-दूसरे के भीतर डाल सकते हैं विस्तारित लेआउट बनाने के लिए। और आप CSS की पूरी ताक़त के साथ इन्हें स्टाइल करने के लिए कीवर्ड आर्ग्यूमेंट (keyword args) का उपयोग कर सकते हैं।
|
|
||||||
|
|
||||||
रिफ़्लेक्स के पास [60+ built-in components](https://reflex.dev/docs/library) हैं जो आपको शुरुआती मदद के लिए हैं। हम बहुत से components जोड़ रहे हैं, और अपने खुद के components बनाना भी आसान है। [create your own components](https://reflex.dev/docs/wrapping-react/overview/)
|
|
||||||
|
|
||||||
### **स्टेट (State)**
|
|
||||||
|
|
||||||
Reflex आपके UI को आपकी स्टेट (state) के एक फ़ंक्शन के रूप में प्रस्तुत करता है।
|
|
||||||
|
|
||||||
```python
|
|
||||||
class State(rx.State):
|
|
||||||
"""The app state."""
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
```
|
|
||||||
|
|
||||||
स्टेट (state) ऐप में उन सभी वेरिएबल्स (vars) को परिभाषित करती है जो बदल सकती हैं और उन फ़ंक्शनों को जो उन्हें बदलते हैं।
|
|
||||||
|
|
||||||
यहां स्टेट (state) में `prompt` और `image_url` शामिल हैं। प्रगति और छवि दिखाने के लिए `processing` और `complete` बूलियन भी हैं।
|
|
||||||
|
|
||||||
### **इवेंट हैंडलर (Event Handlers)**
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_image(self):
|
|
||||||
"""Get the image from the prompt."""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Empty")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai_client.images.generate(
|
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
|
||||||
)
|
|
||||||
self.image_url = response.data[0].url
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
```
|
|
||||||
|
|
||||||
स्टेट (state) के अंदर, हम इवेंट हैंडलर्स (event handlers) को परिभाषित करते हैं जो स्टेट वेरिएबल्स को बदलते हैं। इवेंट हैंडलर्स (event handlers) से reflex में स्टेट (state) को मॉडिफ़ाय किया जा सकता हैं। इन्हें उपयोगकर्ता क्रियाओं (user actions) के प्रति प्रतिक्रिया (response) के रूप में बुलाया जा सकता है, जैसे कि बटन को क्लिक करना या टेक्स्ट बॉक्स में टाइप करना। इन क्रियाओं को इवेंट्स (events) कहा जाता है।
|
|
||||||
|
|
||||||
हमारे DALL·E. ऐप में एक इवेंट हैंडलर `get_image` है जिससे यह OpenAI API से इमेज प्राप्त करता है। इवेंट हैंडलर में `yield` का उपयोग करने कि वजह से UI अपडेट हो जाएगा। अन्यथा UI इवेंट हैंडलर के अंत में अपडेट होगा।
|
|
||||||
|
|
||||||
### **रूटिंग (Routing)**
|
|
||||||
|
|
||||||
आखिरकार, हम अपने एप्लिकेशन को परिभाषित करते हैं।
|
|
||||||
|
|
||||||
```python
|
|
||||||
app = rx.App()
|
|
||||||
```
|
|
||||||
|
|
||||||
हम अपने एप्लिकेशन के रूट से इंडेक्स कॉम्पोनेंट तक एक पेज को जोड़ते हैं। हम एक शीर्षक भी जोड़ते हैं जो पेज प्रीव्यू/ब्राउज़र टैब में दिखाई देगा।
|
|
||||||
|
|
||||||
```python
|
|
||||||
app.add_page(index, title="DALL-E")
|
|
||||||
```
|
|
||||||
|
|
||||||
आप और पेज जोड़कर एक मल्टी-पेज एप्लिकेशन बना सकते हैं।
|
|
||||||
|
|
||||||
## 📑 संसाधन (Resources)
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
📑 [दस्तावेज़](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [ब्लॉग](https://reflex.dev/blog) | 📱 [कॉम्पोनेंट लाइब्रेरी](https://reflex.dev/docs/library) | 🖼️ [गैलरी](https://reflex.dev/docs/gallery) | 🛸 [तैनाती](https://reflex.dev/docs/hosting/deploy)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## ✅ स्टेटस (Status)
|
|
||||||
|
|
||||||
Reflex दिसंबर 2022 में Pynecone नाम से शुरू हुआ।
|
|
||||||
|
|
||||||
फरवरी 2024 तक, हमारी होस्टिंग सेवा अल्फा में है! इस समय कोई भी अपने ऐप्स को मुफ्त में तैनात कर सकता है। देखें हमारी [रोडमैप](https://github.com/reflex-dev/reflex/issues/2727) योजनाबद्ध चीज़ों को जानने के लिए।
|
|
||||||
|
|
||||||
Reflex में हर सप्ताह नए रिलीज़ और फीचर्स आ रहे हैं! सुनिश्चित करें कि ⭐ स्टार और 👀 वॉच इस रेपोजिटरी को अपडेट रहने के लिए।
|
|
||||||
|
|
||||||
## (योगदान) Contributing
|
|
||||||
|
|
||||||
हम हर तरह के योगदान का स्वागत करते हैं! रिफ्लेक्स कम्यूनिटी में शुरुआत करने के कुछ अच्छे तरीके नीचे दिए गए हैं।
|
|
||||||
|
|
||||||
- **Join Our Discord** (डिस्कॉर्ड सर्वर से जुड़ें): Our [Discord](https://discord.gg/T5WSbC2YtQ) हमारा डिस्कॉर्ड रिफ्लेक्स प्रोजेक्ट पर सहायता प्राप्त करने और आप कैसे योगदान दे सकते हैं, इस पर चर्चा करने के लिए सबसे अच्छी जगह है।
|
|
||||||
- **GitHub Discussions** (गिटहब चर्चाएँ): उन सुविधाओं के बारे में बात करने का एक शानदार तरीका जिन्हें आप जोड़ना चाहते हैं या ऐसी चीज़ें जो भ्रमित करने वाली हैं/स्पष्टीकरण की आवश्यकता है।
|
|
||||||
- **GitHub Issues** (गिटहब समस्याएं): ये [बग](https://github.com/reflex-dev/reflex/issues) की रिपोर्ट करने का एक शानदार तरीका है। इसके अतिरिक्त, आप किसी मौजूदा समस्या को हल करने का प्रयास कर सकते हैं और एक पीआर सबमिट कर सकते हैं।
|
|
||||||
|
|
||||||
हम सक्रिय रूप से योगदानकर्ताओं की तलाश कर रहे हैं, चाहे आपका कौशल स्तर या अनुभव कुछ भी हो।योगदान करने के लिए [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) देखें।
|
|
||||||
|
|
||||||
## हमारे सभी योगदानकर्ताओं का धन्यवाद:
|
|
||||||
|
|
||||||
<a href="https://github.com/reflex-dev/reflex/graphs/contributors">
|
|
||||||
<img src="https://contrib.rocks/image?repo=reflex-dev/reflex" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
## लाइसेंस (License)
|
|
||||||
|
|
||||||
रिफ्लेक्स ओपन-सोर्स है और [अपाचे लाइसेंस 2.0](https://github.com/reflex-dev/reflex/blob/main/LICENSE) के तहत लाइसेंस प्राप्त है।
|
|
@ -1,230 +0,0 @@
|
|||||||
```diff
|
|
||||||
+ Stai cercando Pynecone? Sei nella repository giusto. Pynecone è stato rinominato in Reflex. +
|
|
||||||
```
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_dark.svg#gh-light-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_light.svg#gh-dark-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
### **✨ App web performanti e personalizzabili in puro Python. Distribuisci in pochi secondi. ✨**
|
|
||||||
[](https://badge.fury.io/py/reflex)
|
|
||||||

|
|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) |
|
|
||||||
[Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚙️ Installazione
|
|
||||||
|
|
||||||
Apri un terminale ed esegui (Richiede Python 3.10+):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install reflex
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🥳 Crea la tua prima app
|
|
||||||
|
|
||||||
Installando `reflex` si installa anche lo strumento da riga di comando `reflex`.
|
|
||||||
|
|
||||||
Verifica che l'installazione sia stata eseguita correttamente creando un nuovo progetto. (Sostituisci `nome_app` con il nome del tuo progetto):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir nome_app
|
|
||||||
cd nome_app
|
|
||||||
reflex init
|
|
||||||
```
|
|
||||||
|
|
||||||
Questo comando inizializza un'app template nella tua nuova directory.
|
|
||||||
|
|
||||||
Puoi eseguire questa app in modalità sviluppo con:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
reflex run
|
|
||||||
```
|
|
||||||
|
|
||||||
Dovresti vedere la tua app in esecuzione su http://localhost:3000.
|
|
||||||
|
|
||||||
Ora puoi modificare il codice sorgente in `nome_app/nome_app.py`. Reflex offre aggiornamenti rapidi, così puoi vedere le tue modifiche istantaneamente quando salvi il tuo codice.
|
|
||||||
|
|
||||||
## 🫧 Esempio App
|
|
||||||
|
|
||||||
Esaminiamo un esempio: creare un'interfaccia utente per la generazione di immagini attorno a DALL·E. Per semplicità, chiamiamo semplicemente l'API OpenAI, ma potresti sostituirla con un modello ML eseguito localmente.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/dalle.gif" alt="Un wrapper frontend per DALL·E, mostrato nel processo di generazione di un'immagine." width="550" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Ecco il codice completo per crearlo, Tutto fatto in un unico file Python!
|
|
||||||
|
|
||||||
```python
|
|
||||||
import reflex as rx
|
|
||||||
import openai
|
|
||||||
|
|
||||||
openai.api_key = "TUA_API_KEY"
|
|
||||||
|
|
||||||
class State(rx.State):
|
|
||||||
"""Lo stato dell'app."""
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
|
|
||||||
def get_image(self):
|
|
||||||
"""Ottieni l'immagine dal prompt."""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Vuoto")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai.Image.create(prompt=self.prompt, n=1, size="1024x1024")
|
|
||||||
self.image_url = response["data"][0]["url"]
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
rx.vstack(
|
|
||||||
rx.heading("DALL·E"),
|
|
||||||
rx.input(placeholder="Prompt Vuoto", on_blur=State.set_prompt),
|
|
||||||
rx.button(
|
|
||||||
"Genera Immagine",
|
|
||||||
on_click=State.get_image,
|
|
||||||
is_loading=State.processing,
|
|
||||||
width="100%",
|
|
||||||
),
|
|
||||||
rx.cond(
|
|
||||||
State.complete,
|
|
||||||
rx.image(
|
|
||||||
src=State.image_url,
|
|
||||||
height="25em",
|
|
||||||
width="25em",
|
|
||||||
)
|
|
||||||
),
|
|
||||||
padding="2em",
|
|
||||||
shadow="lg",
|
|
||||||
border_radius="lg",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
height="100vh",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Aggiungi stato e pagina all'app.
|
|
||||||
app = rx.App()
|
|
||||||
app.add_page(index, title="reflex:DALL·E")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Analizziamolo
|
|
||||||
|
|
||||||
### **Reflex UI**
|
|
||||||
|
|
||||||
Cominciamo con l'UI.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
...
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Questo `index` definisce il frontend dell'app.
|
|
||||||
|
|
||||||
Utilizziamo diversi componenti come `center`, `vstack`, `input`, e `button` per costruire il frontend. I componenti possono essere annidati gli uni negli altri per creare layout complessi. Puoi utilizzare argomenti chiave per stilizzarli con tutta la potenza di CSS.
|
|
||||||
|
|
||||||
Reflex offre [più di 60 componenti integrati](https://reflex.dev/docs/library) per aiutarti a iniziare. Stiamo attivamente aggiungendo più componenti ed è facile [creare i tuoi componenti](https://reflex.dev/docs/wrapping-react/overview/).
|
|
||||||
|
|
||||||
### **Stato (State)**
|
|
||||||
|
|
||||||
Reflex rappresenta la tua UI come una funzione del tuo stato.
|
|
||||||
|
|
||||||
```python
|
|
||||||
class State(rx.State):
|
|
||||||
"""Lo stato dell'app."""
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
```
|
|
||||||
|
|
||||||
Lo stato definisce tutte le variabili (chiamate vars) in un'app che possono cambiare e le funzioni che le cambiano.
|
|
||||||
|
|
||||||
Qui lo stato è composto da un `prompt` e `image_url`. Ci sono anche i booleani `processing` e `complete` per indicare quando mostrare l'andamento circolare e l'immagine.
|
|
||||||
|
|
||||||
### **Gestori di Eventi (Event Handlers)**
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_image(self):
|
|
||||||
"""Ottieni l'immagine dal prompt."""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Vuoto")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai.Image.create(prompt=self.prompt, n=1, size="1024x1024")
|
|
||||||
self.image_url = response["data"][0]["url"]
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
```
|
|
||||||
|
|
||||||
Dentro lo stato, definiamo funzioni chiamate gestori di eventi che cambiano le vars dello stato. I gestori di eventi sono il modo in cui possiamo modificare lo stato in Reflex. Possono essere chiamati in risposta alle azioni dell'utente, come fare clic su un pulsante o digitare in una casella di testo. Queste azioni vengono chiamate eventi.
|
|
||||||
|
|
||||||
La nostra app DALL·E ha un gestore di eventi, `get_image` con cui ottiene questa immagine dall'API OpenAI. Utilizzando `yield` nel mezzo di un gestore di eventi farà sì che l'UI venga aggiornata. Altrimenti, l'UI verrà aggiornata alla fine del gestore di eventi.
|
|
||||||
|
|
||||||
### **Instradamento (Routing)**
|
|
||||||
|
|
||||||
Infine, definiamo la nostra app.
|
|
||||||
|
|
||||||
```python
|
|
||||||
app = rx.App()
|
|
||||||
```
|
|
||||||
|
|
||||||
Possiamo aggiungere una pagina dalla radice dell'app al componente dell'indice.Aggiungiamo anche un titolo che apparirà nell'anteprima della pagina/scheda del browser
|
|
||||||
|
|
||||||
```python
|
|
||||||
app.add_page(index, title="DALL-E")
|
|
||||||
```
|
|
||||||
|
|
||||||
Puoi creare un'app multi-pagina aggiungendo altre pagine.
|
|
||||||
|
|
||||||
## 📑 Risorse
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
📑 [Documentazione](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Libreria Componenti](https://reflex.dev/docs/library) | 🖼️ [Immagini](https://reflex.dev/docs/gallery) | 🛸 [Distribuzione](https://reflex.dev/docs/hosting/deploy)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## ✅ Stato
|
|
||||||
|
|
||||||
Reflex è stato lanciato nel dicembre 2022 con il nome Pynecone.
|
|
||||||
|
|
||||||
Da luglio 2023, siamo nella fase di Beta Pubblica.
|
|
||||||
|
|
||||||
- :white_check_mark: **Alfa Pubblica**: Chiunque può installare e utilizzare Reflex. Potrebbero esserci dei problemi, ma stiamo lavorando per risolverli attivamente.
|
|
||||||
- :large_orange_diamond: **Beta Pubblica**: Abbastanza stabile per casi d'uso non aziendali.
|
|
||||||
- **Beta Hosting Pubblico**: _Opzionalmente_, distribuisci e ospita le tue app su Reflex!
|
|
||||||
- **Pubblico**: Reflex è pronto per la produzione.
|
|
||||||
|
|
||||||
Reflex ha nuove versioni e funzionalità in arrivo ogni settimana! Assicurati di :star: mettere una stella e :eyes: osservare questa repository per rimanere aggiornato.
|
|
||||||
|
|
||||||
## Contribuire
|
|
||||||
|
|
||||||
Diamo il benvenuto a contributi di qualsiasi dimensione! Di seguito sono alcuni modi per iniziare nella comunità Reflex.
|
|
||||||
|
|
||||||
- **Unisciti al nostro Discord**: Il nostro [Discord](https://discord.gg/T5WSbC2YtQ) è posto migliore per ottenere aiuto sul tuo progetto Reflex e per discutere come puoi contribuire.
|
|
||||||
- **Discussioni su GitHub**: Un ottimo modo per parlare delle funzionalità che desideri aggiungere o di cose che creano confusione o necessitano chiarimenti.
|
|
||||||
- **GitHub Issues**: Sono un ottimo modo per segnalare bug. Inoltre, puoi provare a risolvere un problema esistente e inviare un PR.
|
|
||||||
|
|
||||||
Stiamo attivamente cercando collaboratori, indipendentemente dal tuo livello di abilità o esperienza.
|
|
||||||
|
|
||||||
|
|
||||||
## Licenza
|
|
||||||
|
|
||||||
Reflex è open-source e rilasciato sotto la [Licenza Apache 2.0](LICENSE).
|
|
@ -1,255 +0,0 @@
|
|||||||
```diff
|
|
||||||
+ PyneconeはReflexに名前を変えました。Pyneconeを探している方は、今、ご覧のページがPyneconeのリポジトリになります。 +
|
|
||||||
```
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_dark.svg#gh-light-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_light.svg#gh-dark-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
### **✨ 即時デプロイが可能な、Pure Python で作ったパフォーマンスと汎用性が高い Web アプリケーション ✨**
|
|
||||||
|
|
||||||
[](https://badge.fury.io/py/reflex)
|
|
||||||

|
|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Reflex
|
|
||||||
|
|
||||||
Reflex は Python のみでフルスタック Web アプリケーションを作成できるライブラリです。
|
|
||||||
|
|
||||||
主な特徴:
|
|
||||||
|
|
||||||
- **Pure Python** - Web アプリケーションのフロントエンドとバックエンドを Python のみで実装できるため、Javascript を学ぶ必要がありません。
|
|
||||||
- **高い柔軟性** - Reflex は簡単に始められて、複雑なアプリケーションまで作成できます。
|
|
||||||
- **即時デプロイ** - ビルド後、すぐにデプロイが可能です。[単純な CLI コマンド](https://reflex.dev/docs/hosting/deploy-quick-start/)を使ったアプリケーションのデプロイや、自身のサーバーへのホストができます。
|
|
||||||
|
|
||||||
Reflex がどのように動作しているかを知るには、[アーキテクチャページ](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture)をご覧ください。
|
|
||||||
|
|
||||||
## ⚙️ インストール
|
|
||||||
|
|
||||||
ターミナルを開いて以下のコマンドを実行してください。(Python 3.10 以上が必要です。):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install reflex
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🥳 最初のアプリケーションを作ろう
|
|
||||||
|
|
||||||
`reflex`をインストールすると、`reflex`の CLI ツールが自動でインストールされます。
|
|
||||||
|
|
||||||
新しいプロジェクトを作成して、インストールが成功しているかを確認しましょう。(`my_app_name`を自身のプロジェクト名に書き換えて実行ください。):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir my_app_name
|
|
||||||
cd my_app_name
|
|
||||||
reflex init
|
|
||||||
```
|
|
||||||
|
|
||||||
上記のコマンドを実行すると、新しいフォルダにテンプレートアプリを作成します。
|
|
||||||
|
|
||||||
下記のコマンドを実行すると、開発モードでアプリを開始します。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
reflex run
|
|
||||||
```
|
|
||||||
|
|
||||||
http://localhost:3000 にアクセスしてアプリの動作を見ることができます。
|
|
||||||
|
|
||||||
`my_app_name/my_app_name.py`のソースコードを編集してみましょう!Reflex は fast refresh なので、ソースを保存した直後に変更が Web ページに反映されます。
|
|
||||||
|
|
||||||
## 🫧 実装例
|
|
||||||
|
|
||||||
実装例を見てみましょう: [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node)を中心とした画像生成 UI を作成しました。説明を簡単にするためにここでは[OpenAI API](https://platform.openai.com/docs/api-reference/authentication)を呼んでいますが、ローカルで動作している機械学習モデルに置き換えることも可能です。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/dalle.gif" alt="DALL·Eのフロントエンドラッパーです。画像を生成している過程を表示しています。" width="550" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
画像生成 UI のソースコードの全貌を見てみましょう。下記のように、単一の Python ファイルで作れます!
|
|
||||||
|
|
||||||
```python
|
|
||||||
import reflex as rx
|
|
||||||
import openai
|
|
||||||
|
|
||||||
openai_client = openai.OpenAI()
|
|
||||||
|
|
||||||
|
|
||||||
class State(rx.State):
|
|
||||||
"""アプリのステート"""
|
|
||||||
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
|
|
||||||
def get_image(self):
|
|
||||||
"""プロンプトからイメージを取得する"""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Empty")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai_client.images.generate(
|
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
|
||||||
)
|
|
||||||
self.image_url = response.data[0].url
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
|
|
||||||
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
rx.vstack(
|
|
||||||
rx.heading("DALL-E", font_size="1.5em"),
|
|
||||||
rx.input(
|
|
||||||
placeholder="Enter a prompt..",
|
|
||||||
on_blur=State.set_prompt,
|
|
||||||
width="25em",
|
|
||||||
),
|
|
||||||
rx.button(
|
|
||||||
"Generate Image",
|
|
||||||
on_click=State.get_image,
|
|
||||||
width="25em",
|
|
||||||
loading=State.processing
|
|
||||||
),
|
|
||||||
rx.cond(
|
|
||||||
State.complete,
|
|
||||||
rx.image(src=State.image_url, width="20em"),
|
|
||||||
),
|
|
||||||
align="center",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
height="100vh",
|
|
||||||
)
|
|
||||||
|
|
||||||
# ステートとページをアプリに追加
|
|
||||||
app = rx.App()
|
|
||||||
app.add_page(index, title="Reflex:DALL-E")
|
|
||||||
```
|
|
||||||
|
|
||||||
## それぞれの実装を見てみましょう
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="../../docs/images/dalle_colored_code_example.png" alt="DALL-E appのフロントエンドとバックエンドのパーツの違いを説明しています。" width="900" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### **Reflex UI**
|
|
||||||
|
|
||||||
UI から見てみましょう。
|
|
||||||
|
|
||||||
```python
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
...
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
`index`関数において、アプリのフロントエンドを定義しています。
|
|
||||||
|
|
||||||
フロントエンドを実装するにあたり、`center`、`vstack`、`input`、`button`など異なるコンポーネントを使用しています。コンポーネントはお互いにネストが可能であり、複雑なレイアウトを作成できます。また、keyword args を使うことで、CSS の機能をすべて使ったスタイルが可能です。
|
|
||||||
|
|
||||||
Reflex は[60 を超える内臓コンポーネント](https://reflex.dev/docs/library)があるため、すぐに始められます。私たちは、積極的にコンポーネントを追加していますが、簡単に[自身のコンポーネントを追加](https://reflex.dev/docs/wrapping-react/overview/)することも可能です。
|
|
||||||
|
|
||||||
### **ステート**
|
|
||||||
|
|
||||||
Reflex はステートの関数を用いて UI を表示します。
|
|
||||||
|
|
||||||
```python
|
|
||||||
class State(rx.State):
|
|
||||||
"""アプリのステート"""
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
ステートでは、アプリで変更が可能な全ての変数(vars と呼びます)と、vars の変更が可能な関数を定義します。
|
|
||||||
|
|
||||||
この例では、ステートを`prompt`と`image_url`で構成しています。そして、ブール型の`processing`と`complete`を用いて、ボタンを無効にするタイミング(画像生成中)や生成された画像を表示するタイミングを示しています。
|
|
||||||
|
|
||||||
### **イベントハンドラ**
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_image(self):
|
|
||||||
"""プロンプトからイメージを取得する"""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Empty")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai_client.images.generate(
|
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
|
||||||
)
|
|
||||||
self.image_url = response.data[0].url
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
```
|
|
||||||
|
|
||||||
ステートにおいて、ステートの vars を変更できるイベントハンドラ関数を定義しています。イベントハンドラは Reflex において、ステートの vars を変更する方法です。ボタンクリックやテキストボックスの入力など、ユーザのアクションに応じてイベントハンドラが呼ばれます。
|
|
||||||
|
|
||||||
DALL·E.アプリには、OpenAI API からイメージを取得する`get_image`関数があります。イベントハンドラの最後で UI の更新がかかるため、関数の途中に`yield`を入れることで先に UI を更新しています。
|
|
||||||
|
|
||||||
### **ルーティング**
|
|
||||||
|
|
||||||
最後に、アプリを定義します。
|
|
||||||
|
|
||||||
```python
|
|
||||||
app = rx.App()
|
|
||||||
```
|
|
||||||
|
|
||||||
アプリにページを追加し、ドキュメントルートを index コンポーネントにルーティングしています。更に、ページのプレビューやブラウザタブに表示されるタイトルを記載しています。
|
|
||||||
|
|
||||||
```python
|
|
||||||
app.add_page(index, title="DALL-E")
|
|
||||||
```
|
|
||||||
|
|
||||||
ページを追加することで、マルチページアプリケーションを作成できます。
|
|
||||||
|
|
||||||
## 📑 リソース
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Component Library](https://reflex.dev/docs/library) | 🖼️ [Templates](https://reflex.dev/templates/) | 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## ✅ ステータス
|
|
||||||
|
|
||||||
2022 年 12 月に、Reflex は Pynecone という名前でローンチしました。
|
|
||||||
|
|
||||||
2024 年 2 月に、ホスティングサービスをアルファ版でリリースしました!アルファ版では、だれでも Reflex アプリケーションを無料でデプロイできます。今後の予定は[ロードマップ](https://github.com/reflex-dev/reflex/issues/2727)において見れます。
|
|
||||||
|
|
||||||
Reflex は毎週、新しいリリースや機能追加を行っています!最新情報を逃さないために、 :star: Star や :eyes: Watch をお願いします。
|
|
||||||
|
|
||||||
## コントリビュート
|
|
||||||
|
|
||||||
様々なサイズのコントリビュートを歓迎しています!Reflex コミュニティに入るための方法を、いくつかリストアップします。
|
|
||||||
|
|
||||||
- **Discord に参加**: [Discord](https://discord.gg/T5WSbC2YtQ)は、Reflex プロジェクトの相談や、コントリビュートについての話し合いをするための、最適な場所です。
|
|
||||||
- **GitHub Discussions**: GitHub Discussions では、追加したい機能や、複雑で解明が必要な事柄についての議論に適している場所です。
|
|
||||||
- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues)はバグの報告に適している場所です。また、課題を解決した PR のサブミットにチャレンジしていただくことも、可能です。
|
|
||||||
|
|
||||||
CONTスキルや経験に関わらず、私たちはコントリビュータを積極的に探しています。コントリビュートするために、[CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)をご覧ください。
|
|
||||||
|
|
||||||
## 私たちのコントリビュータに感謝!:
|
|
||||||
|
|
||||||
<a href="https://github.com/reflex-dev/reflex/graphs/contributors">
|
|
||||||
<img src="https://contrib.rocks/image?repo=reflex-dev/reflex" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
## ライセンス
|
|
||||||
|
|
||||||
Reflex はオープンソースであり、[Apache License 2.0](LICENSE)に基づいてライセンス供与されます。
|
|
@ -1,240 +0,0 @@
|
|||||||
```diff
|
|
||||||
+ Pynecone을 찾고 계신가요? 이곳이 맞습니다. Pynecone은 이제 Reflex로 불립니다. +
|
|
||||||
```
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_dark.svg#gh-light-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_light.svg#gh-dark-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
### **✨ 순수 Python으로 고성능 사용자 정의 웹앱을 만들어 보세요. 몇 초만에 배포 가능합니다. ✨**
|
|
||||||
[](https://badge.fury.io/py/reflex)
|
|
||||||

|
|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
|
||||||
---
|
|
||||||
## ⚙️ 설치
|
|
||||||
|
|
||||||
터미널을 열고 실행하세요. (Python 3.10+ 필요):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install reflex
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🥳 첫 앱 만들기
|
|
||||||
|
|
||||||
`reflex`를 설치하면, `reflex` 명령어 라인 도구도 설치됩니다.
|
|
||||||
|
|
||||||
새 프로젝트를 생성하여 설치가 성공적인지 확인합니다. (`my_app_name`을 프로젝트 이름으로 변경합니다.):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir my_app_name
|
|
||||||
cd my_app_name
|
|
||||||
reflex init
|
|
||||||
```
|
|
||||||
|
|
||||||
이 명령어는 새 디렉토리에 템플릿 앱을 초기화합니다.
|
|
||||||
|
|
||||||
개발 모드에서 이 앱을 실행할 수 있습니다:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
reflex run
|
|
||||||
```
|
|
||||||
|
|
||||||
http://localhost:3000 에서 앱이 실행 됩니다.
|
|
||||||
|
|
||||||
이제 `my_app_name/my_app_name.py`에서 소스코드를 수정할 수 있습니다. Reflex는 빠른 새로고침을 지원하므로 코드를 저장할 때마다 즉시 변경 사항을 볼 수 있습니다.
|
|
||||||
|
|
||||||
|
|
||||||
## 🫧 예시 앱
|
|
||||||
|
|
||||||
예시를 살펴보겠습니다: DALL·E를 중심으로 이미지 생성 UI를 만들어 보겠습니다. 간단하게 하기 위해 OpenAI API를 호출했지만, 이를 로컬에서 실행되는 ML 모델로 대체할 수 있습니다.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/dalle.gif" alt="A frontend wrapper for DALL·E, shown in the process of generating an image." width="550" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
이것이 완성된 코드입니다. 이 모든 것은 하나의 Python 파일에서 이루어집니다!
|
|
||||||
|
|
||||||
```python
|
|
||||||
import reflex as rx
|
|
||||||
import openai
|
|
||||||
|
|
||||||
openai_client = openai.OpenAI()
|
|
||||||
|
|
||||||
|
|
||||||
class State(rx.State):
|
|
||||||
"""The app state."""
|
|
||||||
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
|
|
||||||
def get_image(self):
|
|
||||||
"""Get the image from the prompt."""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Empty")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai_client.images.generate(
|
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
|
||||||
)
|
|
||||||
self.image_url = response.data[0].url
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
|
|
||||||
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
rx.vstack(
|
|
||||||
rx.heading("DALL-E", font_size="1.5em"),
|
|
||||||
rx.input(
|
|
||||||
placeholder="Enter a prompt..",
|
|
||||||
on_blur=State.set_prompt,
|
|
||||||
width="25em",
|
|
||||||
),
|
|
||||||
rx.button(
|
|
||||||
"Generate Image",
|
|
||||||
on_click=State.get_image,
|
|
||||||
width="25em",
|
|
||||||
loading=State.processing
|
|
||||||
),
|
|
||||||
rx.cond(
|
|
||||||
State.complete,
|
|
||||||
rx.image(src=State.image_url, width="20em"),
|
|
||||||
),
|
|
||||||
align="center",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
height="100vh",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add state and page to the app.
|
|
||||||
app = rx.App()
|
|
||||||
app.add_page(index, title="Reflex:DALL-E")
|
|
||||||
```
|
|
||||||
|
|
||||||
## 하나씩 살펴보겠습니다.
|
|
||||||
|
|
||||||
### **Reflex UI**
|
|
||||||
|
|
||||||
UI부터 시작해봅시다.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
...
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
`index` 함수는 앱의 프론트엔드를 정의합니다.
|
|
||||||
|
|
||||||
`center`, `vstack`, `input`, `button`과 같은 다양한 컴포넌트를 사용하여 프론트엔드를 구축합니다.
|
|
||||||
컴포넌트들은 복잡한 레이아웃을 만들기 위해 서로 중첩될 수 있습니다.
|
|
||||||
그리고 키워드 인자를 사용하여 CSS의 모든 기능을 사용하여 스타일을 지정할 수 있습니다.
|
|
||||||
|
|
||||||
|
|
||||||
Reflex는 시작하기 위한 [60개 이상의 기본 컴포넌트](https://reflex.dev/docs/library)를 제공하고 있습니다. 더 많은 컴포넌트를 추가하고 있으며, [자신만의 컴포넌트를 생성하는 것](https://reflex.dev/docs/wrapping-react/overview/)도 쉽습니다.
|
|
||||||
|
|
||||||
### **State**
|
|
||||||
|
|
||||||
Reflex는 UI를 State 함수로 표현합니다.
|
|
||||||
|
|
||||||
```python
|
|
||||||
class State(rx.State):
|
|
||||||
"""The app state."""
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
```
|
|
||||||
|
|
||||||
state는 앱에서 변경될 수 있는 모든 변수(vars로 불림)와 이러한 변수를 변경하는 함수를 정의합니다.
|
|
||||||
|
|
||||||
여기서 state는 `prompt`와 `image_url`로 구성됩니다. 또한 `processing`과 `complete`라는 불리언 값이 있습니다. 이 값들은 이미지 생성 중 버튼을 비활성화할 때와, 결과 이미지를 표시할 때를 나타냅니다.
|
|
||||||
|
|
||||||
### **Event Handlers**
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_image(self):
|
|
||||||
"""Get the image from the prompt."""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Empty")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai_client.images.generate(
|
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
|
||||||
)
|
|
||||||
self.image_url = response.data[0].url
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
```
|
|
||||||
|
|
||||||
State 내에서, state vars를 변경하는 이벤트 핸들러라고 불리는 함수를 정의합니다. 이벤트 핸들러는 Reflex에서 state를 변경하는 방법입니다. 버튼을 클릭하거나 텍스트 상자에 입력하는 것과 같이 사용자 동작에 응답하여 호출될 수 있습니다. 이러한 동작을 이벤트라고 합니다.
|
|
||||||
|
|
||||||
DALL·E. 앱에는 OpenAI API에서 이미지를 가져오는 `get_image` 이벤트 핸들러가 있습니다. 이벤트 핸들러의 중간에 `yield`를 사용하면 UI가 업데이트됩니다. 그렇지 않으면 UI는 이벤트 핸들러의 끝에서 업데이트됩니다.
|
|
||||||
|
|
||||||
### **Routing**
|
|
||||||
|
|
||||||
마지막으로, 앱을 정의합니다.
|
|
||||||
|
|
||||||
```python
|
|
||||||
app = rx.App()
|
|
||||||
```
|
|
||||||
|
|
||||||
앱의 루트에서 index 컴포넌트로 페이지를 추가합니다. 또한 페이지 미리보기/브라우저 탭에 표시될 제목도 추가합니다.
|
|
||||||
|
|
||||||
```python
|
|
||||||
app.add_page(index, title="DALL-E")
|
|
||||||
```
|
|
||||||
|
|
||||||
여러 페이지를 추가하여 멀티 페이지 앱을 만들 수 있습니다.
|
|
||||||
|
|
||||||
## 📑 자료
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
📑 [문서](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [블로그](https://reflex.dev/blog) | 📱 [컴포넌트 라이브러리](https://reflex.dev/docs/library) | 🖼️ [갤러리](https://reflex.dev/docs/gallery) | 🛸 [배포](https://reflex.dev/docs/hosting/deploy)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## ✅ 상태
|
|
||||||
|
|
||||||
Reflex는 2022년 12월 Pynecone이라는 이름으로 출시되었습니다.
|
|
||||||
|
|
||||||
2023년 7월 현재, 우리는 **Public Beta** 상태에 있습니다.
|
|
||||||
|
|
||||||
- :white_check_mark: **Public Alpha**: 누구나 Reflex를 설치하고 사용할 수 있습니다. 문제가 발생할 수도 있지만 적극적으로 수정합니다.
|
|
||||||
- :large_orange_diamond: **Public Beta**: 비상업적 용도로 충분히 안정적입니다.
|
|
||||||
- **Public Hosting Beta**: _선택적으로_, Reflex에서 앱을 배포하고 호스팅할 수 있습니다!
|
|
||||||
- **Public** : Reflex는 프로덕션 준비를 마쳤습니다.
|
|
||||||
|
|
||||||
Reflex는 매주 새로운 릴리즈와 기능을 제공합니다! 최신 정보를 확인하려면 :star: Star와 :eyes: Watch를 눌러 이 저장소를 확인하세요.
|
|
||||||
|
|
||||||
## 기여
|
|
||||||
|
|
||||||
우리는 모든 기여를 환영합니다! 아래는 Reflex 커뮤니티에 참여하는 좋은 방법입니다.
|
|
||||||
|
|
||||||
- **Discord 참여**: 우리의 [Discord](https://discord.gg/T5WSbC2YtQ)는 Reflex 프로젝트에 대한 도움을 받고 기여하는 방법을 논의하는 최고의 장소입니다.
|
|
||||||
- **GitHub Discussions**: 추가하고 싶은 기능이나 혼란스럽거나 해결이 필요한 것들에 대해 이야기하는 좋은 방법입니다.
|
|
||||||
- **GitHub Issues**: 이것은 버그를 보고하는 훌륭한 방법입니다. 또한, 기존의 이슈를 해결하고 PR을 제출할 수 있습니다.
|
|
||||||
|
|
||||||
우리는 능력이나 경험에 상관없이 적극적으로 기여자를 찾고 있습니다.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Reflex는 오픈소스이며 [Apache License 2.0](LICENSE)로 라이선스가 부여됩니다.
|
|
@ -1,262 +0,0 @@
|
|||||||
```diff
|
|
||||||
+ دنبال Pynecone میگردی؟ شما در مخزن درستی قرار داری. Pynecone به Reflex تغییر نام داده است. +
|
|
||||||
```
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_dark.svg#gh-light-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_light.svg#gh-dark-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
### **✨ برنامه های تحت وب قابل تنظیم، کارآمد تماما پایتونی که در چند ثانیه مستقر(دپلوی) میشود. ✨**
|
|
||||||
[](https://badge.fury.io/py/reflex)
|
|
||||||

|
|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Reflex - رفلکس
|
|
||||||
|
|
||||||
رفلکس(Reflex) یک کتابخانه برای ساخت برنامه های وب فول استک تماما پایتونی است.
|
|
||||||
|
|
||||||
ویژگی های کلیدی:
|
|
||||||
* **تماما پایتونی** - فرانت اند و بک اند برنامه خود را همه در پایتون بنویسید، بدون نیاز به یادگیری جاوا اسکریپت.
|
|
||||||
* **انعطاف پذیری کامل** - شروع به کار با Reflex آسان است، اما می تواند به برنامه های پیچیده نیز تبدیل شود.
|
|
||||||
* **دپلوی فوری** - پس از ساخت، برنامه خود را با [یک دستور](https://reflex.dev/docs/hosting/deploy-quick-start/) دپلوی کنید یا آن را روی سرور خود میزبانی کنید.
|
|
||||||
|
|
||||||
برای آشنایی با نحوه عملکرد Reflex [صفحه معماری](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) را ببینید.
|
|
||||||
|
|
||||||
## ⚙️ Installation - نصب و راه اندازی
|
|
||||||
|
|
||||||
یک ترمینال را باز کنید و اجرا کنید (نیازمند Python 3.10+):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install reflex
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🥳 اولین برنامه خود را ایجاد کنید
|
|
||||||
|
|
||||||
نصب `reflex` همچنین `reflex` در خط فرمان را نصب میکند.
|
|
||||||
|
|
||||||
با ایجاد یک پروژه جدید موفقیت آمیز بودن نصب را تست کنید. (`my_app_name` را با اسم پروژه خودتان جایگزین کنید):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir my_app_name
|
|
||||||
cd my_app_name
|
|
||||||
reflex init
|
|
||||||
```
|
|
||||||
|
|
||||||
این دستور یک برنامه الگو(تمپلیت) را در فهرست(دایرکتوری) جدید شما مقداردهی اولیه می کند
|
|
||||||
|
|
||||||
می توانید این برنامه را در حالت توسعه(development) اجرا کنید:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
reflex run
|
|
||||||
```
|
|
||||||
|
|
||||||
باید برنامه خود را در حال اجرا ببینید در http://localhost:3000.
|
|
||||||
|
|
||||||
اکنون میتوانید کد منبع را در «my_app_name/my_app_name.py» تغییر دهید. Reflex دارای تازهسازیهای سریعی است، بنابراین میتوانید تغییرات خود را بلافاصله پس از ذخیره کد خود مشاهده کنید.
|
|
||||||
|
|
||||||
|
|
||||||
## 🫧 Example App - برنامه نمونه
|
|
||||||
|
|
||||||
بیایید یک مثال بزنیم: ایجاد یک رابط کاربری برای تولید تصویر [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node). برای سادگی، ما فراخوانی میکنیم [OpenAI API](https://platform.openai.com/docs/api-reference/authentication), اما می توانید آن را با یک مدل ML که به صورت محلی اجرا می شود جایگزین کنید.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/dalle.gif" alt="A frontend wrapper for DALL·E, shown in the process of generating an image." width="550" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
در اینجا کد کامل برای ایجاد این پروژه آمده است. همه اینها در یک فایل پایتون انجام می شود!
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
import reflex as rx
|
|
||||||
import openai
|
|
||||||
|
|
||||||
openai_client = openai.OpenAI()
|
|
||||||
|
|
||||||
|
|
||||||
class State(rx.State):
|
|
||||||
"""The app state."""
|
|
||||||
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
|
|
||||||
def get_image(self):
|
|
||||||
"""Get the image from the prompt."""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Empty")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai_client.images.generate(
|
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
|
||||||
)
|
|
||||||
self.image_url = response.data[0].url
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
|
|
||||||
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
rx.vstack(
|
|
||||||
rx.heading("DALL-E", font_size="1.5em"),
|
|
||||||
rx.input(
|
|
||||||
placeholder="Enter a prompt..",
|
|
||||||
on_blur=State.set_prompt,
|
|
||||||
width="25em",
|
|
||||||
),
|
|
||||||
rx.button(
|
|
||||||
"Generate Image",
|
|
||||||
on_click=State.get_image,
|
|
||||||
width="25em",
|
|
||||||
loading=State.processing
|
|
||||||
),
|
|
||||||
rx.cond(
|
|
||||||
State.complete,
|
|
||||||
rx.image(src=State.image_url, width="20em"),
|
|
||||||
),
|
|
||||||
align="center",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
height="100vh",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add state and page to the app.
|
|
||||||
app = rx.App()
|
|
||||||
app.add_page(index, title="Reflex:DALL-E")
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## بیاید سادش کنیم
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="docs/images/dalle_colored_code_example.png" alt="Explaining the differences between backend and frontend parts of the DALL-E app." width="900" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
### **Reflex UI - رابط کاربری رفلکس**
|
|
||||||
|
|
||||||
بیایید با رابط کاربری شروع کنیم.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
...
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
تابع `index` قسمت فرانت اند برنامه را تعریف می کند.
|
|
||||||
|
|
||||||
ما از اجزای مختلفی مثل `center`, `vstack`, `input` و `button` استفاده میکنیم تا فرانت اند را بسازیم. اجزاء را می توان درون یکدیگر قرار داد
|
|
||||||
برای ایجاد طرح بندی های پیچیده می توانید از args کلمات کلیدی برای استایل دادن به آنها از CSS استفاده کنید.
|
|
||||||
|
|
||||||
رفلکس دارای [بیش از 60 جزء](https://reflex.dev/docs/library) برای کمک به شما برای شروع. ما به طور فعال اجزای بیشتری را اضافه می کنیم, و این خیلی آسان است که [اجزا خود را بسازید](https://reflex.dev/docs/wrapping-react/overview/).
|
|
||||||
|
|
||||||
### **State - حالت**
|
|
||||||
|
|
||||||
رفلکس رابط کاربری شما را به عنوان تابعی از وضعیت شما نشان می دهد.
|
|
||||||
|
|
||||||
```python
|
|
||||||
class State(rx.State):
|
|
||||||
"""The app state."""
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
حالت تمام متغیرها(variables) (به نام vars) را در یک برنامه که می توانند تغییر دهند و توابعی که آنها را تغییر می دهند تعریف می کند..
|
|
||||||
|
|
||||||
در اینجا حالت از یک `prompt` و `image_url` تشکیل شده است. همچنین دو بولی `processing` و `complete` برای نشان دادن زمان غیرفعال کردن دکمه (در طول تولید تصویر) و زمان نمایش تصویر نتیجه وجود دارد.
|
|
||||||
|
|
||||||
### **Event Handlers - کنترل کنندگان رویداد**
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_image(self):
|
|
||||||
"""Get the image from the prompt."""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Empty")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai_client.images.generate(
|
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
|
||||||
)
|
|
||||||
self.image_url = response.data[0].url
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
```
|
|
||||||
|
|
||||||
در داخل حالت، توابعی به نام کنترل کننده رویداد تعریف می کنیم که متغیرهای حالت را تغییر می دهند. کنترل کننده های رویداد راهی هستند که می توانیم وضعیت را در Reflex تغییر دهیم. آنها را می توان در پاسخ به اقدامات کاربر، مانند کلیک کردن روی یک دکمه یا تایپ کردن در یک متن، فراخوانی کرد. به این اعمال وقایع می گویند.
|
|
||||||
|
|
||||||
برنامه DALL·E ما دارای یک کنترل کننده رویداد، `get_image` است که این تصویر را از OpenAI API دریافت می کند. استفاده از `yield` در وسط کنترلکننده رویداد باعث بهروزرسانی رابط کاربری میشود. در غیر این صورت رابط کاربری در پایان کنترل کننده رویداد به روز می شود.
|
|
||||||
|
|
||||||
### **Routing - مسیریابی**
|
|
||||||
|
|
||||||
بالاخره اپلیکیشن خود را تعریف می کنیم.
|
|
||||||
|
|
||||||
```python
|
|
||||||
app = rx.App()
|
|
||||||
```
|
|
||||||
|
|
||||||
ما یک صفحه از root برنامه را به جزء index اضافه می کنیم. ما همچنین عنوانی را اضافه می کنیم که در برگه پیش نمایش/مرورگر صفحه نمایش داده می شود.
|
|
||||||
|
|
||||||
```python
|
|
||||||
app.add_page(index, title="DALL-E")
|
|
||||||
```
|
|
||||||
|
|
||||||
با افزودن صفحات بیشتر می توانید یک برنامه چند صفحه ای ایجاد کنید.
|
|
||||||
|
|
||||||
## 📑 Resources - منابع
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
📑 [اسناد](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [وبلاگ](https://reflex.dev/blog) | 📱 [کتابخانه جزء](https://reflex.dev/docs/library) | 🖼️ [گالری](https://reflex.dev/docs/gallery) | 🛸 [استقرار](https://reflex.dev/docs/hosting/deploy-quick-start)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
## ✅ Status - وضعیت
|
|
||||||
|
|
||||||
رفلکس(reflex) در دسامبر 2022 با نام Pynecone راه اندازی شد.
|
|
||||||
|
|
||||||
از فوریه 2024، سرویس میزبانی ما در آلفا است! در این مدت هر کسی میتواند برنامههای خود را به صورت رایگان اجرا کند. [نقشه راه](https://github.com/reflex-dev/reflex/issues/2727) را ببینید تا متوجه شوید چه برنامهریزی شده است.
|
|
||||||
|
|
||||||
رفلکس(reflex) هر هفته نسخه ها و ویژگی های جدیدی دارد! مطمئن شوید که :star: ستاره و :eyes: این مخزن را تماشا کنید تا به روز بمانید.
|
|
||||||
|
|
||||||
## Contributing - مشارکت کردن
|
|
||||||
|
|
||||||
ما از مشارکت در هر اندازه استقبال می کنیم! در زیر چند راه خوب برای شروع در انجمن رفلکس آورده شده است.
|
|
||||||
|
|
||||||
- **به Discord ما بپیوندید**: [Discord](https://discord.gg/T5WSbC2YtQ) ما بهترین مکان برای دریافت کمک در مورد پروژه Reflex و بحث در مورد اینکه چگونه می توانید کمک کنید است.
|
|
||||||
- **بحث های GitHub**: راهی عالی برای صحبت در مورد ویژگی هایی که می خواهید اضافه کنید یا چیزهایی که گیج کننده هستند/نیاز به توضیح دارند.
|
|
||||||
- **قسمت مشکلات GitHub**: [قسمت مشکلات](https://github.com/reflex-dev/reflex/issues) یک راه عالی برای گزارش اشکال هستند. علاوه بر این، می توانید یک مشکل موجود را حل کنید و یک PR(pull request) ارسال کنید.
|
|
||||||
|
|
||||||
ما فعالانه به دنبال مشارکت کنندگان هستیم، فارغ از سطح مهارت یا تجربه شما. برای مشارکت [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) را بررسی کنید.
|
|
||||||
|
|
||||||
|
|
||||||
## All Thanks To Our Contributors - با تشکر از همکاران ما:
|
|
||||||
<a href="https://github.com/reflex-dev/reflex/graphs/contributors">
|
|
||||||
<img src="https://contrib.rocks/image?repo=reflex-dev/reflex" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
## License - مجوز
|
|
||||||
|
|
||||||
رفلکس متن باز و تحت مجوز [Apache License 2.0](LICENSE) است.
|
|
@ -1,235 +0,0 @@
|
|||||||
```diff
|
|
||||||
+ Está procurando pelo Pynecone? Este é o repositório certo. Pynecone foi renomeado para Reflex. +
|
|
||||||
```
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_dark.svg#gh-light-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_light.svg#gh-dark-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
### **✨ Web apps customizáveis, performáticos, em Python puro. Faça deploy em segundos. ✨**
|
|
||||||
[](https://badge.fury.io/py/reflex)
|
|
||||||

|
|
||||||

|
|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
|
||||||
---
|
|
||||||
## ⚙️ Instalação
|
|
||||||
|
|
||||||
Abra um terminal e execute (Requer Python 3.10+):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install reflex
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🥳 Crie o seu primeiro app
|
|
||||||
|
|
||||||
Instalar `reflex` também instala a ferramenta de linha de comando `reflex`.
|
|
||||||
|
|
||||||
Crie um novo projeto para verificar se a instalação foi bem sucedida. (Mude `nome_do_meu_app` com o nome do seu projeto):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir nome_do_meu_app
|
|
||||||
cd nome_do_meu_app
|
|
||||||
reflex init
|
|
||||||
```
|
|
||||||
|
|
||||||
Este comando inicializa um app base no seu novo diretório.
|
|
||||||
|
|
||||||
Você pode executar este app em modo desenvolvimento:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
reflex run
|
|
||||||
```
|
|
||||||
|
|
||||||
Você deve conseguir verificar seu app sendo executado em http://localhost:3000.
|
|
||||||
|
|
||||||
Agora, você pode modificar o código fonte em `nome_do_meu_app/nome_do_meu_app.py`. O Reflex apresenta recarregamento rápido para que você possa ver suas mudanças instantâneamente quando você salva o seu código.
|
|
||||||
|
|
||||||
|
|
||||||
## 🫧 Exemplo de App
|
|
||||||
|
|
||||||
Veja o seguinte exemplo: criar uma interface de criação de imagens por meio do DALL-E. Para fins de simplicidade, vamos apenas chamar a API da OpenAI, mas você pode substituir esta solução por qualquer modelo de aprendizado de máquina que preferir, executando localmente.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/dalle.gif" alt="Um encapsulador frontend para o DALL-E, mostrado no processo de criação de uma imagem." width="550" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Aqui está o código completo para criar este projeto. Isso tudo foi feito apenas em um arquivo Python!
|
|
||||||
|
|
||||||
```python
|
|
||||||
import reflex as rx
|
|
||||||
import openai
|
|
||||||
|
|
||||||
openai.api_key = "YOUR_API_KEY"
|
|
||||||
|
|
||||||
class State(rx.State):
|
|
||||||
"""Estado da aplicação."""
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
|
|
||||||
def get_image(self):
|
|
||||||
"""Obtenção da imagem a partir do prompt."""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Empty")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai.Image.create(prompt=self.prompt, n=1, size="1024x1024")
|
|
||||||
self.image_url = response["data"][0]["url"]
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
|
|
||||||
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
rx.vstack(
|
|
||||||
rx.heading("DALL·E"),
|
|
||||||
rx.input(placeholder="Enter a prompt", on_blur=State.set_prompt),
|
|
||||||
rx.button(
|
|
||||||
"Generate Image",
|
|
||||||
on_click=State.get_image,
|
|
||||||
is_loading=State.processing,
|
|
||||||
width="100%",
|
|
||||||
),
|
|
||||||
rx.cond(
|
|
||||||
State.complete,
|
|
||||||
rx.image(
|
|
||||||
src=State.image_url,
|
|
||||||
height="25em",
|
|
||||||
width="25em",
|
|
||||||
)
|
|
||||||
),
|
|
||||||
padding="2em",
|
|
||||||
shadow="lg",
|
|
||||||
border_radius="lg",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
height="100vh",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Adição do estado e da página no app.
|
|
||||||
app = rx.App()
|
|
||||||
app.add_page(index, title="reflex:DALL·E")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Vamos por partes.
|
|
||||||
|
|
||||||
### **Reflex UI**
|
|
||||||
|
|
||||||
Vamos começar com a UI (Interface de Usuário)
|
|
||||||
|
|
||||||
```python
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
...
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Esta função `index` define o frontend do app.
|
|
||||||
|
|
||||||
Usamos diferentes componentes, como `center`, `vstack`, `input` e `button`, para construir o frontend. Componentes podem ser aninhados um no do outro
|
|
||||||
para criar layouts mais complexos. E você pode usar argumentos de chave-valor para estilizá-los com todo o poder do CSS.
|
|
||||||
|
|
||||||
O Reflex vem com [60+ componentes nativos](https://reflex.dev/docs/library) para te ajudar. Estamos adicionando ativamente mais componentes, mas também é fácil [criar seus próprios componentes](https://reflex.dev/docs/wrapping-react/overview/).
|
|
||||||
|
|
||||||
### **Estado**
|
|
||||||
|
|
||||||
O Reflex representa a sua UI como uma função do seu estado.
|
|
||||||
|
|
||||||
```python
|
|
||||||
class State(rx.State):
|
|
||||||
"""Estado da aplicação."""
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
```
|
|
||||||
|
|
||||||
O estado define todas as variáveis (chamadas de vars) que podem ser modificadas por funções no seu app.
|
|
||||||
|
|
||||||
Aqui, o estado é composto por um `prompt` e uma `image_url`, representando, respectivamente, o texto que descreve a imagem a ser gerada e a url da imagem gerada.
|
|
||||||
|
|
||||||
### **Handlers de Eventos**
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_image(self):
|
|
||||||
"""Obtenção da imagem a partir do prompt."""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Empty")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai.Image.create(prompt=self.prompt, n=1, size="1024x1024")
|
|
||||||
self.image_url = response["data"][0]["url"]
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
```
|
|
||||||
|
|
||||||
Dentro do estado, são definidas funções chamadas de Handlers de Eventos, que podem mudar as variáveis do estado. Handlers de Eventos são a forma com a qual podemos modificar o estado dentro do Reflex. Eles podem ser chamados como resposta a uma ação do usuário, como o clique de um botão ou a escrita em uma caixa de texto. Estas ações geram eventos que são processados pelos Handlers.
|
|
||||||
|
|
||||||
Nosso app DALL-E possui um Handler de Evento chamado `get_image`, que obtêm a imagem da API da OpenAI. Usar `yield` no meio de um Handler de Evento causa a atualização da UI do seu app. Caso contrário, a UI seria atualizada no fim da execução de um Handler de Evento.
|
|
||||||
|
|
||||||
### **Rotas**
|
|
||||||
|
|
||||||
Finalmente, definimos nosso app.
|
|
||||||
|
|
||||||
```python
|
|
||||||
app = rx.App()
|
|
||||||
```
|
|
||||||
|
|
||||||
Adicionamos uma página na raíz do app, apontando para o componente index. Também adicionamos um título ("DALL-E") que irá aparecer na aba no navegador.
|
|
||||||
|
|
||||||
```python
|
|
||||||
app.add_page(index, title="DALL-E")
|
|
||||||
```
|
|
||||||
|
|
||||||
Você pode criar mais páginas e adicioná-las ao seu app.
|
|
||||||
|
|
||||||
## 📑 Recursos
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Biblioteca de Componentes](https://reflex.dev/docs/library) | 🖼️ [Galeria](https://reflex.dev/docs/gallery) | 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## ✅ Status
|
|
||||||
|
|
||||||
O Reflex foi lançado em Dezembro de 2022 com o nome Pynecone.
|
|
||||||
|
|
||||||
Em Julho de 2023, estamos no estágio de **Beta Público**.
|
|
||||||
|
|
||||||
- :white_check_mark: **Alpha Público**: Qualquer um pode instalar e usar o Reflex. Podem existir alguns problemas, mas estamos trabalhando ativamente para resolvê-los.
|
|
||||||
- :large_orange_diamond: **Beta Público**: Estável o suficiente para utilizar em projetos pessoais, com menor criticidade.
|
|
||||||
- **Hospedagem Pública Beta**: _Opcionalmente_, implante e hospede os seus apps no Reflex!
|
|
||||||
- **Público**: O Reflex está pronto para produção.
|
|
||||||
|
|
||||||
O Reflex agora possui novas versões e funcionalidades sendo lançadas toda semana! Lembre-se de marcar o repositório com uma :star: estrela e :eyes: acompanhe para ficar atualizado sobre o projeto.
|
|
||||||
|
|
||||||
## Contribuições
|
|
||||||
|
|
||||||
Nós somos abertos a contribuições de qualquer tamanho! Abaixo, seguem algumas boas formas de começar a contribuir para a comunidade do Reflex.
|
|
||||||
|
|
||||||
- **Entre no nosso Discord**: Nosso [Discord](https://discord.gg/T5WSbC2YtQ) é o melhor lugar para conseguir ajuda no seu projeto Reflex e para discutir como você pode contribuir.
|
|
||||||
- **Discussões no GitHub**: Uma boa forma de conversar sobre funcionalidades que você gostaria de ver ou coisas que ainda estão confusas/exigem ajuda.
|
|
||||||
- **Issues no GitHub**: Excelente forma de reportar bugs. Além disso, você pode tentar resolver alguma issue existente e enviar um Pull Request.
|
|
||||||
|
|
||||||
Estamos ativamente buscando novos contribuidores, não importa o seu nível de habilidade ou experiência.
|
|
||||||
|
|
||||||
## Licença
|
|
||||||
|
|
||||||
O Reflex é um software de código aberto, sob a [Apache License 2.0](LICENSE).
|
|
@ -1,242 +0,0 @@
|
|||||||
```diff
|
|
||||||
+ Pynecone'u mu arıyorsun? Doğru repodasın. Pynecone, Reflex olarak yeniden adlandırıldı. +
|
|
||||||
```
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_dark.svg#gh-light-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_light.svg#gh-dark-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
### **✨ Saf Python'da performanslı, özelleştirilebilir web uygulamaları. Saniyeler içinde dağıtın. ✨**
|
|
||||||
|
|
||||||
[](https://badge.fury.io/py/reflex)
|
|
||||||

|
|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚙️ Kurulum
|
|
||||||
|
|
||||||
Bir terminal açın ve çalıştırın (Python 3.10+ gerekir):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install reflex
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🥳 İlk Uygulamanı Oluştur
|
|
||||||
|
|
||||||
`reflex`'i kurduğunuzda `reflex` komut satırı aracınıda kurmuş olursunuz.
|
|
||||||
|
|
||||||
Kurulumun başarılı olduğunu test etmek için yeni bir proje oluşturun. (`my_app_name`'i proje ismiyle değiştirin.):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir my_app_name
|
|
||||||
cd my_app_name
|
|
||||||
reflex init
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu komut ile birlikte yeni oluşturduğunuz dizinde bir şablon uygulaması oluşturur.
|
|
||||||
|
|
||||||
Uygulamanızı geliştirme modunda başlatabilirsiniz:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
reflex run
|
|
||||||
```
|
|
||||||
|
|
||||||
Uygulamanızın http://localhost:3000 adresinde çalıştığını görmelisiniz.
|
|
||||||
|
|
||||||
Şimdi `my_app_name/my_app_name.py` yolundaki kaynak kodu düzenleyebilirsiniz. Reflex'in hızlı yenileme özelliği vardır, böylece kodunuzu kaydettiğinizde değişikliklerinizi anında görebilirsiniz.
|
|
||||||
|
|
||||||
## 🫧 Örnek Uygulama
|
|
||||||
|
|
||||||
Bir örnek üzerinden gidelim: [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node) kullanarak bir görüntü oluşturma uygulaması yazalım. Basit olması açısından, yalnızca [OpenAI API](https://platform.openai.com/docs/api-reference/authentication)'ını kullanıyoruz, ancak bunu yerel olarak çalıştırılan bir makine öğrenimi modeliyle değiştirebilirsiniz.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/dalle.gif" alt="A frontend wrapper for DALL·E, shown in the process of generating an image." width="550" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
İşte bunu oluşturmak için kodun tamamaı. Her şey sadece bir Python dosyasıyla hazırlandı!
|
|
||||||
|
|
||||||
```python
|
|
||||||
import reflex as rx
|
|
||||||
import openai
|
|
||||||
|
|
||||||
openai.api_key = "YOUR_API_KEY"
|
|
||||||
|
|
||||||
class State(rx.State):
|
|
||||||
"""Uygulama durumu."""
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
|
|
||||||
def get_image(self):
|
|
||||||
"""Prompt'tan görüntüyü alın."""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Empty")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai.Image.create(prompt=self.prompt, n=1, size="1024x1024")
|
|
||||||
self.image_url = response["data"][0]["url"]
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
|
|
||||||
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
rx.vstack(
|
|
||||||
rx.heading("DALL·E"),
|
|
||||||
rx.input(placeholder="Enter a prompt", on_blur=State.set_prompt),
|
|
||||||
rx.button(
|
|
||||||
"Generate Image",
|
|
||||||
on_click=State.get_image,
|
|
||||||
is_loading=State.processing,
|
|
||||||
width="100%",
|
|
||||||
),
|
|
||||||
rx.cond(
|
|
||||||
State.complete,
|
|
||||||
rx.image(
|
|
||||||
src=State.image_url,
|
|
||||||
height="25em",
|
|
||||||
width="25em",
|
|
||||||
)
|
|
||||||
),
|
|
||||||
padding="2em",
|
|
||||||
shadow="lg",
|
|
||||||
border_radius="lg",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
height="100vh",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Sayfa ve durumu uygulamaya ekleyin.
|
|
||||||
app = rx.App()
|
|
||||||
app.add_page(index, title="reflex:DALL·E")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Daha Detaylı İceleyelim
|
|
||||||
|
|
||||||
### **Reflex UI**
|
|
||||||
|
|
||||||
UI (Kullanıcı Arayüzü) ile başlayalım.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
...
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu `index` fonkisyonu uygulamanın frontend'ini tanımlar.
|
|
||||||
|
|
||||||
Frontend'i oluşturmak için `center`, `vstack`, `input`, ve `button` gibi farklı bileşenler kullanıyoruz. Karmaşık düzenler oluşturmak için bileşenleri birbirinin içine yerleştirilebiliriz. Ayrıca bunları CSS'nin tüm gücüyle şekillendirmek için anahtar kelime argümanları kullanabilirsiniz.
|
|
||||||
|
|
||||||
Reflex, işinizi kolaylaştırmak için [60'tan fazla dahili bileşen](https://reflex.dev/docs/library) içerir. Aktif olarak yeni bileşen ekliyoruz ve [kendi bileşenlerinizi oluşturmak](https://reflex.dev/docs/wrapping-react/overview/) oldukça kolay.
|
|
||||||
|
|
||||||
### **Durum (State)**
|
|
||||||
|
|
||||||
Reflex arayüzünüzü durumunuzun bir fonksiyonu olarak temsil eder.
|
|
||||||
|
|
||||||
```python
|
|
||||||
class State(rx.State):
|
|
||||||
"""Uygulama durumu."""
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
```
|
|
||||||
|
|
||||||
Durum (State), bir uygulamadaki değişebilen tüm değişkenleri (vars olarak adlandırılır) ve bunları değiştiren fonksiyonları tanımlar.
|
|
||||||
|
|
||||||
Burada durum `prompt` ve `image_url`inden oluşur. Ayrıca döngüsel ilerlemenin ve görüntünün ne zaman gösterileceğini belirtmek için `processing` ve `complete` booleanları da vardır.
|
|
||||||
|
|
||||||
### **Olay İşleyicileri (Event Handlers)**
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_image(self):
|
|
||||||
"""Prompt'tan görüntüyü alın."""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Empty")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai.Image.create(prompt=self.prompt, n=1, size="1024x1024")
|
|
||||||
self.image_url = response["data"][0]["url"]
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
```
|
|
||||||
|
|
||||||
Durum içinde, durum değişkenlerini değiştiren olay işleyicileri adı verilen fonksiyonları tanımlarız. Olay işleyicileri, Reflex'te durumu değiştirebilmemizi sağlar. Bir düğmeye tıklamak veya bir metin kutusuna yazı yazmak gibi kullanıcı eylemlerine yanıt olarak çağrılabilirler. Bu eylemlere olay denir.
|
|
||||||
|
|
||||||
DALL·E uygulamamız bir olay işleyicisine sahip, `get_image` ki bu da OpenAI API'ından oluşturulan resmi alır. Bir olay işleyicisinin ortasında `yield`ın kullanılması UI'ın güncellenmesini sağlar. Aksi takdirde UI olay işleyicisinin sonunda güncellenecektir.
|
|
||||||
|
|
||||||
### **Yönlendirme (Routing)**
|
|
||||||
|
|
||||||
En sonunda uygulamamızı tanımlıyoruz.
|
|
||||||
|
|
||||||
```python
|
|
||||||
app = rx.App()
|
|
||||||
```
|
|
||||||
|
|
||||||
Uygulamamızın kök dizinine index bileşeninden bir sayfa ekliyoruz. Ayrıca sayfa önizlemesinde/tarayıcı sekmesinde görünecek bir başlık ekliyoruz.
|
|
||||||
|
|
||||||
```python
|
|
||||||
app.add_page(index, title="DALL-E")
|
|
||||||
```
|
|
||||||
|
|
||||||
Daha fazla sayfa ekleyerek çok sayfalı bir uygulama oluşturabilirsiniz.
|
|
||||||
|
|
||||||
## 📑 Kaynaklar
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Component Library](https://reflex.dev/docs/library) | 🖼️ [Templates](https://reflex.dev/templates/) | 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## ✅ Durum
|
|
||||||
|
|
||||||
Reflex, Aralık 2022'de Pynecone adıyla piyasaya sürüldü.
|
|
||||||
|
|
||||||
Temmuz 2023 itibarıyla **Herkese Açık Beta** aşamasındayız.
|
|
||||||
|
|
||||||
- :white_check_mark: **Public Alpha**: Herkes Reflex'i yükleyebilir ve kullanabilir. Sorunlar olabilir, ancak bunları aktif olarak çözmek için çalışıyoruz.
|
|
||||||
- :large_orange_diamond: **Public Beta**: Kurumsal olmayan kullanım durumları için yeterince kararlı.
|
|
||||||
- **Public Hosting Beta**: _Optionally_, uygulamalarınızı Reflex ile dağıtın ve barındırın!
|
|
||||||
- **Public**: Reflex kullanıma hazır.
|
|
||||||
|
|
||||||
Reflex'in her hafta yeni sürümleri ve özellikleri geliyor! Güncel kalmak için :star: yıldızlamayı ve bu repoyu :eyes: izlediğinizden emin olun.
|
|
||||||
|
|
||||||
## Katkı
|
|
||||||
|
|
||||||
Her boyuttaki katkıları memnuniyetle karşılıyoruz! Aşağıda Reflex topluluğuna adım atmanın bazı yolları mevcut.
|
|
||||||
|
|
||||||
- **Discord Kanalımıza Katılın**: [Discord'umuz](https://discord.gg/T5WSbC2YtQ), Reflex projeniz hakkında yardım almak ve nasıl katkıda bulunabileceğinizi tartışmak için en iyi yerdir.
|
|
||||||
- **GitHub Discussions**: Eklemek istediğiniz özellikler veya kafa karıştırıcı, açıklığa kavuşturulması gereken şeyler hakkında konuşmanın harika bir yolu.
|
|
||||||
- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues) hataları bildirmenin mükemmel bir yoludur. Ayrıca mevcut bir sorunu deneyip çözebilir ve bir PR (Pull Requests) gönderebilirsiniz.
|
|
||||||
|
|
||||||
Beceri düzeyiniz veya deneyiminiz ne olursa olsun aktif olarak katkıda bulunacak kişiler arıyoruz. Katkı sağlamak için katkı sağlama rehberimize bakabilirsiniz: [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
|
|
||||||
|
|
||||||
## Hepsi Katkıda Bulunanlar Sayesinde:
|
|
||||||
|
|
||||||
<a href="https://github.com/reflex-dev/reflex/graphs/contributors">
|
|
||||||
<img src="https://contrib.rocks/image?repo=reflex-dev/reflex" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
## Lisans
|
|
||||||
|
|
||||||
Reflex açık kaynaklıdır ve [Apache License 2.0](LICENSE) altında lisanslıdır.
|
|
@ -1,267 +0,0 @@
|
|||||||
```diff
|
|
||||||
+ Bạn đang tìm kiếm Pynecone? Bạn đã tìm đúng. Pynecone đã được đổi tên thành Reflex. +
|
|
||||||
```
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_dark.svg#gh-light-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_light.svg#gh-dark-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
### **✨ Ứng dụng web hiệu suất cao, tùy chỉnh bằng Python thuần. Deploy trong vài giây. ✨**
|
|
||||||
[](https://badge.fury.io/py/reflex)
|
|
||||||

|
|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md) | [Tiếng Việt](https://github.com/reflex-dev/reflex/blob/main/docs/vi/README.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Reflex
|
|
||||||
|
|
||||||
Reflex là một thư viện để xây dựng ứng dụng web toàn bộ bằng Python thuần.
|
|
||||||
|
|
||||||
Các tính năng chính:
|
|
||||||
* **Python thuần tuý** - Viết toàn bộ ứng dụng cả backend và frontend hoàn toàn bằng Python, không cần học JavaScript.
|
|
||||||
* **Full Flexibility** - Reflex dễ dàng để bắt đầu, nhưng cũng có thể mở rộng lên các ứng dụng phức tạp.
|
|
||||||
* **Deploy Instantly** - Sau khi xây dựng ứng dụng, bạn có thể triển khai bằng [một dòng lệnh](https://reflex.dev/docs/hosting/deploy-quick-start/) hoặc triển khai trên server của riêng bạn.
|
|
||||||
|
|
||||||
Đọc [bài viết về kiến trúc hệ thống](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) để hiểu rõ các hoạt động của Reflex.
|
|
||||||
|
|
||||||
## ⚙️ Cài đặt
|
|
||||||
|
|
||||||
Mở cửa sổ lệnh và chạy (Yêu cầu Python phiên bản 3.10+):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install reflex
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🥳 Tạo ứng dụng đầu tiên
|
|
||||||
|
|
||||||
Cài đặt `reflex` cũng như cài đặt công cụ dòng lệnh `reflex`.
|
|
||||||
|
|
||||||
Kiểm tra việc cài đặt đã thành công hay chưa bằng cách tạo mới một ứng dụng. (Thay `my_app_name` bằng tên ứng dụng của bạn):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir my_app_name
|
|
||||||
cd my_app_name
|
|
||||||
reflex init
|
|
||||||
```
|
|
||||||
|
|
||||||
Lệnh này tạo ra một ứng dụng mẫu trong một thư mục mới.
|
|
||||||
|
|
||||||
Bạn có thể chạy ứng dụng ở chế độ phát triển.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
reflex run
|
|
||||||
```
|
|
||||||
|
|
||||||
Bạn có thể xem ứng dụng của bạn ở địa chỉ http://localhost:3000.
|
|
||||||
|
|
||||||
Bạn có thể thay đổi mã nguồn ở `my_app_name/my_app_name.py`. Reflex nhanh chóng làm mới và bạn có thể thấy thay đổi trên ứng dụng của bạn ngay lập tức khi bạn lưu file.
|
|
||||||
|
|
||||||
|
|
||||||
## 🫧 Ứng dụng ví dụ
|
|
||||||
|
|
||||||
Bắt đầu với ví dụ: tạo một ứng dụng tạo ảnh bằng [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node). Để cho đơn giản, chúng ta sẽ sử dụng [OpenAI API](https://platform.openai.com/docs/api-reference/authentication), nhưng bạn có thể sử dụng model của chính bạn được triển khai trên local.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/dalle.gif" alt="A frontend wrapper for DALL·E, shown in the process of generating an image." width="550" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Đây là toàn bộ đoạn mã để xây dựng ứng dụng trên. Nó được viết hoàn toàn trong một file Python!
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
import reflex as rx
|
|
||||||
import openai
|
|
||||||
|
|
||||||
openai_client = openai.OpenAI()
|
|
||||||
|
|
||||||
|
|
||||||
class State(rx.State):
|
|
||||||
"""The app state."""
|
|
||||||
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
|
|
||||||
def get_image(self):
|
|
||||||
"""Get the image from the prompt."""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Empty")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai_client.images.generate(
|
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
|
||||||
)
|
|
||||||
self.image_url = response.data[0].url
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
|
|
||||||
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
rx.vstack(
|
|
||||||
rx.heading("DALL-E", font_size="1.5em"),
|
|
||||||
rx.input(
|
|
||||||
placeholder="Enter a prompt..",
|
|
||||||
on_blur=State.set_prompt,
|
|
||||||
width="25em",
|
|
||||||
),
|
|
||||||
rx.button(
|
|
||||||
"Generate Image",
|
|
||||||
on_click=State.get_image,
|
|
||||||
width="25em",
|
|
||||||
loading=State.processing
|
|
||||||
),
|
|
||||||
rx.cond(
|
|
||||||
State.complete,
|
|
||||||
rx.image(src=State.image_url, width="20em"),
|
|
||||||
),
|
|
||||||
align="center",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
height="100vh",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add state and page to the app.
|
|
||||||
app = rx.App()
|
|
||||||
app.add_page(index, title="Reflex:DALL-E")
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Hãy phân tích chi tiết.
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="../images/dalle_colored_code_example.png" alt="Explaining the differences between backend and frontend parts of the DALL-E app." width="900" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
### **Reflex UI**
|
|
||||||
|
|
||||||
Bắt đầu với giao diện chính.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
...
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Hàm `index` định nghĩa phần giao diện chính của ứng dụng.
|
|
||||||
|
|
||||||
Chúng tôi sử dụng các component (thành phần) khác nhau như `center`, `vstack`, `input` và `button` để xây dựng giao diện phía trước.
|
|
||||||
Các component có thể được lồng vào nhau để tạo ra các bố cục phức tạp. Và bạn cũng có thể sử dụng từ khoá `args` để tận dụng đầy đủ sức mạnh của CSS.
|
|
||||||
|
|
||||||
Reflex có đến hơn [60 component được xây dựng sẵn](https://reflex.dev/docs/library) để giúp bạn bắt đầu. Chúng ta có thể tạo ra một component mới khá dễ dàng, thao khảo: [xây dựng component của riêng bạn](https://reflex.dev/docs/wrapping-react/overview/).
|
|
||||||
|
|
||||||
### **State**
|
|
||||||
|
|
||||||
Reflex biểu diễn giao diện bằng các hàm của state (trạng thái).
|
|
||||||
|
|
||||||
```python
|
|
||||||
class State(rx.State):
|
|
||||||
"""The app state."""
|
|
||||||
prompt = ""
|
|
||||||
image_url = ""
|
|
||||||
processing = False
|
|
||||||
complete = False
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Một state định nghĩa các biến (được gọi là vars) có thể thay đổi trong một ứng dụng và cho phép các hàm có thể thay đổi chúng.
|
|
||||||
|
|
||||||
Tại đây state được cấu thành từ một `prompt` và `image_url`.
|
|
||||||
Có cũng những biến boolean `processing` và `complete`
|
|
||||||
để chỉ ra khi nào tắt nút (trong quá trình tạo hình ảnh)
|
|
||||||
và khi nào hiển thị hình ảnh kết quả.
|
|
||||||
|
|
||||||
### **Event Handlers**
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_image(self):
|
|
||||||
"""Get the image from the prompt."""
|
|
||||||
if self.prompt == "":
|
|
||||||
return rx.window_alert("Prompt Empty")
|
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
|
||||||
yield
|
|
||||||
response = openai_client.images.generate(
|
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
|
||||||
)
|
|
||||||
self.image_url = response.data[0].url
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
```
|
|
||||||
|
|
||||||
Với các state, chúng ta định nghĩa các hàm có thể thay đổi state vars được gọi là event handlers. Event handler là cách chúng ta có thể thay đổi state trong Reflex. Chúng có thể là phản hồi khi người dùng thao tác, chằng hạn khi nhấn vào nút hoặc khi đang nhập trong text box. Các hành động này được gọi là event.
|
|
||||||
|
|
||||||
Ứng dụng DALL·E. của chúng ta có một event handler, `get_image` để lấy hình ảnh từ OpenAI API. Sử dụng từ khoá `yield` in ở giữa event handler để cập nhật giao diện. Hoặc giao diện có thể cập nhật ở cuối event handler.
|
|
||||||
|
|
||||||
### **Routing**
|
|
||||||
|
|
||||||
Cuối cùng, chúng ta định nghĩa một ứng dụng.
|
|
||||||
|
|
||||||
```python
|
|
||||||
app = rx.App()
|
|
||||||
```
|
|
||||||
|
|
||||||
Chúng ta thêm một trang ở đầu ứng dụng bằng index component. Chúng ta cũng thêm tiêu đề của ứng dụng để hiển thị lên trình duyệt.
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
app.add_page(index, title="DALL-E")
|
|
||||||
```
|
|
||||||
|
|
||||||
Bạn có thể tạo một ứng dụng nhiều trang bằng cách thêm trang.
|
|
||||||
|
|
||||||
## 📑 Tài liệu
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Component Library](https://reflex.dev/docs/library) | 🖼️ [Templates](https://reflex.dev/templates/) | 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
## ✅ Status
|
|
||||||
|
|
||||||
Reflex phát hành vào tháng 12/2022 với tên là Pynecone.
|
|
||||||
|
|
||||||
Đến tháng 02/2024, chúng tôi tạo ra dịch vụ dưới phiên bản alpha! Trong thời gian này mọi người có thể triển khai ứng dụng hoàn toàn miễn phí. Xem [roadmap](https://github.com/reflex-dev/reflex/issues/2727) để biết thêm chi tiết.
|
|
||||||
|
|
||||||
Reflex ra phiên bản mới với các tính năng mới hàng tuần! Hãy :star: star và :eyes: watch repo này để thấy các cập nhật mới nhất.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Chúng tôi chào đón mọi đóng góp dù lớn hay nhỏ. Dưới đây là các cách để bắt đầu với cộng đồng Reflex.
|
|
||||||
|
|
||||||
- **Discord**: [Discord](https://discord.gg/T5WSbC2YtQ) của chúng tôi là nơi tốt nhất để nhờ sự giúp đỡ và thảo luận các bạn có thể đóng góp.
|
|
||||||
- **GitHub Discussions**: Là cách tốt nhất để thảo luận về các tính năng mà bạn có thể đóng góp hoặc những điều bạn chưa rõ.
|
|
||||||
- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues) là nơi tốt nhất để thông báo. Ngoài ra bạn có thể sửa chữa các vấn đề bằng cách tạo PR.
|
|
||||||
|
|
||||||
Chúng tôi luôn sẵn sàng tìm kiếm các contributor, bất kể kinh nghiệm. Để tham gia đóng góp, xin mời xem
|
|
||||||
[CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
|
|
||||||
|
|
||||||
|
|
||||||
## Xin cảm ơn các Contributors:
|
|
||||||
<a href="https://github.com/reflex-dev/reflex/graphs/contributors">
|
|
||||||
<img src="https://contrib.rocks/image?repo=reflex-dev/reflex" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Reflex là mã nguồn mở và sử dụng giấy phép [Apache License 2.0](LICENSE).
|
|
@ -1,159 +1,147 @@
|
|||||||
```diff
|
|
||||||
+ 寻找 Pynecone 吗?您来对了.Pynecone 已经更名为 Reflex.+
|
|
||||||
```
|
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_dark.svg#gh-light-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_light.svg#gh-dark-mode-only" alt="Reflex Logo" width="300px">
|
|
||||||
|
|
||||||
|
<img src="../../images/cones.png">
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
### **✨ 使用 Python 创建高效且可自定义的网页应用程序,几秒钟内即可部署.✨**
|
**✨ 使用 Python 创建高效且可自订的网页应用程序,几秒钟内即可部署。**
|
||||||
|
|
||||||
|
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) 📱 [Component Library](https://reflex.dev/docs/library) 🖼️ [Gallery](https://reflex.dev/docs/gallery) 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy)
|
||||||
|
|
||||||
[](https://badge.fury.io/py/reflex)
|
[](https://badge.fury.io/py/reflex)
|
||||||

|

|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|

|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
[](https://discord.gg/T5WSbC2YtQ)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
---
|
### 不同语言的 README
|
||||||
|
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Reflex
|
[English](../../../README.md) | [简体中文](README.md) | [繁体中文](../zh_tw/README.md)
|
||||||
|
|
||||||
Reflex 是一个使用纯Python构建全栈web应用的库。
|
---
|
||||||
|
|
||||||
关键特性:
|
## 📦 1. 安装
|
||||||
* **纯Python** - 前端、后端开发全都使用Python,不需要学习Javascript。
|
|
||||||
* **完整的灵活性** - Reflex很容易上手, 并且也可以扩展到复杂的应用程序。
|
|
||||||
* **立即部署** - 构建后,使用[单个命令](https://reflex.dev/docs/hosting/deploy-quick-start/)就能部署应用程序;或者也可以将其托管在您自己的服务器上。
|
|
||||||
|
|
||||||
请参阅我们的[架构页](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture)了解Reflex如何工作。
|
Reflex 需要以下最低要求:
|
||||||
|
|
||||||
## ⚙️ 安装
|
- Python 3.7+
|
||||||
|
- [Node.js 16.8.0+](https://nodejs.org/en/) (不用担心,你不需要写任何 JavaScript!)
|
||||||
|
|
||||||
打开一个终端并且运行(要求Python3.10+):
|
```
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install reflex
|
pip install reflex
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🥳 创建您的第一个应用程序
|
## 🥳 2. 创建你的第一个应用程序
|
||||||
|
|
||||||
安装 Reflex 的同时也会安装 `reflex` 命令行工具.
|
安装 Reflex 的同时也会安装 `rx` 命令行工具. 通过创建一个新项目来测试是否安装成功。
|
||||||
|
|
||||||
通过创建一个新项目来测试是否安装成功(请把 my_app_name 替代为您的项目名字):
|
把 my_app_name 替代为你的项目名字:
|
||||||
|
|
||||||
```bash
|
```
|
||||||
mkdir my_app_name
|
mkdir my_app_name
|
||||||
cd my_app_name
|
cd my_app_name
|
||||||
reflex init
|
reflex init
|
||||||
```
|
```
|
||||||
|
|
||||||
这段命令会在新文件夹初始化一个应用程序模板.
|
当你第一次运行这个命令,将会自动下载与安装 [bun](https://bun.sh/)。
|
||||||
|
|
||||||
您可以在开发者模式下运行这个应用程序:
|
这个命令会初始化一个应用程序模板在一个新的文件夹。
|
||||||
|
|
||||||
```bash
|
## 🏃 3. 运行
|
||||||
|
|
||||||
|
你可以在开发者模式运行这个应用程序:
|
||||||
|
|
||||||
|
```
|
||||||
reflex run
|
reflex run
|
||||||
```
|
```
|
||||||
|
|
||||||
您可以看到您的应用程序运行在 http://localhost:3000.
|
你可以看到你的应用程序运行在 http://localhost:3000。
|
||||||
|
|
||||||
现在您可以在以下位置修改代码 `my_app_name/my_app_name.py`,Reflex 拥有快速刷新(fast refresh),所以您可以在保存代码后马上看到更改.
|
现在在以下位置修改原代码 `my_app_name/my_app_name.py`,Reflex 拥有快速重整所以你可以在保存代码后马上看到更改。
|
||||||
|
|
||||||
## 🫧 范例
|
## 🫧 范例
|
||||||
|
|
||||||
让我们来看一个例子: 创建一个使用 [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node) 进行图像生成的图形界面.为了保持范例简单,我们只使用 OpenAI API,但是您可以将其替换成本地端的 ML 模型.
|
让我们来看一个例子: 创建一个使用 DALL·E 的图形用户接口,为了保持范例简单,我们只使用 OpenAI API,但是你可以将其替换成本地端的 ML 模型。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/dalle.gif" alt="DALL·E的前端界面, 展示了图片生成的进程" width="550" />
|
<img src="../images/dalle.gif" alt="A frontend wrapper for DALL·E, shown in the process of generating an image." width="550" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
这是这个范例的完整代码,只需要一个 Python 文件就可以完成!
|
这是上述范例的完整代码,只需要一个 Python 文件就可以完成!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
import openai
|
import openai
|
||||||
|
|
||||||
openai_client = openai.OpenAI()
|
openai.api_key = "YOUR_API_KEY"
|
||||||
|
|
||||||
|
|
||||||
class State(rx.State):
|
class State(rx.State):
|
||||||
"""The app state."""
|
"""应用程序状态"""
|
||||||
|
|
||||||
prompt = ""
|
prompt = ""
|
||||||
image_url = ""
|
image_url = ""
|
||||||
processing = False
|
image_processing = False
|
||||||
complete = False
|
image_made = False
|
||||||
|
|
||||||
|
def process_image(self):
|
||||||
|
"""设置图片处理旗标为 True 并设置还未产生图片"""
|
||||||
|
self.image_processing = True
|
||||||
|
self.image_made = False
|
||||||
|
|
||||||
def get_image(self):
|
def get_image(self):
|
||||||
"""Get the image from the prompt."""
|
"""运用 prompt 取得的参数产生图片"""
|
||||||
if self.prompt == "":
|
response = openai.Image.create(prompt=self.prompt, n=1, size="1024x1024")
|
||||||
return rx.window_alert("Prompt Empty")
|
self.image_url = response["data"][0]["url"]
|
||||||
|
self.image_processing = False
|
||||||
self.processing, self.complete = True, False
|
self.image_made = True
|
||||||
yield
|
|
||||||
response = openai_client.images.generate(
|
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
|
||||||
)
|
|
||||||
self.image_url = response.data[0].url
|
|
||||||
self.processing, self.complete = False, True
|
|
||||||
|
|
||||||
|
|
||||||
def index():
|
def index():
|
||||||
return rx.center(
|
return rx.center(
|
||||||
rx.vstack(
|
rx.vstack(
|
||||||
rx.heading("DALL-E", font_size="1.5em"),
|
rx.heading("DALL·E", font_size="1.5em"),
|
||||||
rx.input(
|
rx.input(placeholder="Enter a prompt..", on_blur=State.set_prompt),
|
||||||
placeholder="Enter a prompt..",
|
|
||||||
on_blur=State.set_prompt,
|
|
||||||
width="25em",
|
|
||||||
),
|
|
||||||
rx.button(
|
rx.button(
|
||||||
"Generate Image",
|
"产生图片",
|
||||||
on_click=State.get_image,
|
on_click=[State.process_image, State.get_image],
|
||||||
width="25em",
|
width="100%",
|
||||||
loading=State.processing
|
|
||||||
),
|
),
|
||||||
|
rx.divider(),
|
||||||
rx.cond(
|
rx.cond(
|
||||||
State.complete,
|
State.image_processing,
|
||||||
rx.image(src=State.image_url, width="20em"),
|
rx.circular_progress(is_indeterminate=True),
|
||||||
|
rx.cond(
|
||||||
|
State.image_made,
|
||||||
|
rx.image(
|
||||||
|
src=State.image_url,
|
||||||
|
height="25em",
|
||||||
|
width="25em",
|
||||||
|
)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
align="center",
|
bg="white",
|
||||||
|
padding="2em",
|
||||||
|
shadow="lg",
|
||||||
|
border_radius="lg",
|
||||||
),
|
),
|
||||||
width="100%",
|
width="100%",
|
||||||
height="100vh",
|
height="100vh",
|
||||||
|
bg="radial-gradient(circle at 22% 11%,rgba(62, 180, 137,.20),hsla(0,0%,100%,0) 19%)",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add state and page to the app.
|
# 把状态跟页面添加到应用程序。
|
||||||
app = rx.App()
|
app = rx.App(state=State)
|
||||||
app.add_page(index, title="Reflex:DALL-E")
|
app.add_page(index, title="Reflex:DALL·E")
|
||||||
|
app.compile()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### **Reflex 中的图形用户接口**
|
||||||
|
|
||||||
|
让我们分解以上步骤。
|
||||||
|
|
||||||
|
|
||||||
## 让我们分解以上步骤.
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="../../images/dalle_colored_code_example.png" alt="解释 DALL-E app 的前端和后端部分的区别。" width="900" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
### **Reflex UI**
|
|
||||||
|
|
||||||
让我们从UI开始.
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def index():
|
def index():
|
||||||
@ -162,101 +150,91 @@ def index():
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
这个 `index` 函数定义了应用程序的前端.
|
这个 `index` function 定义了应用程序的前端.
|
||||||
|
|
||||||
我们用不同的组件比如 `center`, `vstack`, `input`, 和 `button` 来创建前端, 组件之间可以相互嵌入,来创建复杂的布局.
|
我们用不同的组件像是 `center`, `vstack`, `input`, 和 `button` 来创建前端, 组件之间可以相互嵌入,来创建复杂的布局。
|
||||||
并且您可以使用关键字参数来使用 CSS 的全部功能.
|
并且你可以使用关键字参数来使用 CSS 的全部功能。
|
||||||
|
|
||||||
Reflex 拥有 [60+ 个内置组件](https://reflex.dev/docs/library) 来帮助您开始创建应用程序. 我们正在积极添加组件, 但是您也可以容易的 [创建自己的组件](https://reflex.dev/docs/wrapping-react/overview/).
|
Reflex 拥有 [60+ built-in components](https://reflex.dev/docs/library) 来帮助你开始创建应用程序。
|
||||||
|
我们正在积极添加组件, 但是你也可以简单的自己创建一些组件 [create your own components](https://reflex.dev/docs/advanced-guide/wrapping-react)。
|
||||||
|
|
||||||
### **State**
|
### **状态**
|
||||||
|
|
||||||
Reflex 用 State 来渲染您的 UI.
|
Reflex 用 State 来渲染你的 UI。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class State(rx.State):
|
class State(rx.State):
|
||||||
"""The app state."""
|
"""应用程序状态"""
|
||||||
prompt = ""
|
prompt = ""
|
||||||
image_url = ""
|
image_url = ""
|
||||||
processing = False
|
image_processing = False
|
||||||
complete = False
|
image_made = False
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
State定义了所有可能会发生变化的变量(称为 vars)以及能够改变这些变量的函数.
|
State 定义了应用程序中所有可以更改的变量及变更他们的 function (称为 vars)。
|
||||||
|
|
||||||
在这个范例中,State由 `prompt` 和 `image_url` 组成.此外,State还包含有两个布尔值 `processing` 和 `complete`,用于指示何时显示循环进度指示器和图像.
|
这里的状态由 `prompt` 和 `image_url`组成, 以及布尔变量 `image_processing` 和 `image_made` 来决定何时显示进度条及图片。
|
||||||
|
|
||||||
### **Event Handlers**
|
### **事件处理进程**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_image(self):
|
def process_image(self):
|
||||||
"""Get the image from the prompt."""
|
"""设置图片处理旗标为 True 并设置还未产生图片"""
|
||||||
if self.prompt == "":
|
self.image_processing = True
|
||||||
return rx.window_alert("Prompt Empty")
|
self.image_made = False
|
||||||
|
|
||||||
self.processing, self.complete = True, False
|
def get_image(self):
|
||||||
yield
|
"""运用 prompt 取得的参数产生图片"""
|
||||||
response = openai_client.images.generate(
|
response = openai.Image.create(prompt=self.prompt, n=1, size="1024x1024")
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
self.image_url = response["data"][0]["url"]
|
||||||
)
|
self.image_processing = False
|
||||||
self.image_url = response.data[0].url
|
self.image_made = True
|
||||||
self.processing, self.complete = False, True
|
|
||||||
```
|
```
|
||||||
|
|
||||||
在 State 中,我们定义了称为事件处理器(event handlers)的函数,用于改变状态变量(state vars).在Reflex中,事件处理器是我们可以修改状态的方式.它们可以作为对用户操作的响应而被调用,例如点击一个按钮或在文本框中输入.这些操作被称为事件.
|
在 State 中我们定义了事件处理进程来更改状态变量,事件处理进程是我们在 Reflex 中修改状态的方法,可以使用它们来回应用户操作,像是点击按钮或在文本框输入这些动作都是一种事件。
|
||||||
|
|
||||||
我们的DALL·E应用有一个事件处理器,名为 `get_image`,它用于从OpenAI API获取图像.在事件处理器中使用 `yield` 将导致UI进行更新.否则,UI将在事件处理器结束时进行更新.
|
我们的 DALL·E. 应用程序有两个事件处理进程 `process_image` 表示正在生成图片和 `get_image` 调用 OpenAI API。
|
||||||
|
|
||||||
### **Routing**
|
### **路由**
|
||||||
|
|
||||||
最后,定义我们的应用程序.
|
最后定义我们的应用程序并发送状态给它。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
app = rx.App()
|
app = rx.App(state=State)
|
||||||
```
|
```
|
||||||
|
|
||||||
我们添加从应用程序根目录到 index 组件的路由.我们还添加了一个在页面预览或浏览器标签中显示的标题.
|
添加从应用程序根目录到 index 组件的路由。 我们也添加了一个标题将会显示在 预览/浏览 分页。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
app.add_page(index, title="DALL-E")
|
app.add_page(index, title="Reflex:DALL-E")
|
||||||
|
app.compile()
|
||||||
```
|
```
|
||||||
|
|
||||||
您可以通过增加更多页面来创建一个多页面的应用.
|
你可以借由通过添加路由来增加更多页面。
|
||||||
|
|
||||||
## 📑 资源
|
## Reflex 状态
|
||||||
|
|
||||||
<div align="center">
|
Reflex 于 2022 年 12 月推出。
|
||||||
|
|
||||||
📑 [文档](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [日志](https://reflex.dev/blog) | 📱 [组件库](https://reflex.dev/docs/library) | 🖼️ [展览](https://reflex.dev/docs/gallery) | 🛸 [部署](https://reflex.dev/docs/hosting/deploy)
|
截至 2023 年 3 月,我们处于 **Public Beta** 阶段。
|
||||||
|
|
||||||
</div>
|
- :white_check_mark: **Public Alpha**: 任何人都可以安装与使用 Reflex,或许包含问题, 但我们正在积极的解决他们。
|
||||||
|
- :large_orange_diamond: **Public Beta**: 对于非软件产品来说足够稳定。
|
||||||
|
- **Public Hosting Beta**: _Optionally_, 部属跟托管你的 Reflex!
|
||||||
|
- **Public**: 这版本的 Reflex 是可用于软件产品的。
|
||||||
|
|
||||||
|
Reflex 每周都有新功能和发布新版本! 确保你按下 :star: 和 :eyes: watch 这个 repository 来确保知道最新信息。
|
||||||
## ✅ Reflex 的状态
|
|
||||||
|
|
||||||
Reflex 于 2022 年 12 月以Pynecone的名称推出.
|
|
||||||
|
|
||||||
截至2024年2月,我们的托管服务处于alpha测试阶段!在此期间,任何人都可以免费部署他们的应用程序。请查看我们的[路线图](https://github.com/reflex-dev/reflex/issues/2727)以了解我们的计划。
|
|
||||||
|
|
||||||
Reflex 每周都有新功能和发布新版本! 确保您按下 :star: 收藏和 :eyes: 关注 这个 仓库来确保知道最新信息.
|
|
||||||
|
|
||||||
## 贡献
|
## 贡献
|
||||||
|
|
||||||
我们欢迎任何大小的贡献,以下是几个好的方法来加入 Reflex 社群.
|
我们欢迎任何大小的贡献,以下是几个好的方法来加入 Reflex 社群。
|
||||||
|
|
||||||
- **加入我们的 Discord**: 我们的 [Discord](https://discord.gg/T5WSbC2YtQ) 是帮助您加入 Reflex 项目和讨论或贡献最棒的地方.
|
- **加入我们的 Discord**: 我们的 [Discord](https://discord.gg/T5WSbC2YtQ) 是帮助你加入 Reflex 项目和讨论或贡献最棒的地方。
|
||||||
- **GitHub Discussions**: 一个来讨论您想要添加的功能或是需要澄清的事情的好地方.
|
- **GitHub Discussions**: 一个来讨论你想要添加的功能或是需要澄清的事情的好地方。
|
||||||
- **GitHub Issues**: [报告错误](https://github.com/reflex-dev/reflex/issues)的绝佳地方,另外您可以试着解决一些 issue 和送出 PR.
|
- **GitHub Issues**: 报告错误的绝佳地方,另外你可以试着解决一些 issue 和送出 PR。
|
||||||
|
|
||||||
我们正在积极寻找贡献者,无关您的技能或经验水平.
|
我们正在积极寻找贡献者,无关你的技能或经验水平。
|
||||||
|
|
||||||
|
|
||||||
## 感谢我们所有的贡献者:
|
|
||||||
<a href="https://github.com/reflex-dev/reflex/graphs/contributors">
|
|
||||||
<img src="https://contrib.rocks/image?repo=reflex-dev/reflex" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
## 授权
|
## 授权
|
||||||
|
|
||||||
Reflex 是一个开源项目,使用 [Apache License 2.0](LICENSE) 授权.
|
Reflex 是一个开源项目且使用 [Apache License 2.0](LICENSE) 授权。
|
@ -11,32 +11,18 @@
|
|||||||
**✨ 使用 Python 建立高效且可自訂的網頁應用程式,幾秒鐘內即可部署。✨**
|
**✨ 使用 Python 建立高效且可自訂的網頁應用程式,幾秒鐘內即可部署。✨**
|
||||||
|
|
||||||
[](https://badge.fury.io/py/reflex)
|
[](https://badge.fury.io/py/reflex)
|
||||||
|

|
||||||

|

|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
[](https://reflex.dev/docs/getting-started/introduction)
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
[](https://discord.gg/T5WSbC2YtQ)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md)
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Reflex
|
|
||||||
|
|
||||||
Reflex 是一個可以用純 Python 構建全端網頁應用程式的函式庫。
|
|
||||||
|
|
||||||
主要特色:
|
|
||||||
|
|
||||||
* **純 Python** - 您可以用 Python 撰寫應用程式的前端和後端,無需學習 Javascript。
|
|
||||||
* **完全靈活性** - Reflex 易於上手,但也可以擴展到複雜的應用程式。
|
|
||||||
* **立即部署** - 構建後,只需使用[單一指令](https://reflex.dev/docs/hosting/deploy-quick-start/)即可部署您的應用程式,或在您自己的伺服器上託管。
|
|
||||||
請參閱我們的[架構頁面](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture)了解 Reflex 如何在底層運作。
|
|
||||||
|
|
||||||
## ⚙️ 安裝
|
## ⚙️ 安裝
|
||||||
|
|
||||||
開啟一個終端機並且執行 (需要 Python 3.10+):
|
開啟一個終端機並且執行 (需要 Python 3.7+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
@ -84,8 +70,7 @@ reflex run
|
|||||||
import reflex as rx
|
import reflex as rx
|
||||||
import openai
|
import openai
|
||||||
|
|
||||||
openai_client = openai.OpenAI()
|
openai.api_key = "YOUR_API_KEY"
|
||||||
|
|
||||||
|
|
||||||
class State(rx.State):
|
class State(rx.State):
|
||||||
"""應用程式狀態"""
|
"""應用程式狀態"""
|
||||||
@ -101,33 +86,33 @@ class State(rx.State):
|
|||||||
|
|
||||||
self.processing, self.complete = True, False
|
self.processing, self.complete = True, False
|
||||||
yield
|
yield
|
||||||
response = openai_client.images.generate(
|
response = openai.Image.create(prompt=self.prompt, n=1, size="1024x1024")
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
self.image_url = response["data"][0]["url"]
|
||||||
)
|
|
||||||
self.image_url = response.data[0].url
|
|
||||||
self.processing, self.complete = False, True
|
self.processing, self.complete = False, True
|
||||||
|
|
||||||
|
|
||||||
def index():
|
def index():
|
||||||
return rx.center(
|
return rx.center(
|
||||||
rx.vstack(
|
rx.vstack(
|
||||||
rx.heading("DALL-E", font_size="1.5em"),
|
rx.heading("DALL·E"),
|
||||||
rx.input(
|
rx.input(placeholder="Enter a prompt", on_blur=State.set_prompt),
|
||||||
placeholder="Enter a prompt..",
|
|
||||||
on_blur=State.set_prompt,
|
|
||||||
width="25em",
|
|
||||||
),
|
|
||||||
rx.button(
|
rx.button(
|
||||||
"Generate Image",
|
"Generate Image",
|
||||||
on_click=State.get_image,
|
on_click=State.get_image,
|
||||||
width="25em",
|
is_loading=State.processing,
|
||||||
loading=State.processing
|
width="100%",
|
||||||
),
|
),
|
||||||
rx.cond(
|
rx.cond(
|
||||||
State.complete,
|
State.complete,
|
||||||
rx.image(src=State.image_url, width="20em"),
|
rx.image(
|
||||||
|
src=State.image_url,
|
||||||
|
height="25em",
|
||||||
|
width="25em",
|
||||||
|
)
|
||||||
),
|
),
|
||||||
align="center",
|
padding="2em",
|
||||||
|
shadow="lg",
|
||||||
|
border_radius="lg",
|
||||||
),
|
),
|
||||||
width="100%",
|
width="100%",
|
||||||
height="100vh",
|
height="100vh",
|
||||||
@ -135,22 +120,11 @@ def index():
|
|||||||
|
|
||||||
# 把狀態跟頁面添加到應用程式。
|
# 把狀態跟頁面添加到應用程式。
|
||||||
app = rx.App()
|
app = rx.App()
|
||||||
app.add_page(index, title="Reflex:DALL-E")
|
app.add_page(index, title="reflex:DALL·E")
|
||||||
|
app.compile()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 讓我們來拆解一下。
|
## 讓我們來拆解一下。
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="../../images/dalle_colored_code_example.png" alt="解釋 DALL-E app 的前端和後端部分的區別。" width="900" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
### **Reflex 使用者介面**
|
### **Reflex 使用者介面**
|
||||||
|
|
||||||
讓我們從使用介面開始。
|
讓我們從使用介面開始。
|
||||||
@ -166,7 +140,7 @@ def index():
|
|||||||
|
|
||||||
我們用不同的元件像是 `center`, `vstack`, `input`, 和 `button` 來建立前端,元件之間可互相套入以建立出複雜的版面配置。並且您可使用關鍵字引數 *keyword args* 運行 CSS 全部功能來設計這些元件們的樣式。
|
我們用不同的元件像是 `center`, `vstack`, `input`, 和 `button` 來建立前端,元件之間可互相套入以建立出複雜的版面配置。並且您可使用關鍵字引數 *keyword args* 運行 CSS 全部功能來設計這些元件們的樣式。
|
||||||
|
|
||||||
Reflex 擁有 [60+ 內建元件](https://reflex.dev/docs/library) 來幫助你開始建立應用程式。我們正積極添加元件,你也可以簡單地 [創建自己所屬的元件](https://reflex.dev/docs/wrapping-react/overview/)。
|
Reflex 擁有 [60+ 內建元件](https://reflex.dev/docs/library) 來幫助你開始建立應用程式。我們正積極添加元件,你也可以簡單地 [創建自己所屬的元件](https://reflex.dev/docs/advanced-guide/wrapping-react)。
|
||||||
|
|
||||||
### **應用程式狀態**
|
### **應用程式狀態**
|
||||||
|
|
||||||
@ -177,9 +151,8 @@ class State(rx.State):
|
|||||||
"""應用程式狀態"""
|
"""應用程式狀態"""
|
||||||
prompt = ""
|
prompt = ""
|
||||||
image_url = ""
|
image_url = ""
|
||||||
processing = False
|
image_processing = False
|
||||||
complete = False
|
image_made = False
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
應用程式狀態定義了應用程式中所有可以更改的變數及變更他們的函式 (稱為 vars)。
|
應用程式狀態定義了應用程式中所有可以更改的變數及變更他們的函式 (稱為 vars)。
|
||||||
@ -196,10 +169,8 @@ def get_image(self):
|
|||||||
|
|
||||||
self.processing, self.complete = True, False
|
self.processing, self.complete = True, False
|
||||||
yield
|
yield
|
||||||
response = openai_client.images.generate(
|
response = openai.Image.create(prompt=self.prompt, n=1, size="1024x1024")
|
||||||
prompt=self.prompt, n=1, size="1024x1024"
|
self.image_url = response["data"][0]["url"]
|
||||||
)
|
|
||||||
self.image_url = response.data[0].url
|
|
||||||
self.processing, self.complete = False, True
|
self.processing, self.complete = False, True
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -221,6 +192,7 @@ app = rx.App()
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
app.add_page(index, title="DALL-E")
|
app.add_page(index, title="DALL-E")
|
||||||
|
app.compile()
|
||||||
```
|
```
|
||||||
|
|
||||||
你可以添加更多頁面至路由藉此來建立多頁面應用程式(multi-page app)
|
你可以添加更多頁面至路由藉此來建立多頁面應用程式(multi-page app)
|
||||||
@ -229,35 +201,34 @@ app.add_page(index, title="DALL-E")
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Component Library](https://reflex.dev/docs/library) | 🖼️ [Templates](https://reflex.dev/templates/) | 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start)
|
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Component Library](https://reflex.dev/docs/library) | 🖼️ [Gallery](https://reflex.dev/docs/gallery) | 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## ✅ 產品狀態
|
## ✅ Reflex 狀態
|
||||||
|
|
||||||
Reflex 在 2022 年 12 月以 Pynecone 的名字推出。
|
Reflex 於 2022 年 12 月推出,當時名為 Pynecone。
|
||||||
|
|
||||||
截至 2024 年 2 月,我們的託管服務已進入 alpha 階段!在此期間,任何人都可以免費部署他們的應用程式。請參閱我們的[產品地圖](https://github.com/reflex-dev/reflex/issues/2727)了解未來的計劃。
|
截至 2023 年 7 月,我們處於 **Public Beta** 階段。
|
||||||
|
|
||||||
|
- :white_check_mark: **Public Alpha**: 任何人都可以安裝與使用 Reflex,或許包含問題, 但我們正在積極的解決他們。
|
||||||
|
- :large_orange_diamond: **Public Beta**: 對於不涉及商業目的使用情境來說足夠穩定。
|
||||||
|
- **Public Hosting Beta**: _Optionally_, 部屬跟託管你的 Reflex!
|
||||||
|
- **Public**: 這版本的 Reflex 是可用於軟體產品的。
|
||||||
|
|
||||||
Reflex 每周都有新功能和釋出新版本! 確保你按下 :star: 和 :eyes: watch 這個 repository 來確保知道最新資訊。
|
Reflex 每周都有新功能和釋出新版本! 確保你按下 :star: 和 :eyes: watch 這個 repository 來確保知道最新資訊。
|
||||||
|
|
||||||
## 貢獻
|
## 貢獻
|
||||||
|
|
||||||
我們歡迎任何大小的貢獻,以下是一些加入 Reflex 社群的好方法。
|
我們歡迎任何大小的貢獻,以下是幾個好的方法來加入 Reflex 社群。
|
||||||
|
|
||||||
- **加入我們的 Discord**: 我們的 [Discord](https://discord.gg/T5WSbC2YtQ) 是獲取 Reflex 專案幫助和討論如何貢獻的最佳地方。
|
- **加入我們的 Discord**: 我們的 [Discord](https://discord.gg/T5WSbC2YtQ) 是幫助你加入 Reflex 專案和討論或貢獻最棒的地方。
|
||||||
- **GitHub Discussions**: 這是一個討論您想新增的功能或對於一些困惑/需要澄清事項的好方法。
|
- **GitHub Discussions**: 一個來討論你想要添加的功能或是需要澄清的事情的好地方。
|
||||||
- **GitHub Issues**: 在 [Issues](https://github.com/reflex-dev/reflex/issues) 頁面報告錯誤是一個絕佳的方式。此外,您也可以嘗試解決現有 Issue 並提交 PR。
|
- **GitHub Issues**: 報告錯誤的絕佳地方,另外你可以試著解決一些 issue 和送出 PR。
|
||||||
|
|
||||||
我們積極尋找貢獻者,不論您的技能水平或經驗如何。要貢獻,請查看 [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
|
我們正在積極尋找貢獻者,無關你的技能水平或經驗。
|
||||||
|
|
||||||
|
|
||||||
## 感謝所有貢獻者:
|
|
||||||
<a href="https://github.com/reflex-dev/reflex/graphs/contributors">
|
|
||||||
<img src="https://contrib.rocks/image?repo=reflex-dev/reflex" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
## 授權
|
## 授權
|
||||||
|
|
||||||
|
59
integration/conftest.py
Normal file
59
integration/conftest.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
"""Shared conftest for all integration tests."""
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
DISPLAY = None
|
||||||
|
XVFB_DIMENSIONS = (800, 600)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def xvfb():
|
||||||
|
"""Create virtual X display.
|
||||||
|
|
||||||
|
This function is a no-op unless GITHUB_ACTIONS is set in the environment.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
the pyvirtualdisplay object that the browser will be open on
|
||||||
|
"""
|
||||||
|
if os.environ.get("GITHUB_ACTIONS"):
|
||||||
|
from pyvirtualdisplay.smartdisplay import ( # pyright: ignore [reportMissingImports]
|
||||||
|
SmartDisplay,
|
||||||
|
)
|
||||||
|
|
||||||
|
global DISPLAY
|
||||||
|
with SmartDisplay(visible=0, size=XVFB_DIMENSIONS) as DISPLAY:
|
||||||
|
yield DISPLAY
|
||||||
|
DISPLAY = None
|
||||||
|
else:
|
||||||
|
yield None
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_exception_interact(node, call, report):
|
||||||
|
"""Take and upload screenshot when tests fail.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node: The pytest item that failed.
|
||||||
|
call: The pytest call describing when/where the test was invoked.
|
||||||
|
report: The pytest log report object.
|
||||||
|
"""
|
||||||
|
screenshot_dir = os.environ.get("SCREENSHOT_DIR")
|
||||||
|
if DISPLAY is None or screenshot_dir is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
screenshot_dir = Path(screenshot_dir)
|
||||||
|
screenshot_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
safe_filename = re.sub(
|
||||||
|
r"(?u)[^-\w.]",
|
||||||
|
"_",
|
||||||
|
str(node.nodeid).strip().replace(" ", "_").replace(":", "_"),
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
DISPLAY.waitgrab().save(
|
||||||
|
(Path(screenshot_dir) / safe_filename).with_suffix(".png"),
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to take screenshot for {node}: {e}")
|
21
integration/init-test/Dockerfile
Normal file
21
integration/init-test/Dockerfile
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
FROM ubuntu:latest
|
||||||
|
|
||||||
|
ARG USERNAME=kerrigan
|
||||||
|
ARG USER_UID=1000
|
||||||
|
ARG USER_GID=$USER_UID
|
||||||
|
|
||||||
|
RUN groupadd --gid $USER_GID $USERNAME \
|
||||||
|
&& useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \
|
||||||
|
#
|
||||||
|
# [Optional] Add sudo support. Omit if you don't need to install software after connecting.
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y sudo curl xz-utils python3 python3-pip python3.10-venv unzip \
|
||||||
|
&& echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
|
||||||
|
&& chmod 0440 /etc/sudoers.d/$USERNAME
|
||||||
|
|
||||||
|
USER $USERNAME
|
||||||
|
|
||||||
|
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||||
|
RUN sudo ln -s /home/$USERNAME/.local/bin/poetry /usr/local/bin/poetry
|
||||||
|
|
||||||
|
WORKDIR /home/$USERNAME
|
14
integration/init-test/in_docker_test_script.sh
Executable file
14
integration/init-test/in_docker_test_script.sh
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
echo "Preparing test project dir"
|
||||||
|
mkdir hello
|
||||||
|
python3 -m venv ~/hello/venv
|
||||||
|
source ~/hello/venv/bin/activate
|
||||||
|
|
||||||
|
echo "Installing reflex from local repo code"
|
||||||
|
cd /reflex-repo
|
||||||
|
poetry install
|
||||||
|
echo "Running reflex init in test project dir"
|
||||||
|
poetry run /bin/bash -c "cd ~/hello && reflex init && rm -rf ~/.reflex .web && reflex export --backend-only"
|
190
integration/test_dynamic_routes.py
Normal file
190
integration/test_dynamic_routes.py
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
"""Integration tests for dynamic route page behavior."""
|
||||||
|
import time
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from typing import Generator
|
||||||
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
|
||||||
|
from reflex.testing import AppHarness
|
||||||
|
|
||||||
|
|
||||||
|
def DynamicRoute():
|
||||||
|
"""App for testing dynamic routes."""
|
||||||
|
import reflex as rx
|
||||||
|
|
||||||
|
class DynamicState(rx.State):
|
||||||
|
order: list[str] = []
|
||||||
|
page_id: str = ""
|
||||||
|
|
||||||
|
def on_load(self):
|
||||||
|
self.order.append(self.page_id or "no page id")
|
||||||
|
|
||||||
|
@rx.var
|
||||||
|
def next_page(self) -> str:
|
||||||
|
try:
|
||||||
|
return str(int(self.page_id) + 1)
|
||||||
|
except ValueError:
|
||||||
|
return "0"
|
||||||
|
|
||||||
|
@rx.var
|
||||||
|
def token(self) -> str:
|
||||||
|
return self.get_token()
|
||||||
|
|
||||||
|
def index():
|
||||||
|
return rx.fragment(
|
||||||
|
rx.input(value=DynamicState.token, is_read_only=True, id="token"),
|
||||||
|
rx.input(value=DynamicState.page_id, is_read_only=True, id="page_id"),
|
||||||
|
rx.link("index", href="/", id="link_index"),
|
||||||
|
rx.link("page_X", href="/static/x", id="link_page_x"),
|
||||||
|
rx.link(
|
||||||
|
"next", href="/page/" + DynamicState.next_page, id="link_page_next" # type: ignore
|
||||||
|
),
|
||||||
|
rx.list(
|
||||||
|
rx.foreach(DynamicState.order, lambda i: rx.list_item(rx.text(i))), # type: ignore
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
app = rx.App(state=DynamicState)
|
||||||
|
app.add_page(index)
|
||||||
|
app.add_page(index, route="/page/[page_id]", on_load=DynamicState.on_load) # type: ignore
|
||||||
|
app.add_page(index, route="/static/x", on_load=DynamicState.on_load) # type: ignore
|
||||||
|
app.compile()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def dynamic_route(tmp_path_factory) -> Generator[AppHarness, None, None]:
|
||||||
|
"""Start DynamicRoute app at tmp_path via AppHarness.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tmp_path_factory: pytest tmp_path_factory fixture
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
running AppHarness instance
|
||||||
|
"""
|
||||||
|
with AppHarness.create(
|
||||||
|
root=tmp_path_factory.mktemp("dynamic_route"),
|
||||||
|
app_source=DynamicRoute, # type: ignore
|
||||||
|
) as harness:
|
||||||
|
yield harness
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def driver(dynamic_route: AppHarness):
|
||||||
|
"""Get an instance of the browser open to the dynamic_route app.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dynamic_route: harness for DynamicRoute app
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
WebDriver instance.
|
||||||
|
"""
|
||||||
|
assert dynamic_route.app_instance is not None, "app is not running"
|
||||||
|
driver = dynamic_route.frontend()
|
||||||
|
try:
|
||||||
|
assert dynamic_route.poll_for_clients()
|
||||||
|
yield driver
|
||||||
|
finally:
|
||||||
|
driver.quit()
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def poll_for_navigation(driver, timeout: int = 5) -> Generator[None, None, None]:
|
||||||
|
"""Wait for driver url to change.
|
||||||
|
|
||||||
|
Use as a contextmanager, and apply the navigation event inside the context
|
||||||
|
block, polling will occur after the context block exits.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
driver: WebDriver instance.
|
||||||
|
timeout: Time to wait for url to change.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
prev_url = driver.current_url
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
AppHarness._poll_for(lambda: prev_url != driver.current_url, timeout=timeout)
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_load_navigate(dynamic_route: AppHarness, driver):
|
||||||
|
"""Click links to navigate between dynamic pages with on_load event.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dynamic_route: harness for DynamicRoute app.
|
||||||
|
driver: WebDriver instance.
|
||||||
|
"""
|
||||||
|
assert dynamic_route.app_instance is not None
|
||||||
|
token_input = driver.find_element(By.ID, "token")
|
||||||
|
link = driver.find_element(By.ID, "link_page_next")
|
||||||
|
assert token_input
|
||||||
|
assert link
|
||||||
|
|
||||||
|
# wait for the backend connection to send the token
|
||||||
|
token = dynamic_route.poll_for_value(token_input)
|
||||||
|
assert token is not None
|
||||||
|
|
||||||
|
# click the link a few times
|
||||||
|
for ix in range(10):
|
||||||
|
# wait for navigation, then assert on url
|
||||||
|
with poll_for_navigation(driver):
|
||||||
|
link.click()
|
||||||
|
assert urlsplit(driver.current_url).path == f"/page/{ix}/"
|
||||||
|
|
||||||
|
link = driver.find_element(By.ID, "link_page_next")
|
||||||
|
page_id_input = driver.find_element(By.ID, "page_id")
|
||||||
|
|
||||||
|
assert link
|
||||||
|
assert page_id_input
|
||||||
|
|
||||||
|
assert dynamic_route.poll_for_value(page_id_input) == str(ix)
|
||||||
|
|
||||||
|
# look up the backend state and assert that `on_load` was called for all
|
||||||
|
# navigation events
|
||||||
|
backend_state = dynamic_route.app_instance.state_manager.states[token]
|
||||||
|
time.sleep(0.2)
|
||||||
|
assert backend_state.order == [str(ix) for ix in range(10)]
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_load_navigate_non_dynamic(dynamic_route: AppHarness, driver):
|
||||||
|
"""Click links to navigate between static pages with on_load event.
|
||||||
|
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dynamic_route: harness for DynamicRoute app.
|
||||||
|
driver: WebDriver instance.
|
||||||
|
"""
|
||||||
|
assert dynamic_route.app_instance is not None
|
||||||
|
token_input = driver.find_element(By.ID, "token")
|
||||||
|
link = driver.find_element(By.ID, "link_page_x")
|
||||||
|
assert token_input
|
||||||
|
assert link
|
||||||
|
|
||||||
|
# wait for the backend connection to send the token
|
||||||
|
token = dynamic_route.poll_for_value(token_input)
|
||||||
|
assert token is not None
|
||||||
|
|
||||||
|
with poll_for_navigation(driver):
|
||||||
|
link.click()
|
||||||
|
assert urlsplit(driver.current_url).path == "/static/x/"
|
||||||
|
|
||||||
|
# look up the backend state and assert that `on_load` was called once
|
||||||
|
backend_state = dynamic_route.app_instance.state_manager.states[token]
|
||||||
|
time.sleep(0.2)
|
||||||
|
assert backend_state.order == ["no page id"]
|
||||||
|
|
||||||
|
# go back to the index and navigate back to the static route
|
||||||
|
link = driver.find_element(By.ID, "link_index")
|
||||||
|
with poll_for_navigation(driver):
|
||||||
|
link.click()
|
||||||
|
assert urlsplit(driver.current_url).path == "/"
|
||||||
|
|
||||||
|
link = driver.find_element(By.ID, "link_page_x")
|
||||||
|
with poll_for_navigation(driver):
|
||||||
|
link.click()
|
||||||
|
assert urlsplit(driver.current_url).path == "/static/x/"
|
||||||
|
time.sleep(0.2)
|
||||||
|
assert backend_state.order == ["no page id", "no page id"]
|
332
integration/test_event_chain.py
Normal file
332
integration/test_event_chain.py
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
"""Ensure that Event Chains are properly queued and handled between frontend and backend."""
|
||||||
|
|
||||||
|
import time
|
||||||
|
from typing import Generator
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
|
||||||
|
from reflex.testing import AppHarness
|
||||||
|
|
||||||
|
MANY_EVENTS = 50
|
||||||
|
|
||||||
|
|
||||||
|
def EventChain():
|
||||||
|
"""App with chained event handlers."""
|
||||||
|
import reflex as rx
|
||||||
|
|
||||||
|
# repeated here since the outer global isn't exported into the App module
|
||||||
|
MANY_EVENTS = 50
|
||||||
|
|
||||||
|
class State(rx.State):
|
||||||
|
event_order: list[str] = []
|
||||||
|
|
||||||
|
@rx.var
|
||||||
|
def token(self) -> str:
|
||||||
|
return self.get_token()
|
||||||
|
|
||||||
|
def event_no_args(self):
|
||||||
|
self.event_order.append("event_no_args")
|
||||||
|
|
||||||
|
def event_arg(self, arg):
|
||||||
|
self.event_order.append(f"event_arg:{arg}")
|
||||||
|
|
||||||
|
def event_nested_1(self):
|
||||||
|
self.event_order.append("event_nested_1")
|
||||||
|
yield State.event_nested_2
|
||||||
|
yield State.event_arg("nested_1") # type: ignore
|
||||||
|
|
||||||
|
def event_nested_2(self):
|
||||||
|
self.event_order.append("event_nested_2")
|
||||||
|
yield State.event_nested_3
|
||||||
|
yield rx.console_log("event_nested_2")
|
||||||
|
yield State.event_arg("nested_2") # type: ignore
|
||||||
|
|
||||||
|
def event_nested_3(self):
|
||||||
|
self.event_order.append("event_nested_3")
|
||||||
|
yield State.event_no_args
|
||||||
|
yield State.event_arg("nested_3") # type: ignore
|
||||||
|
|
||||||
|
def on_load_return_chain(self):
|
||||||
|
self.event_order.append("on_load_return_chain")
|
||||||
|
return [State.event_arg(1), State.event_arg(2), State.event_arg(3)] # type: ignore
|
||||||
|
|
||||||
|
def on_load_yield_chain(self):
|
||||||
|
self.event_order.append("on_load_yield_chain")
|
||||||
|
yield State.event_arg(4) # type: ignore
|
||||||
|
yield State.event_arg(5) # type: ignore
|
||||||
|
yield State.event_arg(6) # type: ignore
|
||||||
|
|
||||||
|
def click_return_event(self):
|
||||||
|
self.event_order.append("click_return_event")
|
||||||
|
return State.event_no_args
|
||||||
|
|
||||||
|
def click_return_events(self):
|
||||||
|
self.event_order.append("click_return_events")
|
||||||
|
return [
|
||||||
|
State.event_arg(7), # type: ignore
|
||||||
|
rx.console_log("click_return_events"),
|
||||||
|
State.event_arg(8), # type: ignore
|
||||||
|
State.event_arg(9), # type: ignore
|
||||||
|
]
|
||||||
|
|
||||||
|
def click_yield_chain(self):
|
||||||
|
self.event_order.append("click_yield_chain:0")
|
||||||
|
yield State.event_arg(10) # type: ignore
|
||||||
|
self.event_order.append("click_yield_chain:1")
|
||||||
|
yield rx.console_log("click_yield_chain")
|
||||||
|
yield State.event_arg(11) # type: ignore
|
||||||
|
self.event_order.append("click_yield_chain:2")
|
||||||
|
yield State.event_arg(12) # type: ignore
|
||||||
|
self.event_order.append("click_yield_chain:3")
|
||||||
|
|
||||||
|
def click_yield_many_events(self):
|
||||||
|
self.event_order.append("click_yield_many_events")
|
||||||
|
for ix in range(MANY_EVENTS):
|
||||||
|
yield State.event_arg(ix) # type: ignore
|
||||||
|
yield rx.console_log(f"many_events_{ix}")
|
||||||
|
self.event_order.append("click_yield_many_events_done")
|
||||||
|
|
||||||
|
def click_yield_nested(self):
|
||||||
|
self.event_order.append("click_yield_nested")
|
||||||
|
yield State.event_nested_1
|
||||||
|
yield State.event_arg("yield_nested") # type: ignore
|
||||||
|
|
||||||
|
def redirect_return_chain(self):
|
||||||
|
self.event_order.append("redirect_return_chain")
|
||||||
|
yield rx.redirect("/on-load-return-chain")
|
||||||
|
|
||||||
|
def redirect_yield_chain(self):
|
||||||
|
self.event_order.append("redirect_yield_chain")
|
||||||
|
yield rx.redirect("/on-load-yield-chain")
|
||||||
|
|
||||||
|
app = rx.App(state=State)
|
||||||
|
|
||||||
|
@app.add_page
|
||||||
|
def index():
|
||||||
|
return rx.fragment(
|
||||||
|
rx.input(value=State.token, readonly=True, id="token"),
|
||||||
|
rx.button(
|
||||||
|
"Return Event",
|
||||||
|
id="return_event",
|
||||||
|
on_click=State.click_return_event,
|
||||||
|
),
|
||||||
|
rx.button(
|
||||||
|
"Return Events",
|
||||||
|
id="return_events",
|
||||||
|
on_click=State.click_return_events,
|
||||||
|
),
|
||||||
|
rx.button(
|
||||||
|
"Yield Chain",
|
||||||
|
id="yield_chain",
|
||||||
|
on_click=State.click_yield_chain,
|
||||||
|
),
|
||||||
|
rx.button(
|
||||||
|
"Yield Many events",
|
||||||
|
id="yield_many_events",
|
||||||
|
on_click=State.click_yield_many_events,
|
||||||
|
),
|
||||||
|
rx.button(
|
||||||
|
"Yield Nested",
|
||||||
|
id="yield_nested",
|
||||||
|
on_click=State.click_yield_nested,
|
||||||
|
),
|
||||||
|
rx.button(
|
||||||
|
"Redirect Yield Chain",
|
||||||
|
id="redirect_yield_chain",
|
||||||
|
on_click=State.redirect_yield_chain,
|
||||||
|
),
|
||||||
|
rx.button(
|
||||||
|
"Redirect Return Chain",
|
||||||
|
id="redirect_return_chain",
|
||||||
|
on_click=State.redirect_return_chain,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_load_return_chain():
|
||||||
|
return rx.fragment(
|
||||||
|
rx.text("return"),
|
||||||
|
rx.input(value=State.token, readonly=True, id="token"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_load_yield_chain():
|
||||||
|
return rx.fragment(
|
||||||
|
rx.text("yield"),
|
||||||
|
rx.input(value=State.token, readonly=True, id="token"),
|
||||||
|
)
|
||||||
|
|
||||||
|
app.add_page(on_load_return_chain, on_load=State.on_load_return_chain) # type: ignore
|
||||||
|
app.add_page(on_load_yield_chain, on_load=State.on_load_yield_chain) # type: ignore
|
||||||
|
|
||||||
|
app.compile()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def event_chain(tmp_path_factory) -> Generator[AppHarness, None, None]:
|
||||||
|
"""Start EventChain app at tmp_path via AppHarness.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tmp_path_factory: pytest tmp_path_factory fixture
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
running AppHarness instance
|
||||||
|
"""
|
||||||
|
with AppHarness.create(
|
||||||
|
root=tmp_path_factory.mktemp("event_chain"),
|
||||||
|
app_source=EventChain, # type: ignore
|
||||||
|
) as harness:
|
||||||
|
yield harness
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def driver(event_chain: AppHarness):
|
||||||
|
"""Get an instance of the browser open to the event_chain app.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event_chain: harness for EventChain app
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
WebDriver instance.
|
||||||
|
"""
|
||||||
|
assert event_chain.app_instance is not None, "app is not running"
|
||||||
|
driver = event_chain.frontend()
|
||||||
|
try:
|
||||||
|
assert event_chain.poll_for_clients()
|
||||||
|
yield driver
|
||||||
|
finally:
|
||||||
|
driver.quit()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("button_id", "exp_event_order"),
|
||||||
|
[
|
||||||
|
("return_event", ["click_return_event", "event_no_args"]),
|
||||||
|
(
|
||||||
|
"return_events",
|
||||||
|
["click_return_events", "event_arg:7", "event_arg:8", "event_arg:9"],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"yield_chain",
|
||||||
|
[
|
||||||
|
"click_yield_chain:0",
|
||||||
|
"click_yield_chain:1",
|
||||||
|
"click_yield_chain:2",
|
||||||
|
"click_yield_chain:3",
|
||||||
|
"event_arg:10",
|
||||||
|
"event_arg:11",
|
||||||
|
"event_arg:12",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"yield_many_events",
|
||||||
|
[
|
||||||
|
"click_yield_many_events",
|
||||||
|
"click_yield_many_events_done",
|
||||||
|
*[f"event_arg:{ix}" for ix in range(MANY_EVENTS)],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"yield_nested",
|
||||||
|
[
|
||||||
|
"click_yield_nested",
|
||||||
|
"event_nested_1",
|
||||||
|
"event_arg:yield_nested",
|
||||||
|
"event_nested_2",
|
||||||
|
"event_arg:nested_1",
|
||||||
|
"event_nested_3",
|
||||||
|
"event_arg:nested_2",
|
||||||
|
"event_no_args",
|
||||||
|
"event_arg:nested_3",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"redirect_return_chain",
|
||||||
|
[
|
||||||
|
"redirect_return_chain",
|
||||||
|
"on_load_return_chain",
|
||||||
|
"event_arg:1",
|
||||||
|
"event_arg:2",
|
||||||
|
"event_arg:3",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"redirect_yield_chain",
|
||||||
|
[
|
||||||
|
"redirect_yield_chain",
|
||||||
|
"on_load_yield_chain",
|
||||||
|
"event_arg:4",
|
||||||
|
"event_arg:5",
|
||||||
|
"event_arg:6",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_event_chain_click(event_chain, driver, button_id, exp_event_order):
|
||||||
|
"""Click the button, assert that the events are handled in the correct order.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event_chain: AppHarness for the event_chain app
|
||||||
|
driver: selenium WebDriver open to the app
|
||||||
|
button_id: the ID of the button to click
|
||||||
|
exp_event_order: the expected events recorded in the State
|
||||||
|
"""
|
||||||
|
token_input = driver.find_element(By.ID, "token")
|
||||||
|
btn = driver.find_element(By.ID, button_id)
|
||||||
|
assert token_input
|
||||||
|
assert btn
|
||||||
|
|
||||||
|
token = event_chain.poll_for_value(token_input)
|
||||||
|
|
||||||
|
btn.click()
|
||||||
|
if "redirect" in button_id:
|
||||||
|
# wait a bit longer if we're redirecting
|
||||||
|
time.sleep(1)
|
||||||
|
if "many_events" in button_id:
|
||||||
|
# wait a bit longer if we have loads of events
|
||||||
|
time.sleep(1)
|
||||||
|
time.sleep(0.5)
|
||||||
|
backend_state = event_chain.app_instance.state_manager.states[token]
|
||||||
|
assert backend_state.event_order == exp_event_order
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("uri", "exp_event_order"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"/on-load-return-chain",
|
||||||
|
[
|
||||||
|
"on_load_return_chain",
|
||||||
|
"event_arg:1",
|
||||||
|
"event_arg:2",
|
||||||
|
"event_arg:3",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/on-load-yield-chain",
|
||||||
|
[
|
||||||
|
"on_load_yield_chain",
|
||||||
|
"event_arg:4",
|
||||||
|
"event_arg:5",
|
||||||
|
"event_arg:6",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_event_chain_on_load(event_chain, driver, uri, exp_event_order):
|
||||||
|
"""Load the URI, assert that the events are handled in the correct order.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event_chain: AppHarness for the event_chain app
|
||||||
|
driver: selenium WebDriver open to the app
|
||||||
|
uri: the page to load
|
||||||
|
exp_event_order: the expected events recorded in the State
|
||||||
|
"""
|
||||||
|
driver.get(event_chain.frontend_url + uri)
|
||||||
|
token_input = driver.find_element(By.ID, "token")
|
||||||
|
assert token_input
|
||||||
|
|
||||||
|
token = event_chain.poll_for_value(token_input)
|
||||||
|
|
||||||
|
time.sleep(0.5)
|
||||||
|
backend_state = event_chain.app_instance.state_manager.states[token]
|
||||||
|
assert backend_state.event_order == exp_event_order
|
141
integration/test_form_submit.py
Normal file
141
integration/test_form_submit.py
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
"""Integration tests for forms."""
|
||||||
|
import time
|
||||||
|
from typing import Generator
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.common.keys import Keys
|
||||||
|
|
||||||
|
from reflex.testing import AppHarness
|
||||||
|
|
||||||
|
|
||||||
|
def FormSubmit():
|
||||||
|
"""App with a form using on_submit."""
|
||||||
|
import reflex as rx
|
||||||
|
|
||||||
|
class FormState(rx.State):
|
||||||
|
form_data: dict = {}
|
||||||
|
|
||||||
|
def form_submit(self, form_data: dict):
|
||||||
|
self.form_data = form_data
|
||||||
|
|
||||||
|
app = rx.App(state=FormState)
|
||||||
|
|
||||||
|
@app.add_page
|
||||||
|
def index():
|
||||||
|
return rx.vstack(
|
||||||
|
rx.form(
|
||||||
|
rx.vstack(
|
||||||
|
rx.input(id="name_input"),
|
||||||
|
rx.hstack(rx.pin_input(length=4, id="pin_input")),
|
||||||
|
rx.number_input(id="number_input"),
|
||||||
|
rx.checkbox(id="bool_input"),
|
||||||
|
rx.switch(id="bool_input2"),
|
||||||
|
rx.slider(id="slider_input"),
|
||||||
|
rx.range_slider(id="range_input"),
|
||||||
|
rx.radio_group(["option1", "option2"], id="radio_input"),
|
||||||
|
rx.select(["option1", "option2"], id="select_input"),
|
||||||
|
rx.text_area(id="text_area_input"),
|
||||||
|
rx.button("Submit", type_="submit"),
|
||||||
|
),
|
||||||
|
on_submit=FormState.form_submit,
|
||||||
|
),
|
||||||
|
rx.spacer(),
|
||||||
|
height="100vh",
|
||||||
|
)
|
||||||
|
|
||||||
|
app.compile()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def form_submit(tmp_path_factory) -> Generator[AppHarness, None, None]:
|
||||||
|
"""Start FormSubmit app at tmp_path via AppHarness.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tmp_path_factory: pytest tmp_path_factory fixture
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
running AppHarness instance
|
||||||
|
"""
|
||||||
|
with AppHarness.create(
|
||||||
|
root=tmp_path_factory.mktemp("form_submit"),
|
||||||
|
app_source=FormSubmit, # type: ignore
|
||||||
|
) as harness:
|
||||||
|
assert harness.app_instance is not None, "app is not running"
|
||||||
|
yield harness
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def driver(form_submit: AppHarness):
|
||||||
|
"""GEt an instance of the browser open to the form_submit app.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
form_submit: harness for ServerSideEvent app
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
WebDriver instance.
|
||||||
|
"""
|
||||||
|
driver = form_submit.frontend()
|
||||||
|
try:
|
||||||
|
assert form_submit.poll_for_clients()
|
||||||
|
yield driver
|
||||||
|
finally:
|
||||||
|
driver.quit()
|
||||||
|
|
||||||
|
|
||||||
|
def test_submit(driver, form_submit: AppHarness):
|
||||||
|
"""Fill a form with various different output, submit it to backend and verify
|
||||||
|
the output.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
driver: selenium WebDriver open to the app
|
||||||
|
form_submit: harness for FormSubmit app
|
||||||
|
"""
|
||||||
|
assert form_submit.app_instance is not None, "app is not running"
|
||||||
|
_, backend_state = list(form_submit.app_instance.state_manager.states.items())[0]
|
||||||
|
|
||||||
|
name_input = driver.find_element(By.ID, "name_input")
|
||||||
|
name_input.send_keys("foo")
|
||||||
|
|
||||||
|
pin_inputs = driver.find_elements(By.CLASS_NAME, "chakra-pin-input")
|
||||||
|
pin_values = ["8", "1", "6", "4"]
|
||||||
|
for i, pin_input in enumerate(pin_inputs):
|
||||||
|
pin_input.send_keys(pin_values[i])
|
||||||
|
|
||||||
|
number_input = driver.find_element(By.CLASS_NAME, "chakra-numberinput")
|
||||||
|
buttons = number_input.find_elements(By.XPATH, "//div[@role='button']")
|
||||||
|
for _ in range(3):
|
||||||
|
buttons[1].click()
|
||||||
|
|
||||||
|
checkbox_input = driver.find_element(By.CLASS_NAME, "chakra-checkbox__control")
|
||||||
|
checkbox_input.click()
|
||||||
|
|
||||||
|
switch_input = driver.find_element(By.CLASS_NAME, "chakra-switch__track")
|
||||||
|
switch_input.click()
|
||||||
|
|
||||||
|
radio_buttons = driver.find_elements(By.CLASS_NAME, "chakra-radio__control")
|
||||||
|
radio_buttons[1].click()
|
||||||
|
|
||||||
|
textarea_input = driver.find_element(By.CLASS_NAME, "chakra-textarea")
|
||||||
|
textarea_input.send_keys("Some", Keys.ENTER, "Text")
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
submit_input = driver.find_element(By.CLASS_NAME, "chakra-button")
|
||||||
|
submit_input.click()
|
||||||
|
|
||||||
|
# wait for the form data to arrive at the backend
|
||||||
|
AppHarness._poll_for(
|
||||||
|
lambda: backend_state.form_data != {},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert backend_state.form_data["name_input"] == "foo"
|
||||||
|
assert backend_state.form_data["pin_input"] == pin_values
|
||||||
|
assert backend_state.form_data["number_input"] == "-3"
|
||||||
|
assert backend_state.form_data["bool_input"] is True
|
||||||
|
assert backend_state.form_data["bool_input2"] is True
|
||||||
|
assert backend_state.form_data["slider_input"] == "50"
|
||||||
|
assert backend_state.form_data["range_input"] == ["25", "75"]
|
||||||
|
assert backend_state.form_data["radio_input"] == "option2"
|
||||||
|
assert backend_state.form_data["select_input"] == "option1"
|
||||||
|
assert backend_state.form_data["text_area_input"] == "Some\nText"
|
119
integration/test_input.py
Normal file
119
integration/test_input.py
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
"""Integration tests for text input and related components."""
|
||||||
|
import time
|
||||||
|
from typing import Generator
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.common.keys import Keys
|
||||||
|
|
||||||
|
from reflex.testing import AppHarness
|
||||||
|
|
||||||
|
|
||||||
|
def FullyControlledInput():
|
||||||
|
"""App using a fully controlled input with implicit debounce wrapper."""
|
||||||
|
import reflex as rx
|
||||||
|
|
||||||
|
class State(rx.State):
|
||||||
|
text: str = "initial"
|
||||||
|
|
||||||
|
app = rx.App(state=State)
|
||||||
|
|
||||||
|
@app.add_page
|
||||||
|
def index():
|
||||||
|
return rx.fragment(
|
||||||
|
rx.input(
|
||||||
|
id="debounce_input_input",
|
||||||
|
on_change=State.set_text, # type: ignore
|
||||||
|
value=State.text,
|
||||||
|
),
|
||||||
|
rx.input(value=State.text, id="value_input"),
|
||||||
|
rx.input(on_change=State.set_text, id="on_change_input"), # type: ignore
|
||||||
|
rx.button("CLEAR", on_click=rx.set_value("on_change_input", "")),
|
||||||
|
)
|
||||||
|
|
||||||
|
app.compile()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def fully_controlled_input(tmp_path) -> Generator[AppHarness, None, None]:
|
||||||
|
"""Start FullyControlledInput app at tmp_path via AppHarness.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tmp_path: pytest tmp_path fixture
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
running AppHarness instance
|
||||||
|
"""
|
||||||
|
with AppHarness.create(
|
||||||
|
root=tmp_path,
|
||||||
|
app_source=FullyControlledInput, # type: ignore
|
||||||
|
) as harness:
|
||||||
|
yield harness
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_fully_controlled_input(fully_controlled_input: AppHarness):
|
||||||
|
"""Type text after moving cursor. Update text on backend.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fully_controlled_input: harness for FullyControlledInput app
|
||||||
|
"""
|
||||||
|
assert fully_controlled_input.app_instance is not None, "app is not running"
|
||||||
|
driver = fully_controlled_input.frontend()
|
||||||
|
|
||||||
|
# get a reference to the connected client
|
||||||
|
assert len(fully_controlled_input.poll_for_clients()) == 1
|
||||||
|
token, backend_state = list(
|
||||||
|
fully_controlled_input.app_instance.state_manager.states.items()
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
# find the input and wait for it to have the initial state value
|
||||||
|
debounce_input = driver.find_element(By.ID, "debounce_input_input")
|
||||||
|
value_input = driver.find_element(By.ID, "value_input")
|
||||||
|
on_change_input = driver.find_element(By.ID, "on_change_input")
|
||||||
|
clear_button = driver.find_element(By.TAG_NAME, "button")
|
||||||
|
assert fully_controlled_input.poll_for_value(debounce_input) == "initial"
|
||||||
|
assert fully_controlled_input.poll_for_value(value_input) == "initial"
|
||||||
|
|
||||||
|
# move cursor to home, then to the right and type characters
|
||||||
|
debounce_input.send_keys(Keys.HOME, Keys.ARROW_RIGHT)
|
||||||
|
debounce_input.send_keys("foo")
|
||||||
|
time.sleep(0.5)
|
||||||
|
assert debounce_input.get_attribute("value") == "ifoonitial"
|
||||||
|
assert backend_state.text == "ifoonitial"
|
||||||
|
assert fully_controlled_input.poll_for_value(value_input) == "ifoonitial"
|
||||||
|
|
||||||
|
# clear the input on the backend
|
||||||
|
backend_state.text = ""
|
||||||
|
fully_controlled_input.app_instance.state_manager.set_state(token, backend_state)
|
||||||
|
await fully_controlled_input.emit_state_updates()
|
||||||
|
assert backend_state.text == ""
|
||||||
|
assert (
|
||||||
|
fully_controlled_input.poll_for_value(
|
||||||
|
debounce_input, exp_not_equal="ifoonitial"
|
||||||
|
)
|
||||||
|
== ""
|
||||||
|
)
|
||||||
|
|
||||||
|
# type more characters
|
||||||
|
debounce_input.send_keys("getting testing done")
|
||||||
|
time.sleep(0.5)
|
||||||
|
assert debounce_input.get_attribute("value") == "getting testing done"
|
||||||
|
assert backend_state.text == "getting testing done"
|
||||||
|
assert fully_controlled_input.poll_for_value(value_input) == "getting testing done"
|
||||||
|
|
||||||
|
# type into the on_change input
|
||||||
|
on_change_input.send_keys("overwrite the state")
|
||||||
|
time.sleep(0.5)
|
||||||
|
assert debounce_input.get_attribute("value") == "overwrite the state"
|
||||||
|
assert on_change_input.get_attribute("value") == "overwrite the state"
|
||||||
|
assert backend_state.text == "overwrite the state"
|
||||||
|
assert fully_controlled_input.poll_for_value(value_input) == "overwrite the state"
|
||||||
|
|
||||||
|
clear_button.click()
|
||||||
|
time.sleep(0.5)
|
||||||
|
assert on_change_input.get_attribute("value") == ""
|
||||||
|
# potential bug: clearing the on_change field doesn't itself trigger on_change
|
||||||
|
# assert backend_state.text == ""
|
||||||
|
# assert debounce_input.get_attribute("value") == ""
|
||||||
|
# assert value_input.get_attribute("value") == ""
|
@ -1,5 +1,4 @@
|
|||||||
"""Integration tests for special server side events."""
|
"""Integration tests for special server side events."""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from typing import Generator
|
from typing import Generator
|
||||||
|
|
||||||
@ -14,19 +13,16 @@ def ServerSideEvent():
|
|||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
class SSState(rx.State):
|
class SSState(rx.State):
|
||||||
@rx.event
|
|
||||||
def set_value_yield(self):
|
def set_value_yield(self):
|
||||||
yield rx.set_value("a", "")
|
yield rx.set_value("a", "")
|
||||||
yield rx.set_value("b", "")
|
yield rx.set_value("b", "")
|
||||||
yield rx.set_value("c", "")
|
yield rx.set_value("c", "")
|
||||||
|
|
||||||
@rx.event
|
|
||||||
def set_value_yield_return(self):
|
def set_value_yield_return(self):
|
||||||
yield rx.set_value("a", "")
|
yield rx.set_value("a", "")
|
||||||
yield rx.set_value("b", "")
|
yield rx.set_value("b", "")
|
||||||
return rx.set_value("c", "")
|
return rx.set_value("c", "")
|
||||||
|
|
||||||
@rx.event
|
|
||||||
def set_value_return(self):
|
def set_value_return(self):
|
||||||
return [
|
return [
|
||||||
rx.set_value("a", ""),
|
rx.set_value("a", ""),
|
||||||
@ -34,18 +30,14 @@ def ServerSideEvent():
|
|||||||
rx.set_value("c", ""),
|
rx.set_value("c", ""),
|
||||||
]
|
]
|
||||||
|
|
||||||
@rx.event
|
|
||||||
def set_value_return_c(self):
|
def set_value_return_c(self):
|
||||||
return rx.set_value("c", "")
|
return rx.set_value("c", "")
|
||||||
|
|
||||||
app = rx.App(_state=rx.State)
|
app = rx.App(state=SSState)
|
||||||
|
|
||||||
@app.add_page
|
@app.add_page
|
||||||
def index():
|
def index():
|
||||||
return rx.fragment(
|
return rx.fragment(
|
||||||
rx.input(
|
|
||||||
id="token", value=SSState.router.session.client_token, is_read_only=True
|
|
||||||
),
|
|
||||||
rx.input(default_value="a", id="a"),
|
rx.input(default_value="a", id="a"),
|
||||||
rx.input(default_value="b", id="b"),
|
rx.input(default_value="b", id="b"),
|
||||||
rx.input(default_value="c", id="c"),
|
rx.input(default_value="c", id="c"),
|
||||||
@ -80,8 +72,10 @@ def ServerSideEvent():
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
app.compile()
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
def server_side_event(tmp_path_factory) -> Generator[AppHarness, None, None]:
|
def server_side_event(tmp_path_factory) -> Generator[AppHarness, None, None]:
|
||||||
"""Start ServerSideEvent app at tmp_path via AppHarness.
|
"""Start ServerSideEvent app at tmp_path via AppHarness.
|
||||||
|
|
||||||
@ -93,7 +87,7 @@ def server_side_event(tmp_path_factory) -> Generator[AppHarness, None, None]:
|
|||||||
"""
|
"""
|
||||||
with AppHarness.create(
|
with AppHarness.create(
|
||||||
root=tmp_path_factory.mktemp("server_side_event"),
|
root=tmp_path_factory.mktemp("server_side_event"),
|
||||||
app_source=ServerSideEvent,
|
app_source=ServerSideEvent, # type: ignore
|
||||||
) as harness:
|
) as harness:
|
||||||
yield harness
|
yield harness
|
||||||
|
|
||||||
@ -102,6 +96,7 @@ def server_side_event(tmp_path_factory) -> Generator[AppHarness, None, None]:
|
|||||||
def driver(server_side_event: AppHarness):
|
def driver(server_side_event: AppHarness):
|
||||||
"""Get an instance of the browser open to the server_side_event app.
|
"""Get an instance of the browser open to the server_side_event app.
|
||||||
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
server_side_event: harness for ServerSideEvent app
|
server_side_event: harness for ServerSideEvent app
|
||||||
|
|
||||||
@ -111,12 +106,7 @@ def driver(server_side_event: AppHarness):
|
|||||||
assert server_side_event.app_instance is not None, "app is not running"
|
assert server_side_event.app_instance is not None, "app is not running"
|
||||||
driver = server_side_event.frontend()
|
driver = server_side_event.frontend()
|
||||||
try:
|
try:
|
||||||
token_input = driver.find_element(By.ID, "token")
|
assert server_side_event.poll_for_clients()
|
||||||
assert token_input
|
|
||||||
# wait for the backend connection to send the token
|
|
||||||
token = server_side_event.poll_for_value(token_input)
|
|
||||||
assert token is not None
|
|
||||||
|
|
||||||
yield driver
|
yield driver
|
||||||
finally:
|
finally:
|
||||||
driver.quit()
|
driver.quit()
|
174
integration/test_upload.py
Normal file
174
integration/test_upload.py
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
"""Integration tests for file upload."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import time
|
||||||
|
from typing import Generator
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
|
||||||
|
from reflex.testing import AppHarness
|
||||||
|
|
||||||
|
|
||||||
|
def UploadFile():
|
||||||
|
"""App for testing dynamic routes."""
|
||||||
|
import reflex as rx
|
||||||
|
|
||||||
|
class UploadState(rx.State):
|
||||||
|
_file_data: dict[str, str] = {}
|
||||||
|
|
||||||
|
async def handle_upload(self, files: list[rx.UploadFile]):
|
||||||
|
for file in files:
|
||||||
|
upload_data = await file.read()
|
||||||
|
self._file_data[file.filename or ""] = upload_data.decode("utf-8")
|
||||||
|
|
||||||
|
@rx.var
|
||||||
|
def token(self) -> str:
|
||||||
|
return self.get_token()
|
||||||
|
|
||||||
|
def index():
|
||||||
|
return rx.vstack(
|
||||||
|
rx.input(value=UploadState.token, is_read_only=True, id="token"),
|
||||||
|
rx.upload(
|
||||||
|
rx.vstack(
|
||||||
|
rx.button("Select File"),
|
||||||
|
rx.text("Drag and drop files here or click to select files"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
rx.button(
|
||||||
|
"Upload",
|
||||||
|
on_click=lambda: UploadState.handle_upload(rx.upload_files()), # type: ignore
|
||||||
|
id="upload_button",
|
||||||
|
),
|
||||||
|
rx.box(
|
||||||
|
rx.foreach(
|
||||||
|
rx.selected_files,
|
||||||
|
lambda f: rx.text(f),
|
||||||
|
),
|
||||||
|
id="selected_files",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
app = rx.App(state=UploadState)
|
||||||
|
app.add_page(index)
|
||||||
|
app.compile()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def upload_file(tmp_path_factory) -> Generator[AppHarness, None, None]:
|
||||||
|
"""Start UploadFile app at tmp_path via AppHarness.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tmp_path_factory: pytest tmp_path_factory fixture
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
running AppHarness instance
|
||||||
|
"""
|
||||||
|
with AppHarness.create(
|
||||||
|
root=tmp_path_factory.mktemp("upload_file"),
|
||||||
|
app_source=UploadFile, # type: ignore
|
||||||
|
) as harness:
|
||||||
|
yield harness
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def driver(upload_file: AppHarness):
|
||||||
|
"""Get an instance of the browser open to the upload_file app.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
upload_file: harness for DynamicRoute app
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
WebDriver instance.
|
||||||
|
"""
|
||||||
|
assert upload_file.app_instance is not None, "app is not running"
|
||||||
|
driver = upload_file.frontend()
|
||||||
|
try:
|
||||||
|
assert upload_file.poll_for_clients()
|
||||||
|
yield driver
|
||||||
|
finally:
|
||||||
|
driver.quit()
|
||||||
|
|
||||||
|
|
||||||
|
def test_upload_file(tmp_path, upload_file: AppHarness, driver):
|
||||||
|
"""Submit a file upload and check that it arrived on the backend.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tmp_path: pytest tmp_path fixture
|
||||||
|
upload_file: harness for UploadFile app.
|
||||||
|
driver: WebDriver instance.
|
||||||
|
"""
|
||||||
|
assert upload_file.app_instance is not None
|
||||||
|
token_input = driver.find_element(By.ID, "token")
|
||||||
|
assert token_input
|
||||||
|
# wait for the backend connection to send the token
|
||||||
|
token = upload_file.poll_for_value(token_input)
|
||||||
|
assert token is not None
|
||||||
|
|
||||||
|
upload_box = driver.find_element(By.XPATH, "//input[@type='file']")
|
||||||
|
assert upload_box
|
||||||
|
upload_button = driver.find_element(By.ID, "upload_button")
|
||||||
|
assert upload_button
|
||||||
|
|
||||||
|
exp_name = "test.txt"
|
||||||
|
exp_contents = "test file contents!"
|
||||||
|
target_file = tmp_path / exp_name
|
||||||
|
target_file.write_text(exp_contents)
|
||||||
|
|
||||||
|
upload_box.send_keys(str(target_file))
|
||||||
|
upload_button.click()
|
||||||
|
|
||||||
|
# look up the backend state and assert on uploaded contents
|
||||||
|
backend_state = upload_file.app_instance.state_manager.states[token]
|
||||||
|
time.sleep(0.5)
|
||||||
|
assert backend_state._file_data[exp_name] == exp_contents
|
||||||
|
|
||||||
|
# check that the selected files are displayed
|
||||||
|
selected_files = driver.find_element(By.ID, "selected_files")
|
||||||
|
assert selected_files.text == exp_name
|
||||||
|
|
||||||
|
|
||||||
|
def test_upload_file_multiple(tmp_path, upload_file: AppHarness, driver):
|
||||||
|
"""Submit several file uploads and check that they arrived on the backend.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tmp_path: pytest tmp_path fixture
|
||||||
|
upload_file: harness for UploadFile app.
|
||||||
|
driver: WebDriver instance.
|
||||||
|
"""
|
||||||
|
assert upload_file.app_instance is not None
|
||||||
|
token_input = driver.find_element(By.ID, "token")
|
||||||
|
assert token_input
|
||||||
|
# wait for the backend connection to send the token
|
||||||
|
token = upload_file.poll_for_value(token_input)
|
||||||
|
assert token is not None
|
||||||
|
|
||||||
|
upload_box = driver.find_element(By.XPATH, "//input[@type='file']")
|
||||||
|
assert upload_box
|
||||||
|
upload_button = driver.find_element(By.ID, "upload_button")
|
||||||
|
assert upload_button
|
||||||
|
|
||||||
|
exp_files = {
|
||||||
|
"test1.txt": "test file contents!",
|
||||||
|
"test2.txt": "this is test file number 2!",
|
||||||
|
"reflex.txt": "reflex is awesome!",
|
||||||
|
}
|
||||||
|
for exp_name, exp_contents in exp_files.items():
|
||||||
|
target_file = tmp_path / exp_name
|
||||||
|
target_file.write_text(exp_contents)
|
||||||
|
upload_box.send_keys(str(target_file))
|
||||||
|
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
# check that the selected files are displayed
|
||||||
|
selected_files = driver.find_element(By.ID, "selected_files")
|
||||||
|
assert selected_files.text == "\n".join(exp_files)
|
||||||
|
|
||||||
|
# do the upload
|
||||||
|
upload_button.click()
|
||||||
|
|
||||||
|
# look up the backend state and assert on uploaded contents
|
||||||
|
backend_state = upload_file.app_instance.state_manager.states[token]
|
||||||
|
time.sleep(0.5)
|
||||||
|
for exp_name, exp_contents in exp_files.items():
|
||||||
|
assert backend_state._file_data[exp_name] == exp_contents
|
3516
poetry.lock
generated
3516
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
171
pyproject.toml
171
pyproject.toml
@ -1,74 +1,70 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "reflex"
|
name = "reflex"
|
||||||
version = "0.7.2dev1"
|
version = "0.2.5"
|
||||||
description = "Web apps in pure Python."
|
description = "Web apps in pure Python."
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikhil Rao <nikhil@reflex.dev>",
|
"Nikhil Rao <nikhil@reflex.dev>",
|
||||||
"Alek Petuskey <alek@reflex.dev>",
|
"Alek Petuskey <alek@reflex.dev>",
|
||||||
"Masen Furer <masen@reflex.dev>",
|
|
||||||
"Elijah Ahianyo <elijah@reflex.dev>",
|
|
||||||
"Thomas Brandého <thomas@reflex.dev>",
|
|
||||||
]
|
]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
homepage = "https://reflex.dev"
|
homepage = "https://reflex.dev"
|
||||||
repository = "https://github.com/reflex-dev/reflex"
|
repository = "https://github.com/reflex-dev/reflex"
|
||||||
documentation = "https://reflex.dev/docs/getting-started/introduction"
|
documentation = "https://reflex.dev/docs/getting-started/introduction"
|
||||||
keywords = ["web", "framework"]
|
keywords = [
|
||||||
classifiers = ["Development Status :: 4 - Beta"]
|
"web",
|
||||||
|
"framework",
|
||||||
|
]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 4 - Beta",
|
||||||
|
]
|
||||||
|
packages = [
|
||||||
|
{include = "reflex"}
|
||||||
|
]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = ">=3.10, <4.0"
|
python = "^3.7"
|
||||||
fastapi = ">=0.96.0,!=0.111.0,!=0.111.1"
|
cloudpickle = "^2.2.1"
|
||||||
gunicorn = ">=20.1.0,<24.0"
|
fastapi = "^0.96.0"
|
||||||
jinja2 = ">=3.1.2,<4.0"
|
gunicorn = "^20.1.0"
|
||||||
psutil = ">=5.9.4,<7.0"
|
httpx = "^0.24.0"
|
||||||
pydantic = ">=1.10.21,<3.0"
|
jinja2 = "^3.1.2"
|
||||||
python-multipart = ">=0.0.5,<0.1"
|
plotly = "^5.13.0"
|
||||||
python-socketio = ">=5.7.0,<6.0"
|
psutil = "^5.9.4"
|
||||||
redis = ">=4.3.5,<6.0"
|
pydantic = "^1.10.2"
|
||||||
rich = ">=13.0.0,<14.0"
|
python-multipart = "^0.0.5"
|
||||||
sqlmodel = ">=0.0.14,<0.1"
|
python-socketio = "^5.7.0"
|
||||||
typer = ">=0.15.1,<1.0"
|
redis = "^4.3.5"
|
||||||
uvicorn = ">=0.20.0"
|
rich = "^13.0.0"
|
||||||
starlette-admin = ">=0.11.0,<1.0"
|
sqlmodel = "^0.0.8"
|
||||||
alembic = ">=1.11.1,<2.0"
|
typer = "0.4.2"
|
||||||
platformdirs = ">=3.10.0,<5.0"
|
uvicorn = "^0.20.0"
|
||||||
distro = { version = ">=1.8.0,<2.0", platform = "linux" }
|
watchdog = "^2.3.1"
|
||||||
python-engineio = "!=4.6.0"
|
watchfiles = "^0.19.0"
|
||||||
wrapt = ">=1.17.0,<2.0"
|
websockets = "^10.4"
|
||||||
packaging = ">=23.1,<25.0"
|
starlette-admin = "^0.9.0"
|
||||||
reflex-hosting-cli = ">=0.1.29"
|
importlib-metadata = {version = "^6.7.0", python = ">=3.7, <3.8"}
|
||||||
charset-normalizer = ">=3.3.2,<4.0"
|
alembic = "^1.11.1"
|
||||||
wheel = ">=0.42.0,<1.0"
|
platformdirs = "^3.10.0"
|
||||||
build = ">=1.0.3,<2.0"
|
distro = {version = "^1.8.0", platform = "linux"}
|
||||||
setuptools = ">=75.0"
|
|
||||||
httpx = ">=0.25.1,<1.0"
|
|
||||||
twine = ">=4.0.0,<7.0"
|
|
||||||
tomlkit = ">=0.12.4,<1.0"
|
|
||||||
lazy_loader = ">=0.4"
|
|
||||||
typing_extensions = ">=4.6.0"
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
pytest = ">=7.1.2,<9.0"
|
pytest = "^7.1.2"
|
||||||
pytest-mock = ">=3.10.0,<4.0"
|
pytest-mock = "^3.10.0"
|
||||||
pyright = ">=1.1.394, <1.2"
|
pyright = "^1.1.229"
|
||||||
darglint = ">=1.8.1,<2.0"
|
darglint = "^1.8.1"
|
||||||
dill = ">=0.3.8"
|
toml = "^0.10.2"
|
||||||
toml = ">=0.10.2,<1.0"
|
pytest-asyncio = "^0.20.1"
|
||||||
pytest-asyncio = ">=0.24.0"
|
pytest-cov = "^4.0.0"
|
||||||
pytest-cov = ">=4.0.0,<7.0"
|
black = "^22.10.0"
|
||||||
ruff = "0.9.6"
|
ruff = "^0.0.244"
|
||||||
pandas = ">=2.1.1,<3.0"
|
pandas = [
|
||||||
pillow = ">=10.0.0,<12.0"
|
{version = "^1.5.3", python = ">=3.8,<4.0"},
|
||||||
plotly = ">=5.13.0,<6.0"
|
{version = "^1.1", python = ">=3.7, <3.8"}
|
||||||
asynctest = ">=0.13.0,<1.0"
|
]
|
||||||
pre-commit = ">=3.2.1"
|
asynctest = "^0.13.0"
|
||||||
selenium = ">=4.11.0,<5.0"
|
pre-commit = {version = "^3.2.1", python = ">=3.8,<4.0"}
|
||||||
pytest-benchmark = ">=4.0.0,<6.0"
|
selenium = "^4.10.0"
|
||||||
playwright = ">=1.46.0"
|
|
||||||
pytest-playwright = ">=0.5.1"
|
|
||||||
pytest-codspeed = "^3.1.2"
|
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
reflex = "reflex.reflex:cli"
|
reflex = "reflex.reflex:cli"
|
||||||
@ -78,60 +74,17 @@ requires = ["poetry-core>=1.5.1"]
|
|||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
[tool.pyright]
|
[tool.pyright]
|
||||||
reportIncompatibleMethodOverride = false
|
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
target-version = "py310"
|
|
||||||
output-format = "concise"
|
|
||||||
lint.isort.split-on-trailing-comma = false
|
|
||||||
lint.select = [
|
|
||||||
"ANN001",
|
|
||||||
"B",
|
|
||||||
"C4",
|
|
||||||
"D",
|
|
||||||
"E",
|
|
||||||
"ERA",
|
|
||||||
"F",
|
|
||||||
"FURB",
|
|
||||||
"I",
|
|
||||||
"N",
|
|
||||||
"PERF",
|
|
||||||
"PGH",
|
|
||||||
"PTH",
|
|
||||||
"RUF",
|
|
||||||
"SIM",
|
|
||||||
"T",
|
|
||||||
"TRY",
|
|
||||||
"W",
|
|
||||||
]
|
|
||||||
lint.ignore = [
|
|
||||||
"B008",
|
|
||||||
"D205",
|
|
||||||
"E501",
|
|
||||||
"F403",
|
|
||||||
"SIM115",
|
|
||||||
"RUF006",
|
|
||||||
"RUF008",
|
|
||||||
"RUF012",
|
|
||||||
"TRY0",
|
|
||||||
]
|
|
||||||
lint.pydocstyle.convention = "google"
|
|
||||||
|
|
||||||
[tool.ruff.lint.per-file-ignores]
|
select = ["B", "D", "E", "F", "I", "SIM", "W"]
|
||||||
|
|
||||||
|
ignore = ["B008", "D203", "D205", "D213", "D401", "D406", "D407", "E501", "F403", "F405", "F541"]
|
||||||
|
|
||||||
|
target-version = "py37"
|
||||||
|
|
||||||
|
[tool.ruff.per-file-ignores]
|
||||||
|
|
||||||
"__init__.py" = ["F401"]
|
"__init__.py" = ["F401"]
|
||||||
"tests/*.py" = ["ANN001", "D100", "D103", "D104", "B018", "PERF", "T", "N"]
|
"tests/*.py" = ["D100", "D103", "D104"]
|
||||||
"benchmarks/*.py" = ["ANN001", "D100", "D103", "D104", "B018", "PERF", "T", "N"]
|
|
||||||
"reflex/.templates/*.py" = ["D100", "D103", "D104"]
|
"reflex/.templates/*.py" = ["D100", "D103", "D104"]
|
||||||
"*.pyi" = ["D301", "D415", "D417", "D418", "E742", "N", "PGH"]
|
|
||||||
"pyi_generator.py" = ["N802"]
|
|
||||||
"reflex/constants/*.py" = ["N"]
|
|
||||||
"*/blank.py" = ["I001"]
|
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
|
||||||
filterwarnings = "ignore:fields may not start with an underscore:RuntimeWarning"
|
|
||||||
asyncio_default_fixture_loop_scope = "function"
|
|
||||||
asyncio_mode = "auto"
|
|
||||||
|
|
||||||
[tool.codespell]
|
|
||||||
skip = "docs/*,*.html,examples/*, *.pyi, poetry.lock"
|
|
||||||
ignore-words-list = "te, TreeE"
|
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB |
@ -1,39 +0,0 @@
|
|||||||
"""Welcome to Reflex! This file outlines the steps to create a basic app."""
|
|
||||||
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
from rxconfig import config
|
|
||||||
|
|
||||||
|
|
||||||
class State(rx.State):
|
|
||||||
"""The app state."""
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
def index() -> rx.Component:
|
|
||||||
# Welcome Page (Index)
|
|
||||||
return rx.container(
|
|
||||||
rx.color_mode.button(position="top-right"),
|
|
||||||
rx.vstack(
|
|
||||||
rx.heading("Welcome to Reflex!", size="9"),
|
|
||||||
rx.text(
|
|
||||||
"Get started by editing ",
|
|
||||||
rx.code(f"{config.app_name}/{config.app_name}.py"),
|
|
||||||
size="5",
|
|
||||||
),
|
|
||||||
rx.link(
|
|
||||||
rx.button("Check out our docs!"),
|
|
||||||
href="https://reflex.dev/docs/getting-started/introduction/",
|
|
||||||
is_external=True,
|
|
||||||
),
|
|
||||||
spacing="5",
|
|
||||||
justify="center",
|
|
||||||
min_height="85vh",
|
|
||||||
),
|
|
||||||
rx.logo(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
app = rx.App()
|
|
||||||
app.add_page(index)
|
|
53
reflex/.templates/apps/counter/counter.py
Normal file
53
reflex/.templates/apps/counter/counter.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
"""Welcome to Reflex! This file creates a counter app."""
|
||||||
|
import random
|
||||||
|
|
||||||
|
import reflex as rx
|
||||||
|
|
||||||
|
|
||||||
|
class State(rx.State):
|
||||||
|
"""The app state."""
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
def increment(self):
|
||||||
|
"""Increment the count."""
|
||||||
|
self.count += 1
|
||||||
|
|
||||||
|
def decrement(self):
|
||||||
|
"""Decrement the count."""
|
||||||
|
self.count -= 1
|
||||||
|
|
||||||
|
def random(self):
|
||||||
|
"""Randomize the count."""
|
||||||
|
self.count = random.randint(0, 100)
|
||||||
|
|
||||||
|
|
||||||
|
def index() -> rx.Component:
|
||||||
|
return rx.center(
|
||||||
|
rx.vstack(
|
||||||
|
rx.heading(State.count),
|
||||||
|
rx.hstack(
|
||||||
|
rx.button("Decrement", on_click=State.decrement, color_scheme="red"),
|
||||||
|
rx.button(
|
||||||
|
"Randomize",
|
||||||
|
on_click=State.random,
|
||||||
|
background_image="linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(0,176,34,1) 100%)",
|
||||||
|
color="white",
|
||||||
|
),
|
||||||
|
rx.button("Increment", on_click=State.increment, color_scheme="green"),
|
||||||
|
),
|
||||||
|
padding="1em",
|
||||||
|
bg="#ededed",
|
||||||
|
border_radius="1em",
|
||||||
|
box_shadow="lg",
|
||||||
|
),
|
||||||
|
padding_y="5em",
|
||||||
|
font_size="2em",
|
||||||
|
text_align="center",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Add state and page to the app.
|
||||||
|
app = rx.App()
|
||||||
|
app.add_page(index, title="Counter")
|
||||||
|
app.compile()
|
45
reflex/.templates/apps/default/default.py
Normal file
45
reflex/.templates/apps/default/default.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
"""Welcome to Reflex! This file outlines the steps to create a basic app."""
|
||||||
|
from rxconfig import config
|
||||||
|
|
||||||
|
import reflex as rx
|
||||||
|
|
||||||
|
docs_url = "https://reflex.dev/docs/getting-started/introduction"
|
||||||
|
filename = f"{config.app_name}/{config.app_name}.py"
|
||||||
|
|
||||||
|
|
||||||
|
class State(rx.State):
|
||||||
|
"""The app state."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def index() -> rx.Component:
|
||||||
|
return rx.fragment(
|
||||||
|
rx.color_mode_button(rx.color_mode_icon(), float="right"),
|
||||||
|
rx.vstack(
|
||||||
|
rx.heading("Welcome to Reflex!", font_size="2em"),
|
||||||
|
rx.box("Get started by editing ", rx.code(filename, font_size="1em")),
|
||||||
|
rx.link(
|
||||||
|
"Check out our docs!",
|
||||||
|
href=docs_url,
|
||||||
|
border="0.1em solid",
|
||||||
|
padding="0.5em",
|
||||||
|
border_radius="0.5em",
|
||||||
|
_hover={
|
||||||
|
"color": rx.color_mode_cond(
|
||||||
|
light="rgb(107,99,246)",
|
||||||
|
dark="rgb(179, 175, 255)",
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
spacing="1.5em",
|
||||||
|
font_size="2em",
|
||||||
|
padding_top="10%",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Add state and page to the app.
|
||||||
|
app = rx.App()
|
||||||
|
app.add_page(index)
|
||||||
|
app.compile()
|
BIN
reflex/.templates/assets/favicon.ico
Normal file
BIN
reflex/.templates/assets/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
@ -1,5 +1,8 @@
|
|||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
config = rx.Config(
|
class {{ config_name }}(rx.Config):
|
||||||
|
pass
|
||||||
|
|
||||||
|
config = {{ config_name }}(
|
||||||
app_name="{{ app_name }}",
|
app_name="{{ app_name }}",
|
||||||
)
|
)
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
# {{ module_name }}
|
|
||||||
|
|
||||||
A Reflex custom component {{ module_name }}.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install {{ package_name }}
|
|
||||||
```
|
|
@ -1 +0,0 @@
|
|||||||
from .{{ module_name }} import *
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user