reflex-junction 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- reflex_junction-0.1.0/.github/actions/basic-checks/action.yml +13 -0
- reflex_junction-0.1.0/.github/actions/full-checks/action.yml +47 -0
- reflex_junction-0.1.0/.github/actions/setup-python-env/action.yml +28 -0
- reflex_junction-0.1.0/.github/workflows/_reusable-ci.yml +54 -0
- reflex_junction-0.1.0/.github/workflows/ci-forks.yml +20 -0
- reflex_junction-0.1.0/.github/workflows/ci.yml +21 -0
- reflex_junction-0.1.0/.github/workflows/publish.yml +57 -0
- reflex_junction-0.1.0/.gitignore +42 -0
- reflex_junction-0.1.0/.pre-commit-config.yaml +26 -0
- reflex_junction-0.1.0/LICENSE +21 -0
- reflex_junction-0.1.0/PKG-INFO +193 -0
- reflex_junction-0.1.0/README.md +170 -0
- reflex_junction-0.1.0/Taskfile.yml +99 -0
- reflex_junction-0.1.0/custom_components/reflex_junction/__init__.py +33 -0
- reflex_junction-0.1.0/custom_components/reflex_junction/base.py +7 -0
- reflex_junction-0.1.0/custom_components/reflex_junction/fastapi_helpers.py +83 -0
- reflex_junction-0.1.0/custom_components/reflex_junction/junction_provider.py +375 -0
- reflex_junction-0.1.0/custom_components/reflex_junction/models.py +39 -0
- reflex_junction-0.1.0/docs/getting_started.md +78 -0
- reflex_junction-0.1.0/docs/index.md +27 -0
- reflex_junction-0.1.0/junction_demo/junction_demo/__init__.py +0 -0
- reflex_junction-0.1.0/junction_demo/junction_demo/junction_demo.py +50 -0
- reflex_junction-0.1.0/junction_demo/rxconfig.py +5 -0
- reflex_junction-0.1.0/mkdocs.yml +44 -0
- reflex_junction-0.1.0/pyproject.toml +81 -0
- reflex_junction-0.1.0/tests/__init__.py +0 -0
- reflex_junction-0.1.0/tests/test_models.py +75 -0
- reflex_junction-0.1.0/tests/test_state.py +210 -0
- reflex_junction-0.1.0/uv.lock +2236 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: 'Full CI Checks'
|
|
2
|
+
description: 'Runs full CI including integration tests with secrets'
|
|
3
|
+
inputs:
|
|
4
|
+
junction-api-key:
|
|
5
|
+
description: 'Junction (Vital) API key'
|
|
6
|
+
required: true
|
|
7
|
+
|
|
8
|
+
runs:
|
|
9
|
+
using: 'composite'
|
|
10
|
+
steps:
|
|
11
|
+
- name: Check env vars set
|
|
12
|
+
run: |
|
|
13
|
+
if [ -z "${{ inputs.junction-api-key }}" ]; then
|
|
14
|
+
echo "JUNCTION_API_KEY is not set"
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
shell: bash
|
|
18
|
+
|
|
19
|
+
- name: Create .env
|
|
20
|
+
run: |
|
|
21
|
+
echo "JUNCTION_API_KEY=${{ inputs.junction-api-key }}" >> .env
|
|
22
|
+
shell: bash
|
|
23
|
+
|
|
24
|
+
- name: DEBUG - check python version
|
|
25
|
+
run: uv sync && uv run python --version
|
|
26
|
+
shell: bash
|
|
27
|
+
|
|
28
|
+
- name: Run lint
|
|
29
|
+
run: task lint
|
|
30
|
+
shell: bash
|
|
31
|
+
|
|
32
|
+
- name: Run typecheck
|
|
33
|
+
run: task typecheck
|
|
34
|
+
shell: bash
|
|
35
|
+
|
|
36
|
+
- name: Initialize Reflex
|
|
37
|
+
run: uv run reflex init
|
|
38
|
+
working-directory: junction_demo
|
|
39
|
+
shell: bash
|
|
40
|
+
|
|
41
|
+
- name: Install playwright
|
|
42
|
+
run: uv run playwright install chromium
|
|
43
|
+
shell: bash
|
|
44
|
+
|
|
45
|
+
- name: Run tests
|
|
46
|
+
run: task test
|
|
47
|
+
shell: bash
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: 'Setup Python Environment'
|
|
2
|
+
description: 'Sets up Python, uv, and Task for CI'
|
|
3
|
+
inputs:
|
|
4
|
+
python-version:
|
|
5
|
+
description: 'Python version to setup'
|
|
6
|
+
required: true
|
|
7
|
+
github-token:
|
|
8
|
+
description: 'GitHub token for Task installation'
|
|
9
|
+
required: true
|
|
10
|
+
|
|
11
|
+
runs:
|
|
12
|
+
using: 'composite'
|
|
13
|
+
steps:
|
|
14
|
+
- name: Set up Python
|
|
15
|
+
uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: ${{ inputs.python-version }}
|
|
18
|
+
|
|
19
|
+
- name: Install uv
|
|
20
|
+
uses: astral-sh/setup-uv@v5
|
|
21
|
+
with:
|
|
22
|
+
version: "0.6.5"
|
|
23
|
+
|
|
24
|
+
- name: Install Task
|
|
25
|
+
uses: arduino/setup-task@v2
|
|
26
|
+
with:
|
|
27
|
+
version: 3.x
|
|
28
|
+
repo-token: ${{ inputs.github-token }}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Reusable CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_call:
|
|
5
|
+
inputs:
|
|
6
|
+
checkout_ref:
|
|
7
|
+
description: 'Git ref to checkout'
|
|
8
|
+
required: false
|
|
9
|
+
type: string
|
|
10
|
+
default: ''
|
|
11
|
+
checkout_repository:
|
|
12
|
+
description: 'Repository to checkout (for forks)'
|
|
13
|
+
required: false
|
|
14
|
+
type: string
|
|
15
|
+
default: ''
|
|
16
|
+
check_type:
|
|
17
|
+
description: 'Type of checks to run: basic or full'
|
|
18
|
+
required: true
|
|
19
|
+
type: string
|
|
20
|
+
environment:
|
|
21
|
+
description: 'Environment to use for secrets (only for full checks)'
|
|
22
|
+
required: false
|
|
23
|
+
type: string
|
|
24
|
+
default: ''
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
ci:
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
strategy:
|
|
30
|
+
matrix:
|
|
31
|
+
python-versions: ["3.11", "3.12", "3.13"]
|
|
32
|
+
environment: ${{ inputs.environment || null }}
|
|
33
|
+
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v4
|
|
36
|
+
with:
|
|
37
|
+
ref: ${{ inputs.checkout_ref || github.sha }}
|
|
38
|
+
repository: ${{ inputs.checkout_repository || github.repository }}
|
|
39
|
+
|
|
40
|
+
- name: Setup Python Environment
|
|
41
|
+
uses: ./.github/actions/setup-python-env
|
|
42
|
+
with:
|
|
43
|
+
python-version: ${{ matrix.python-versions }}
|
|
44
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
45
|
+
|
|
46
|
+
- name: Run Basic Checks
|
|
47
|
+
if: inputs.check_type == 'basic'
|
|
48
|
+
uses: ./.github/actions/basic-checks
|
|
49
|
+
|
|
50
|
+
- name: Run Full Checks
|
|
51
|
+
if: inputs.check_type == 'full'
|
|
52
|
+
uses: ./.github/actions/full-checks
|
|
53
|
+
with:
|
|
54
|
+
junction-api-key: ${{ secrets.JUNCTION_API_KEY }}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: CI for Fork PRs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request_target:
|
|
5
|
+
# Note: Repo must be set to require approval before running workflows from forks
|
|
6
|
+
# This only runs basic checks without secrets for safety
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
basic-checks:
|
|
15
|
+
uses: ./.github/workflows/_reusable-ci.yml
|
|
16
|
+
with:
|
|
17
|
+
checkout_ref: ${{ github.event.pull_request.head.sha }}
|
|
18
|
+
checkout_repository: ${{ github.event.pull_request.head.repo.full_name }}
|
|
19
|
+
check_type: 'basic'
|
|
20
|
+
secrets: inherit
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*.*.*"
|
|
7
|
+
# branches: [main]
|
|
8
|
+
pull_request:
|
|
9
|
+
branches: [main]
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
ci:
|
|
17
|
+
uses: ./.github/workflows/_reusable-ci.yml
|
|
18
|
+
with:
|
|
19
|
+
check_type: 'full'
|
|
20
|
+
environment: 'demo'
|
|
21
|
+
secrets: inherit
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_run:
|
|
5
|
+
workflows: ["CI"]
|
|
6
|
+
types:
|
|
7
|
+
- completed
|
|
8
|
+
branches: [v*.*.*]
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
deploy:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
if: ${{ github.event.workflow_run.conclusion == 'success' && github.repository == 'Syntropy-Health/reflex-junction' }}
|
|
18
|
+
environment: deploy
|
|
19
|
+
permissions:
|
|
20
|
+
contents: write
|
|
21
|
+
id-token: write # Required for PyPI trusted publishers
|
|
22
|
+
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- name: Set up Python
|
|
27
|
+
uses: actions/setup-python@v5
|
|
28
|
+
with:
|
|
29
|
+
python-version: "3.13"
|
|
30
|
+
|
|
31
|
+
- name: Install uv
|
|
32
|
+
uses: astral-sh/setup-uv@v5
|
|
33
|
+
with:
|
|
34
|
+
version: "0.6.5"
|
|
35
|
+
|
|
36
|
+
- name: Install Task
|
|
37
|
+
uses: arduino/setup-task@v2
|
|
38
|
+
with:
|
|
39
|
+
version: 3.x
|
|
40
|
+
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
41
|
+
|
|
42
|
+
- name: Build component
|
|
43
|
+
run: |
|
|
44
|
+
cd custom_components && uv run python -m reflex.utils.pyi_generator reflex_junction
|
|
45
|
+
cd .. && uv build
|
|
46
|
+
|
|
47
|
+
- name: Publish to PyPI
|
|
48
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
49
|
+
with:
|
|
50
|
+
# Uses trusted publishers (OIDC) — no token needed
|
|
51
|
+
# Falls back to PYPI_TOKEN if trusted publishers not configured
|
|
52
|
+
password: ${{ secrets.PYPI_TOKEN }}
|
|
53
|
+
|
|
54
|
+
- name: Publish docs
|
|
55
|
+
run: |
|
|
56
|
+
uv sync --dev
|
|
57
|
+
uv run mkdocs gh-deploy --force
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
*.egg
|
|
9
|
+
|
|
10
|
+
# Virtual environments
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
|
|
14
|
+
# Reflex
|
|
15
|
+
.web/
|
|
16
|
+
*.db
|
|
17
|
+
|
|
18
|
+
# IDE
|
|
19
|
+
.idea/
|
|
20
|
+
.vscode/
|
|
21
|
+
*.swp
|
|
22
|
+
*.swo
|
|
23
|
+
|
|
24
|
+
# OS
|
|
25
|
+
.DS_Store
|
|
26
|
+
Thumbs.db
|
|
27
|
+
|
|
28
|
+
# Environment
|
|
29
|
+
.env
|
|
30
|
+
.env.*
|
|
31
|
+
!.env.example
|
|
32
|
+
|
|
33
|
+
# Testing
|
|
34
|
+
.pytest_cache/
|
|
35
|
+
htmlcov/
|
|
36
|
+
.coverage
|
|
37
|
+
|
|
38
|
+
# Build artifacts
|
|
39
|
+
*.pyi
|
|
40
|
+
|
|
41
|
+
# Node (from Reflex)
|
|
42
|
+
node_modules/
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v5.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: trailing-whitespace
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
- id: check-yaml
|
|
8
|
+
- id: check-added-large-files
|
|
9
|
+
|
|
10
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
11
|
+
rev: v0.11.2
|
|
12
|
+
hooks:
|
|
13
|
+
- id: ruff
|
|
14
|
+
args: [--fix]
|
|
15
|
+
- id: ruff-format
|
|
16
|
+
|
|
17
|
+
- repo: https://github.com/RobertCraigie/pyright-python
|
|
18
|
+
rev: v1.1.397
|
|
19
|
+
hooks:
|
|
20
|
+
- id: pyright
|
|
21
|
+
|
|
22
|
+
- repo: https://github.com/astral-sh/uv-pre-commit
|
|
23
|
+
# uv version.
|
|
24
|
+
rev: 0.6.9
|
|
25
|
+
hooks:
|
|
26
|
+
- id: uv-lock
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Syntropy Health
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: reflex-junction
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Reflex custom component wrapping @tryvital/vital-link and integrating the Junction (Vital) health data SDK
|
|
5
|
+
Project-URL: repository, https://github.com/Syntropy-Health/reflex-junction
|
|
6
|
+
Author: Syntropy Health
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: health-data,junction,reflex,reflex-custom-components,vital
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Requires-Dist: fastapi>=0.115.0
|
|
17
|
+
Requires-Dist: reflex>=0.8.0
|
|
18
|
+
Requires-Dist: vital>=2.1.0
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: build; extra == 'dev'
|
|
21
|
+
Requires-Dist: twine; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
[](https://github.com/Syntropy-Health/reflex-junction/actions/workflows/ci.yml)
|
|
25
|
+
[](https://pypi.org/project/reflex-junction/)
|
|
26
|
+
[](https://pypi.org/project/reflex-junction/)
|
|
27
|
+
[](https://opensource.org/licenses/MIT)
|
|
28
|
+
[](https://syntropy-health.github.io/reflex-junction/)
|
|
29
|
+
|
|
30
|
+
# reflex-junction
|
|
31
|
+
|
|
32
|
+
A [Reflex](https://reflex.dev) custom component for integrating [Junction (Vital)](https://tryvital.io/) health data into your application. Connect wearables and health platforms (Oura, Fitbit, Apple Health, Garmin, etc.) with a few lines of Python.
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- **JunctionState** — Reflex state management for the Vital API (user creation, provider connections, data refresh)
|
|
37
|
+
- **JunctionUser** — Extended state with health data summaries (activity, sleep, body, meals, workouts)
|
|
38
|
+
- **wrap_app()** — One-line integration that configures your entire Reflex app
|
|
39
|
+
- **junction_provider()** — Component-level integration for wrapping specific pages
|
|
40
|
+
- **Webhook support** — FastAPI router for receiving real-time health data events
|
|
41
|
+
- **Link widget support** — Token generation for the Vital Link provider connection UI
|
|
42
|
+
- **Multi-region** — US and EU sandbox/production environments
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install reflex-junction
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or with your preferred package manager:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
uv add reflex-junction
|
|
54
|
+
poetry add reflex-junction
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Quick Start
|
|
58
|
+
|
|
59
|
+
### 1. Get an API key
|
|
60
|
+
|
|
61
|
+
Sign up at [tryvital.io](https://tryvital.io/) and grab your API key from the dashboard.
|
|
62
|
+
|
|
63
|
+
### 2. Wrap your app
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import os
|
|
67
|
+
import reflex as rx
|
|
68
|
+
import reflex_junction as junction
|
|
69
|
+
|
|
70
|
+
app = rx.App()
|
|
71
|
+
|
|
72
|
+
junction.wrap_app(
|
|
73
|
+
app,
|
|
74
|
+
api_key=os.environ["JUNCTION_API_KEY"],
|
|
75
|
+
environment="sandbox", # or "production"
|
|
76
|
+
register_user_state=True,
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 3. Use Junction state in your pages
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
def health_dashboard() -> rx.Component:
|
|
84
|
+
return rx.container(
|
|
85
|
+
rx.heading("Health Dashboard"),
|
|
86
|
+
rx.text(f"Connected: {junction.JunctionState.has_connections}"),
|
|
87
|
+
rx.foreach(
|
|
88
|
+
junction.JunctionState.connected_sources,
|
|
89
|
+
lambda p: rx.badge(p["name"]),
|
|
90
|
+
),
|
|
91
|
+
)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Usage
|
|
95
|
+
|
|
96
|
+
### Using `junction_provider` directly
|
|
97
|
+
|
|
98
|
+
For page-level integration instead of app-wide:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
import reflex_junction as junction
|
|
102
|
+
|
|
103
|
+
def health_page() -> rx.Component:
|
|
104
|
+
return junction.junction_provider(
|
|
105
|
+
rx.container(
|
|
106
|
+
rx.text("Connected providers: "),
|
|
107
|
+
rx.text(junction.JunctionState.connected_sources.length()),
|
|
108
|
+
),
|
|
109
|
+
api_key=os.environ["JUNCTION_API_KEY"],
|
|
110
|
+
)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Webhook support
|
|
114
|
+
|
|
115
|
+
Receive real-time events when health data updates:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
junction.wrap_app(
|
|
119
|
+
app,
|
|
120
|
+
api_key=os.environ["JUNCTION_API_KEY"],
|
|
121
|
+
register_webhooks=True,
|
|
122
|
+
webhook_secret=os.environ["JUNCTION_WEBHOOK_SECRET"],
|
|
123
|
+
webhook_prefix="/junction", # POST /junction/webhooks
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Environment options
|
|
128
|
+
|
|
129
|
+
| Environment | Description |
|
|
130
|
+
|------------------|--------------------|
|
|
131
|
+
| `sandbox` | US sandbox (default) |
|
|
132
|
+
| `production` | US production |
|
|
133
|
+
| `sandbox_eu` | EU sandbox |
|
|
134
|
+
| `production_eu` | EU production |
|
|
135
|
+
|
|
136
|
+
## API Reference
|
|
137
|
+
|
|
138
|
+
### State Classes
|
|
139
|
+
|
|
140
|
+
| Class | Description |
|
|
141
|
+
|-------|-------------|
|
|
142
|
+
| `JunctionState` | Core state — user creation, provider management, Link tokens |
|
|
143
|
+
| `JunctionUser` | Extended state — health data summaries (activity, sleep, body, meals, workouts) |
|
|
144
|
+
|
|
145
|
+
### Configuration Models
|
|
146
|
+
|
|
147
|
+
| Class | Description |
|
|
148
|
+
|-------|-------------|
|
|
149
|
+
| `JunctionConfig` | Environment and region settings |
|
|
150
|
+
| `LinkConfig` | Redirect URL and provider filter for the Link widget |
|
|
151
|
+
| `ProviderInfo` | Provider metadata (name, slug, logo, auth_type) |
|
|
152
|
+
|
|
153
|
+
### Functions
|
|
154
|
+
|
|
155
|
+
| Function | Description |
|
|
156
|
+
|----------|-------------|
|
|
157
|
+
| `wrap_app(app, api_key, ...)` | One-line app integration with optional webhooks |
|
|
158
|
+
| `junction_provider(*children, api_key, ...)` | Component wrapper for page-level integration |
|
|
159
|
+
| `on_load(handlers)` | Wrap page on_load handlers to wait for Junction init |
|
|
160
|
+
| `register_on_auth_change_handler(handler)` | Register handler to fire after Junction initializes |
|
|
161
|
+
| `create_webhook_router(prefix, secret)` | Create a standalone FastAPI webhook router |
|
|
162
|
+
| `register_webhook_api(app, secret, prefix)` | Register webhook endpoint on a Reflex app |
|
|
163
|
+
|
|
164
|
+
### JunctionState Events
|
|
165
|
+
|
|
166
|
+
| Event | Description |
|
|
167
|
+
|-------|-------------|
|
|
168
|
+
| `create_user(client_user_id)` | Create a Junction user mapped to your app's user |
|
|
169
|
+
| `get_connected_providers()` | Fetch connected health data providers |
|
|
170
|
+
| `disconnect_provider(provider)` | Disconnect a specific provider by slug |
|
|
171
|
+
| `refresh_data()` | Trigger data sync from all connected providers |
|
|
172
|
+
| `create_link_token(redirect_url)` | Generate a Link widget token |
|
|
173
|
+
|
|
174
|
+
See the [full documentation](https://syntropy-health.github.io/reflex-junction/) for detailed guides.
|
|
175
|
+
|
|
176
|
+
## Contributing
|
|
177
|
+
|
|
178
|
+
Contributions welcome! We use [Taskfile](https://taskfile.dev/) for common tasks:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
task install # Install dev dependencies + pre-commit
|
|
182
|
+
task test # Run lint + typecheck + pytest
|
|
183
|
+
task run # Run the demo app
|
|
184
|
+
task run-docs # Serve docs locally at localhost:9000
|
|
185
|
+
task bump-patch # Bump patch version (bug fix)
|
|
186
|
+
task bump-minor # Bump minor version (new feature)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Workflow: Fork → feature branch → add tests → submit PR.
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
[MIT](LICENSE) — Copyright (c) 2025 Syntropy Health
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
[](https://github.com/Syntropy-Health/reflex-junction/actions/workflows/ci.yml)
|
|
2
|
+
[](https://pypi.org/project/reflex-junction/)
|
|
3
|
+
[](https://pypi.org/project/reflex-junction/)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://syntropy-health.github.io/reflex-junction/)
|
|
6
|
+
|
|
7
|
+
# reflex-junction
|
|
8
|
+
|
|
9
|
+
A [Reflex](https://reflex.dev) custom component for integrating [Junction (Vital)](https://tryvital.io/) health data into your application. Connect wearables and health platforms (Oura, Fitbit, Apple Health, Garmin, etc.) with a few lines of Python.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **JunctionState** — Reflex state management for the Vital API (user creation, provider connections, data refresh)
|
|
14
|
+
- **JunctionUser** — Extended state with health data summaries (activity, sleep, body, meals, workouts)
|
|
15
|
+
- **wrap_app()** — One-line integration that configures your entire Reflex app
|
|
16
|
+
- **junction_provider()** — Component-level integration for wrapping specific pages
|
|
17
|
+
- **Webhook support** — FastAPI router for receiving real-time health data events
|
|
18
|
+
- **Link widget support** — Token generation for the Vital Link provider connection UI
|
|
19
|
+
- **Multi-region** — US and EU sandbox/production environments
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install reflex-junction
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or with your preferred package manager:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
uv add reflex-junction
|
|
31
|
+
poetry add reflex-junction
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
### 1. Get an API key
|
|
37
|
+
|
|
38
|
+
Sign up at [tryvital.io](https://tryvital.io/) and grab your API key from the dashboard.
|
|
39
|
+
|
|
40
|
+
### 2. Wrap your app
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import os
|
|
44
|
+
import reflex as rx
|
|
45
|
+
import reflex_junction as junction
|
|
46
|
+
|
|
47
|
+
app = rx.App()
|
|
48
|
+
|
|
49
|
+
junction.wrap_app(
|
|
50
|
+
app,
|
|
51
|
+
api_key=os.environ["JUNCTION_API_KEY"],
|
|
52
|
+
environment="sandbox", # or "production"
|
|
53
|
+
register_user_state=True,
|
|
54
|
+
)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 3. Use Junction state in your pages
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
def health_dashboard() -> rx.Component:
|
|
61
|
+
return rx.container(
|
|
62
|
+
rx.heading("Health Dashboard"),
|
|
63
|
+
rx.text(f"Connected: {junction.JunctionState.has_connections}"),
|
|
64
|
+
rx.foreach(
|
|
65
|
+
junction.JunctionState.connected_sources,
|
|
66
|
+
lambda p: rx.badge(p["name"]),
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Usage
|
|
72
|
+
|
|
73
|
+
### Using `junction_provider` directly
|
|
74
|
+
|
|
75
|
+
For page-level integration instead of app-wide:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
import reflex_junction as junction
|
|
79
|
+
|
|
80
|
+
def health_page() -> rx.Component:
|
|
81
|
+
return junction.junction_provider(
|
|
82
|
+
rx.container(
|
|
83
|
+
rx.text("Connected providers: "),
|
|
84
|
+
rx.text(junction.JunctionState.connected_sources.length()),
|
|
85
|
+
),
|
|
86
|
+
api_key=os.environ["JUNCTION_API_KEY"],
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Webhook support
|
|
91
|
+
|
|
92
|
+
Receive real-time events when health data updates:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
junction.wrap_app(
|
|
96
|
+
app,
|
|
97
|
+
api_key=os.environ["JUNCTION_API_KEY"],
|
|
98
|
+
register_webhooks=True,
|
|
99
|
+
webhook_secret=os.environ["JUNCTION_WEBHOOK_SECRET"],
|
|
100
|
+
webhook_prefix="/junction", # POST /junction/webhooks
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Environment options
|
|
105
|
+
|
|
106
|
+
| Environment | Description |
|
|
107
|
+
|------------------|--------------------|
|
|
108
|
+
| `sandbox` | US sandbox (default) |
|
|
109
|
+
| `production` | US production |
|
|
110
|
+
| `sandbox_eu` | EU sandbox |
|
|
111
|
+
| `production_eu` | EU production |
|
|
112
|
+
|
|
113
|
+
## API Reference
|
|
114
|
+
|
|
115
|
+
### State Classes
|
|
116
|
+
|
|
117
|
+
| Class | Description |
|
|
118
|
+
|-------|-------------|
|
|
119
|
+
| `JunctionState` | Core state — user creation, provider management, Link tokens |
|
|
120
|
+
| `JunctionUser` | Extended state — health data summaries (activity, sleep, body, meals, workouts) |
|
|
121
|
+
|
|
122
|
+
### Configuration Models
|
|
123
|
+
|
|
124
|
+
| Class | Description |
|
|
125
|
+
|-------|-------------|
|
|
126
|
+
| `JunctionConfig` | Environment and region settings |
|
|
127
|
+
| `LinkConfig` | Redirect URL and provider filter for the Link widget |
|
|
128
|
+
| `ProviderInfo` | Provider metadata (name, slug, logo, auth_type) |
|
|
129
|
+
|
|
130
|
+
### Functions
|
|
131
|
+
|
|
132
|
+
| Function | Description |
|
|
133
|
+
|----------|-------------|
|
|
134
|
+
| `wrap_app(app, api_key, ...)` | One-line app integration with optional webhooks |
|
|
135
|
+
| `junction_provider(*children, api_key, ...)` | Component wrapper for page-level integration |
|
|
136
|
+
| `on_load(handlers)` | Wrap page on_load handlers to wait for Junction init |
|
|
137
|
+
| `register_on_auth_change_handler(handler)` | Register handler to fire after Junction initializes |
|
|
138
|
+
| `create_webhook_router(prefix, secret)` | Create a standalone FastAPI webhook router |
|
|
139
|
+
| `register_webhook_api(app, secret, prefix)` | Register webhook endpoint on a Reflex app |
|
|
140
|
+
|
|
141
|
+
### JunctionState Events
|
|
142
|
+
|
|
143
|
+
| Event | Description |
|
|
144
|
+
|-------|-------------|
|
|
145
|
+
| `create_user(client_user_id)` | Create a Junction user mapped to your app's user |
|
|
146
|
+
| `get_connected_providers()` | Fetch connected health data providers |
|
|
147
|
+
| `disconnect_provider(provider)` | Disconnect a specific provider by slug |
|
|
148
|
+
| `refresh_data()` | Trigger data sync from all connected providers |
|
|
149
|
+
| `create_link_token(redirect_url)` | Generate a Link widget token |
|
|
150
|
+
|
|
151
|
+
See the [full documentation](https://syntropy-health.github.io/reflex-junction/) for detailed guides.
|
|
152
|
+
|
|
153
|
+
## Contributing
|
|
154
|
+
|
|
155
|
+
Contributions welcome! We use [Taskfile](https://taskfile.dev/) for common tasks:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
task install # Install dev dependencies + pre-commit
|
|
159
|
+
task test # Run lint + typecheck + pytest
|
|
160
|
+
task run # Run the demo app
|
|
161
|
+
task run-docs # Serve docs locally at localhost:9000
|
|
162
|
+
task bump-patch # Bump patch version (bug fix)
|
|
163
|
+
task bump-minor # Bump minor version (new feature)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Workflow: Fork → feature branch → add tests → submit PR.
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
[MIT](LICENSE) — Copyright (c) 2025 Syntropy Health
|