csiapps 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.
@@ -0,0 +1,17 @@
1
+ {
2
+ "version": "0.0.1",
3
+ "configurations": [
4
+ {
5
+ "name": "example-app",
6
+ "runtimeExecutable": "uv",
7
+ "runtimeArgs": ["run", "shiny", "run", "--port", "8123", "examples/app.py"],
8
+ "port": 8123
9
+ },
10
+ {
11
+ "name": "docs",
12
+ "runtimeExecutable": "uv",
13
+ "runtimeArgs": ["run", "--group", "docs", "mkdocs", "serve", "-a", "localhost:8000"],
14
+ "port": 8000
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,25 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.10", "3.11", "3.12"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - name: Install uv
17
+ uses: astral-sh/setup-uv@v3
18
+ - name: Set up Python ${{ matrix.python-version }}
19
+ run: uv python install ${{ matrix.python-version }}
20
+ - name: Install deps
21
+ run: uv sync --python ${{ matrix.python-version }}
22
+ - name: Lint
23
+ run: uv run ruff check .
24
+ - name: Test
25
+ run: uv run pytest
@@ -0,0 +1,19 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ # Needed for mkdocs gh-deploy to push the built site to the gh-pages branch.
8
+ permissions:
9
+ contents: write
10
+
11
+ jobs:
12
+ deploy:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - name: Install uv
17
+ uses: astral-sh/setup-uv@v3
18
+ - name: Build and deploy docs
19
+ run: uv run --group docs mkdocs gh-deploy --force
@@ -0,0 +1,26 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+
9
+ # Environments
10
+ .venv/
11
+ venv/
12
+ .env
13
+
14
+ # Docs / build output
15
+ site/
16
+
17
+ # Tooling caches
18
+ .pytest_cache/
19
+ .ruff_cache/
20
+ .mypy_cache/
21
+
22
+ # uv
23
+ uv.lock
24
+
25
+ # OS
26
+ .DS_Store
csiapps-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 csiapps authors
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.
csiapps-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: csiapps
3
+ Version: 0.1.0
4
+ Summary: Helper functions and utilities for CSI data warehouse ingestion and Shiny web applications (Python port of the csiapps R package).
5
+ Project-URL: Homepage, https://github.com/CSIOntario/csiapps-py
6
+ Project-URL: Issues, https://github.com/CSIOntario/csiapps-py/issues
7
+ Author: Kyu Min Shim
8
+ Author-email: David Awosoga <dawosoga@csiontario.ca>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 csiapps authors
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: csi,csiontario,oauth2,shiny,warehouse
32
+ Requires-Python: >=3.10
33
+ Requires-Dist: faker>=25
34
+ Requires-Dist: httpx>=0.27
35
+ Requires-Dist: jsonschema>=4.21
36
+ Requires-Dist: shiny>=1.0
37
+ Description-Content-Type: text/markdown
38
+
39
+ # csiapps (Python)
40
+
41
+ Python port of the CSIO [`csiapps`](https://github.com/CSIOntario/csiapps) R
42
+ package. Helper functions and utilities for CSI data warehouse ingestion and
43
+ [Shiny for Python](https://shiny.posit.co/py/) web applications.
44
+
45
+ Full feature parity with the R package: the API client (`make_request`,
46
+ `fetch_*`), the local sandbox (schema → ingest → retrieve, plus a dummy
47
+ registration registry), and the Shiny app wrappers (`ui_wrapper`,
48
+ `server_wrapper`). **Sandbox mode is on by default** so nothing hits production
49
+ by accident.
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ pip install csiapps
55
+ ```
56
+
57
+ ## Quickstart
58
+
59
+ ```python
60
+ import csiapps
61
+
62
+ csiapps.register_sandbox_schema("demo", {
63
+ "type": "object", "required": ["id"],
64
+ "properties": {"id": {"type": "string"}},
65
+ })
66
+ csiapps.make_request("api/warehouse/ingestion/primary/", method="POST",
67
+ body={"source": "demo", "records": [{"id": "a1"}], "subject_field": "id"})
68
+ page = csiapps.make_request("api/warehouse/data-records", query={"source_uuid": "demo"})
69
+ print(page["count"]) # 1
70
+ ```
71
+
72
+ See the [cross-language documentation](https://csiontario.github.io/csiapps-py/)
73
+ (R and Python side by side, plus a [parity checklist](https://csiontario.github.io/csiapps-py/parity/))
74
+ and the runnable [`examples/`](examples/) (`warehouse_ingest.py`, `app.py`).
75
+
76
+ ## Development
77
+
78
+ Uses [uv](https://docs.astral.sh/uv/).
79
+
80
+ ```bash
81
+ uv sync # install deps + dev tools
82
+ uv run pytest # run tests
83
+ uv run ruff check . # lint
84
+ uv run --group docs mkdocs serve # preview docs
85
+ uv build # build sdist + wheel
86
+ ```
@@ -0,0 +1,184 @@
1
+ # csiapps: R → Python porting plan
2
+
3
+ Port of the CSIO [`csiapps`](https://github.com/CSIOntario/csiapps) R package to
4
+ Python, with full feature parity. Target web framework for the app-wrapper
5
+ layer: **[Shiny for Python](https://shiny.posit.co/py/)** (chosen because it is a
6
+ near-1:1 match for the R Shiny wrapper).
7
+
8
+ ## The package is three layers
9
+
10
+ | Layer | R source | Nature | Port difficulty |
11
+ |---|---|---|---|
12
+ | **API client** | `R/utils.R` | HTTP + OAuth2 PKCE + JSON, pure logic | Mechanical |
13
+ | **Sandbox** | `R/sandbox.R` | In-memory warehouse emulation, JSON-schema validation, dummy data registry | Mechanical (two dep swaps) |
14
+ | **App wrapper** | `R/shiny.R` | Shiny UI/server wrapper, navbar/footer chrome, in-app OAuth redirect | Mechanical *because* target is Shiny for Python |
15
+
16
+ ## Repository decision
17
+
18
+ Separate repo (`csiapps-py`, publishing package `csiapps` to PyPI) rather than a
19
+ monorepo or mixing into the R repo. R packaging artifacts (`DESCRIPTION`,
20
+ `NAMESPACE`, `man/`, roxygen, pkgdown) don't coexist cleanly with
21
+ `pyproject.toml`/pytest/ruff. The two implementations stay in sync via the
22
+ **shared REST API contract + JSON schemas**, not shared code.
23
+
24
+ ## Dependency mapping
25
+
26
+ Most R deps collapse to Python stdlib.
27
+
28
+ | R dependency | Used for | Python replacement |
29
+ |---|---|---|
30
+ | `httr2` | HTTP requests, retries, pagination | `httpx` |
31
+ | `httr2` OAuth2 PKCE helpers | auth flow | **stdlib** `secrets` + `hashlib.sha256` + `base64.urlsafe_b64encode` |
32
+ | `jsonlite` | JSON parse/serialize | **stdlib** `json` |
33
+ | `openssl` | base64, `rand_bytes` | **stdlib** `base64`, `secrets` |
34
+ | `jsonvalidate` (Ajv) | sandbox schema validation | `jsonschema` |
35
+ | `babynames` | random dummy athlete names | `faker` |
36
+ | `shiny` / `shinyjs` | app wrapper | `shiny` (Shiny for Python); `shinyjs` → custom-message JS handler |
37
+
38
+ Runtime deps: `httpx`, `jsonschema`, `faker`, `shiny`. Everything else is stdlib.
39
+
40
+ ## Function map (mirrors the R `NAMESPACE`)
41
+
42
+ All exported functions carry over; names stay `snake_case`.
43
+
44
+ **config.py** — `set_institute`, `is_sandbox_mode`. R uses a `package_state`
45
+ environment + `options(csiapps.sandbox=)` + `CSIAPPS_ENV`. Python: module-level
46
+ state object + `CSIAPPS_ENV` env var. Do **not** build a Config class — mirror
47
+ the R global.
48
+
49
+ **auth.py** — `check_secrets`, PKCE helpers (`pkce_state_encode/decode`,
50
+ base64url), `exchange_code_for_token`. All stdlib, ~30 lines of PKCE.
51
+
52
+ **client.py** — `make_request` (with `paginate`, `sandbox` dispatch),
53
+ `fetch_org_options`, `fetch_profiles` (auto-paginate), `fetch_profile`.
54
+ Two-tier token resolution `.current_token()`: per-session token first, then
55
+ `CSIAPPS_ACCESS_TOKEN` env var.
56
+
57
+ **sandbox.py** — `register_sandbox_schema`, `clear_sandbox`, `browse_sandbox`,
58
+ `create_sport_org`, `create_profile`, plus the internal request router
59
+ (`data-sources/{uuid}`, `ingestion/primary`, `data-records`) and
60
+ `sandbox_ingest`. Keep **read-time** subject resolution so athletes registered
61
+ after ingestion backfill.
62
+
63
+ **app.py** — `ui_wrapper`, `server_wrapper`, navbar/footer/CSS chrome, sandbox
64
+ banner, `/me` load, logout re-seed. (`global_wrapper` was ported then dropped in
65
+ the ponytail audit — a no-op in Python; module scope covers the R use case.)
66
+
67
+ ### Shiny R → Shiny for Python
68
+
69
+ | R (Shiny) | Python (Shiny for Python) |
70
+ |---|---|
71
+ | `reactiveVal(NULL)` | `reactive.value(None)` |
72
+ | `observe({...})` | `@reactive.effect` |
73
+ | `observeEvent(x(), {...})` | `@reactive.effect` + `@reactive.event(x)` |
74
+ | `renderUI` / `uiOutput` | `@render.ui` / `ui.output_ui` |
75
+ | `tags$div`, `HTML`, `tagList`, `fluidPage` | `ui.tags.div`, `ui.HTML`, `ui.TagList`, `ui.page_fluid` |
76
+ | `session$sendCustomMessage(...)` | `session.send_custom_message` |
77
+ | `getDefaultReactiveDomain()` | `shiny.session.get_current_session()` |
78
+ | `parseQueryString(session$clientData$url_search)` | `session.clientdata.url_search()` |
79
+
80
+ Two gotchas:
81
+
82
+ * **`shinyjs` has no Python port.** Only used for
83
+ `runjs("window.location.href = ...")`. Replace with the same custom-message
84
+ handler the redirect already uses — no dependency; `useShinyjs()` drops out.
85
+ * **Per-session token** (`session$userData$csiapps_token`): implemented as a
86
+ `WeakKeyDictionary` keyed on the Shiny session (`client.set_session_token`),
87
+ read back by `client.current_token()` via `shiny.session.get_current_session()`,
88
+ falling back to `CSIAPPS_ACCESS_TOKEN`. (A `contextvar`, tried in phase 3, does
89
+ **not** work: Shiny reactive contexts don't propagate it across effects.)
90
+
91
+ ## Verification strategy
92
+
93
+ The R package's `testthat` suite is the spec. Port each test to `pytest`
94
+ alongside the module it covers:
95
+
96
+ | R test | Python |
97
+ |---|---|
98
+ | `tests/testthat/test-sandbox.R` | `tests/test_sandbox.py` |
99
+ | `tests/testthat/test-sandbox-wrapper.R` | `tests/test_sandbox_wrapper.py` |
100
+ | `tests/testthat/test-sandbox-registry.R` | `tests/test_sandbox_registry.py` |
101
+ | `tests/testthat/test-sandbox-mode.R` | `tests/test_sandbox_mode.py` |
102
+
103
+ ## Phases
104
+
105
+ - [x] **Phase 1 — Scaffold.** New repo, `pyproject.toml` (uv/hatchling), CI
106
+ (pytest + ruff), package skeleton, this plan.
107
+ - [x] **Phase 2 — config + auth.** `config.py` (set_institute, is_sandbox_mode,
108
+ set_sandbox_mode, URL helpers) + `auth.py` (PKCE stdlib, token exchange,
109
+ check_secrets). Ported the `is_sandbox_mode` portion of `test-sandbox-mode.R`
110
+ plus PKCE round-trip / token-exchange / check_secrets tests (16 passing).
111
+ - [x] **Phase 3 — client.** `client.py`: `make_request` (retry + pagination),
112
+ `fetch_org_options`, `fetch_profiles` (auto-paginate), `fetch_profile`, and
113
+ per-session `current_token()` (contextvar → `CSIAPPS_ACCESS_TOKEN`). Sandbox
114
+ branches delegate to `csiapps.sandbox` internals (lazy import) landing in
115
+ phase 4. HTTP paths tested with `respx` (27 passing).
116
+
117
+ > **Phase 4 must provide these in `sandbox.py`:** `_make_sandbox_request(...)`,
118
+ > `_sandbox_org_options()`, `_sandbox_profiles(sport_org_id)`,
119
+ > `_sandbox_profile(profile_id)` — the delegation targets `client.py` calls.
120
+ > `flatten_record` (R internal, unexported, uncalled within the package) was
121
+ > **skipped**; add it only if a consuming app actually needs it.
122
+ - [x] **Phase 4 — sandbox.** `sandbox.py`: request router, ingest + Draft-7
123
+ `jsonschema` validation, dummy sport-org/athlete registry (`faker` names),
124
+ read-time subject resolution, and the four `_sandbox_*` delegation functions
125
+ `client.py` calls. Ported `test-sandbox.R`, `test-sandbox-registry.R`, the
126
+ `make_request` routing + `fetch_*` sandbox cases (69 passing total). Validator
127
+ wording adapted Ajv→jsonschema ("too short" vs "fewer than 10 characters").
128
+ `flatten_record` and its test remain skipped (see phase 3 note).
129
+ - [x] **Phase 5 — app wrapper.** `app.py` (Shiny for Python): `ui_wrapper`
130
+ (navbar/footer/chrome, sandbox banner, auth-status), `server_wrapper` (OAuth2
131
+ PKCE login + simulated sandbox login, per-session token, `/me` load, logout),
132
+ `global_wrapper`. `shinyjs` dropped (replaced by a `csip_reset`
133
+ custom-message handler); message-sending effects are `async`. Ported the
134
+ `ui_wrapper`/`server_wrapper`/`global_wrapper` cases from
135
+ `test-sandbox-wrapper.R` (77 passing); the deep `testServer` reactive cases
136
+ have no simple Shiny-for-Python equivalent, so sandbox-seed + auth-status
137
+ logic is covered via extracted pure helpers, and a real `App` is smoke-built.
138
+ Phase 3's token mechanism was corrected here (contextvar → session
139
+ `WeakKeyDictionary`).
140
+ - [x] **Phase 6 — docs + packaging.** mkdocs-material + mkdocstrings site
141
+ (`mkdocs.yml`, `docs/`), runnable `examples/` (`warehouse_ingest.py`,
142
+ `app.py`), README quickstart. `uv build` produces sdist + wheel; wheel
143
+ verified to install and import in a clean venv.
144
+ **PyPI publish is intentionally NOT done here** — it is an outward-facing
145
+ release and requires credentials the assistant cannot handle. Publish with
146
+ `uv publish` (or `twine upload dist/*`) when ready.
147
+
148
+ Phases 1–4 are framework-independent and safe to build in any order after 1.
149
+
150
+ ## Dashboard dogfooding findings (fixed)
151
+
152
+ Building a real Shiny for Python dashboard against the package surfaced three
153
+ issues from common workflows, all fixed:
154
+
155
+ 1. **`fetch_org_options` shape** — returned R's `[{"label","value"}]`, which
156
+ crashed `ui.input_select` (`TypeError: unhashable type: 'dict'`). Now returns
157
+ a `{value: label}` dict that feeds `input_select(choices=...)` directly.
158
+ Deliberate divergence from R (whose shape suited `selectInput`).
159
+ 2. **No path from nested results to a table** — `fetch_profiles` returns deeply
160
+ nested dicts; `render.DataGrid` rejected them ("Unsupported dataframe type").
161
+ Added `flatten_profile` producing scalar rows; docs show wrapping in a
162
+ pandas/polars frame (kept out of package deps).
163
+ 3. **Fixed-bottom footer overlapped content** — the R wrapper's
164
+ `padding-bottom: 80px` was dropped in the port; restored in `ui_wrapper`.
165
+
166
+ ## Ponytail audit cleanup
167
+
168
+ Repo-wide over-engineering pass, all applied: dropped `global_wrapper` (no-op in
169
+ Python) and `flatten_record` (no caller; only `flatten_profile` is used); deleted
170
+ `config.clear_token` (dead) and `tests/test_smoke.py` (redundant vs 78 real
171
+ tests); hoisted the duplicated `_message` helper into `config`; inlined the
172
+ `_rmtree` wrapper to `shutil.rmtree`. ~55 lines removed, no dep changes.
173
+
174
+ ## Parity caveats carried over from R
175
+
176
+ * **Validation parity is approximate.** Sandbox ingestion validates against the
177
+ JSON Schema with `jsonschema` (Python) where R used Ajv via `jsonvalidate`.
178
+ Validators differ on edge cases (e.g. `format` keyword enforcement), and the
179
+ real server enforces things the schema can't (subject resolution against
180
+ registered profiles, duplicate/dataset handling, token permissions). A green
181
+ sandbox run is **necessary but not sufficient** for production acceptance.
182
+ * **Subject linkage** is emulated only for athletes registered with
183
+ `create_profile()`, resolved at read time; unresolved → `subject = None`
184
+ (production would reject).
@@ -0,0 +1,48 @@
1
+ # csiapps (Python)
2
+
3
+ Python port of the CSIO [`csiapps`](https://github.com/CSIOntario/csiapps) R
4
+ package. Helper functions and utilities for CSI data warehouse ingestion and
5
+ [Shiny for Python](https://shiny.posit.co/py/) web applications.
6
+
7
+ Full feature parity with the R package: the API client (`make_request`,
8
+ `fetch_*`), the local sandbox (schema → ingest → retrieve, plus a dummy
9
+ registration registry), and the Shiny app wrappers (`ui_wrapper`,
10
+ `server_wrapper`). **Sandbox mode is on by default** so nothing hits production
11
+ by accident.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install csiapps
17
+ ```
18
+
19
+ ## Quickstart
20
+
21
+ ```python
22
+ import csiapps
23
+
24
+ csiapps.register_sandbox_schema("demo", {
25
+ "type": "object", "required": ["id"],
26
+ "properties": {"id": {"type": "string"}},
27
+ })
28
+ csiapps.make_request("api/warehouse/ingestion/primary/", method="POST",
29
+ body={"source": "demo", "records": [{"id": "a1"}], "subject_field": "id"})
30
+ page = csiapps.make_request("api/warehouse/data-records", query={"source_uuid": "demo"})
31
+ print(page["count"]) # 1
32
+ ```
33
+
34
+ See the [cross-language documentation](https://csiontario.github.io/csiapps-py/)
35
+ (R and Python side by side, plus a [parity checklist](https://csiontario.github.io/csiapps-py/parity/))
36
+ and the runnable [`examples/`](examples/) (`warehouse_ingest.py`, `app.py`).
37
+
38
+ ## Development
39
+
40
+ Uses [uv](https://docs.astral.sh/uv/).
41
+
42
+ ```bash
43
+ uv sync # install deps + dev tools
44
+ uv run pytest # run tests
45
+ uv run ruff check . # lint
46
+ uv run --group docs mkdocs serve # preview docs
47
+ uv build # build sdist + wheel
48
+ ```
@@ -0,0 +1,37 @@
1
+ # Python API reference
2
+
3
+ Everything exported from the top-level `csiapps` Python package. For the R
4
+ package, see the [R reference](https://csiontario.github.io/csiapps/reference/)
5
+ (pkgdown); for how the two map onto each other, see the
6
+ [parity checklist](parity.md).
7
+
8
+ ## Configuration
9
+
10
+ ::: csiapps.config.set_institute
11
+ ::: csiapps.config.is_sandbox_mode
12
+ ::: csiapps.config.set_sandbox_mode
13
+
14
+ ## Authentication
15
+
16
+ ::: csiapps.auth.check_secrets
17
+
18
+ ## Client
19
+
20
+ ::: csiapps.client.make_request
21
+ ::: csiapps.client.fetch_org_options
22
+ ::: csiapps.client.fetch_profiles
23
+ ::: csiapps.client.fetch_profile
24
+ ::: csiapps.client.flatten_profile
25
+
26
+ ## Sandbox
27
+
28
+ ::: csiapps.sandbox.register_sandbox_schema
29
+ ::: csiapps.sandbox.create_sport_org
30
+ ::: csiapps.sandbox.create_profile
31
+ ::: csiapps.sandbox.clear_sandbox
32
+ ::: csiapps.sandbox.browse_sandbox
33
+
34
+ ## App wrappers
35
+
36
+ ::: csiapps.app.ui_wrapper
37
+ ::: csiapps.app.server_wrapper
@@ -0,0 +1,78 @@
1
+ # csiapps
2
+
3
+ Helper functions and utilities for CSI data warehouse ingestion and
4
+ [Shiny](https://shiny.posit.co/) web applications. `csiapps` ships as **two
5
+ packages with full feature parity** — one for R, one for Python — so a team can
6
+ work in either language against the same CSIAPPS warehouse and registration API.
7
+
8
+ This site documents **both**: every tutorial shows the R and the Python code
9
+ side by side. Pick your language with the tabs and it stays selected across the
10
+ whole page.
11
+
12
+ The library has three layers:
13
+
14
+ - **API client** — `make_request`, `fetch_org_options`, `fetch_profiles`,
15
+ `fetch_profile`, with OAuth2 PKCE auth.
16
+ - **Sandbox** — a local, in-memory emulation of the warehouse and registration
17
+ API so you can develop the full schema → ingest → retrieve workflow with no
18
+ network and no credentials. **Sandbox mode is on by default.**
19
+ - **App wrapper** — `ui_wrapper` / `server_wrapper` add a consistent CSI
20
+ navbar/footer and handle authentication for a Shiny app.
21
+
22
+ ## Installation
23
+
24
+ === "R"
25
+
26
+ ```r
27
+ # install.packages("remotes")
28
+ remotes::install_github("CSIOntario/csiapps")
29
+ ```
30
+
31
+ === "Python"
32
+
33
+ ```bash
34
+ pip install csiapps
35
+ ```
36
+
37
+ ## Sandbox mode is the default
38
+
39
+ Every call routes to the local sandbox until you explicitly turn it off, so you
40
+ never hit the production warehouse by accident:
41
+
42
+ === "R"
43
+
44
+ ```r
45
+ library(csiapps)
46
+
47
+ is_sandbox_mode() # TRUE
48
+
49
+ # Turn it off for deployment:
50
+ options(csiapps.sandbox = FALSE) # or set CSIAPPS_ENV=production
51
+ ```
52
+
53
+ === "Python"
54
+
55
+ ```python
56
+ import csiapps
57
+
58
+ csiapps.is_sandbox_mode() # True
59
+
60
+ # Turn it off for deployment:
61
+ csiapps.set_sandbox_mode(False) # or set CSIAPPS_ENV=production
62
+ ```
63
+
64
+ ## Where to go next
65
+
66
+ - **[Shiny apps](shiny-apps.md)** — wrap an app's UI and server with CSIAPPS
67
+ authentication and chrome.
68
+ - **[REST API](rest-api.md)** — `make_request` and the registration helpers.
69
+ - **[Sandbox mode](sandbox.md)** — the full local warehouse/registration
70
+ workflow.
71
+ - **[Parity checklist](parity.md)** — how each R function maps to its Python
72
+ equivalent.
73
+
74
+ ## Function reference
75
+
76
+ - **Python:** [Python reference](api.md) (on this site).
77
+ - **R:** [R reference](https://csiontario.github.io/csiapps/reference/)
78
+ (pkgdown).
@@ -0,0 +1,85 @@
1
+ # Parity checklist
2
+
3
+ This page is a translation guide for the operational parity between the R and
4
+ Python versions of `csiapps`. Each row is a capability; the R and Python columns
5
+ give the function(s) that provide it. An empty cell marks a capability that
6
+ exists in only one language (see the notes below for why).
7
+
8
+ The two packages have **full feature parity** — every capability is available in
9
+ both — with two exceptions that are intentional and explained under
10
+ [Intentional divergences](#intentional-divergences).
11
+
12
+ ## Configuration
13
+
14
+ | Capability | R | Python |
15
+ |---|---|---|
16
+ | Set the target institute | `set_institute()` | `set_institute()` |
17
+ | Check whether sandbox mode is on | `is_sandbox_mode()` | `is_sandbox_mode()` |
18
+ | Force sandbox mode on/off | `options(csiapps.sandbox = )` | `set_sandbox_mode()` |
19
+ | Check auth environment / secrets | `check_secrets()` | `check_secrets()` |
20
+
21
+ ## REST API client
22
+
23
+ | Capability | R | Python |
24
+ |---|---|---|
25
+ | Authenticated request to any endpoint | `make_request()` | `make_request()` |
26
+ | Fetch organisation options | `fetch_org_options()` | `fetch_org_options()` |
27
+ | Fetch profiles (auto-paginating) | `fetch_profiles()` | `fetch_profiles()` |
28
+ | Fetch a single profile by id | `fetch_profile()` | `fetch_profile()` |
29
+ | Flatten a profile into a table row | | `flatten_profile()` |
30
+
31
+ ## Sandbox
32
+
33
+ | Capability | R | Python |
34
+ |---|---|---|
35
+ | Register a JSON schema for a source | `register_sandbox_schema()` | `register_sandbox_schema()` |
36
+ | Create a dummy sport organization | `create_sport_org()` | `create_sport_org()` |
37
+ | Create dummy athlete profiles | `create_profile()` | `create_profile()` |
38
+ | Clear sandbox state | `clear_sandbox()` | `clear_sandbox()` |
39
+ | Open the sandbox payload folder | `browse_sandbox()` | `browse_sandbox()` |
40
+
41
+ ## Shiny app wrappers
42
+
43
+ | Capability | R | Python |
44
+ |---|---|---|
45
+ | Wrap app UI (navbar/footer/auth status) | `ui_wrapper()` | `ui_wrapper()` |
46
+ | Wrap app server (OAuth2 / sandbox login) | `server_wrapper()` | `server_wrapper()` |
47
+ | Make globals visible to the server | `global_wrapper()` | |
48
+
49
+ ## Intentional divergences
50
+
51
+ These are the only differences in the public surface, and each exists for a
52
+ concrete reason:
53
+
54
+ - **`global_wrapper()` is R-only.** It evaluates a code block in R's global
55
+ environment so top-level objects are reachable from the `server` function — an
56
+ R Shiny scoping concern. In Shiny for Python a module-level assignment is
57
+ already captured by the server closure, so no equivalent is needed.
58
+ - **`flatten_profile()` is Python-only.** Shiny for Python's `render.data_frame`
59
+ rejects the nested dicts `fetch_profiles()` returns, so this helper flattens
60
+ one profile into scalar fields for a table. R users flatten inline with
61
+ `data.frame()` / `do.call(rbind, ...)`, so no dedicated helper ships in R.
62
+
63
+ Beyond the public surface, a few behaviours differ by language idiom or as a
64
+ deliberate hardening — same capability, adapted shape:
65
+
66
+ - **Sandbox toggle mechanism.** R uses the `csiapps.sandbox` **option**
67
+ (`options(csiapps.sandbox = FALSE)`); Python has no options system, so it uses
68
+ the `set_sandbox_mode()` function. Both also respect `CSIAPPS_ENV=production`.
69
+ - **`fetch_org_options()` return shape.** R returns `label`/`value` pairs for
70
+ `selectInput()`; Python returns a `{value: label}` dict for
71
+ `ui.input_select(choices = )`. Same data, framework-native shape.
72
+ - **`fetch_profile()` argument order.** R is `fetch_profile(token, profile_id)`;
73
+ Python puts the required argument first: `fetch_profile(profile_id, token)`.
74
+ Equivalent when called by keyword.
75
+ - **Schema validator engine.** Sandbox ingestion validates with Ajv (via
76
+ `jsonvalidate`) in R and `jsonschema` (Draft 7) in Python. Error wording and
77
+ some `format`-keyword edge cases differ.
78
+ - **`fetch_profiles()` pagination guard.** The Python port adds a `max_pages`
79
+ bound with cycle detection and a truncation warning; the R version paginates
80
+ until the API stops returning a `next` link. Python is a hardened superset.
81
+
82
+ ## Function reference
83
+
84
+ - **R:** [R reference](https://csiontario.github.io/csiapps/reference/) (pkgdown)
85
+ - **Python:** [Python reference](api.md) (on this site)