Metadata-Version: 2.4
Name: csiapps
Version: 0.3.1
Summary: Helper functions and utilities for CSI data warehouse ingestion and Shiny or Dash web applications (Python port of the csiapps R package).
Project-URL: Homepage, https://github.com/CSIOntario/csiapps-py
Project-URL: Issues, https://github.com/CSIOntario/csiapps-py/issues
Author-email: David Awosoga <dawosoga@csiontario.ca>, Kyu Min Shim <kmshim@uwaterloo.ca>
License: MIT License
        
        Copyright (c) 2026 csiapps authors
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: csi,csiontario,dash,oauth2,shiny,warehouse
Requires-Python: >=3.10
Requires-Dist: faker>=25
Requires-Dist: httpx>=0.27
Requires-Dist: jsonschema>=4.21
Provides-Extra: dash
Requires-Dist: dash-auth>=2.3; extra == 'dash'
Requires-Dist: dash>=3.0; extra == 'dash'
Provides-Extra: shiny
Requires-Dist: shiny>=1.0; extra == 'shiny'
Description-Content-Type: text/markdown

# csiapps (Python)

Python port of the CSIO [`csiapps`](https://github.com/CSIOntario/csiapps-r) R
package. Helper functions and utilities for CSI data warehouse ingestion and
[Shiny for Python](https://shiny.posit.co/py/) or [Dash](https://dash.plotly.com/)
web applications.

Full feature parity with the R package: the API client (`make_request`,
`fetch_*`), the local sandbox (schema → ingest → retrieve, plus a dummy
registration registry), and per-framework app wrappers for Shiny
([`csiapps.shiny`](#shiny-apps): `ui_wrapper`, `server_wrapper`) and Dash
([`csiapps.dash`](#dash-apps): `layout_wrapper`, `attach`). **Sandbox mode is on
by default** so nothing hits production by accident.

The core — client, sandbox, and OAuth2 PKCE helpers — is framework-independent
and depends on neither framework, so `import csiapps` pulls in no web framework
at all. The two frameworks are symmetric, mutually exclusive optional extras:
each app installs and imports only the one it uses.

## Installation

```bash
pip install csiapps            # core only: ingestion + sandbox, no web framework
```

For an app, add the extra for your framework:

```bash
pip install 'csiapps[shiny]'   # Shiny app
pip install 'csiapps[dash]'    # Dash app
```

> **Migrating from 0.2.x (breaking):** Shiny is no longer a hard dependency and
> the wrappers moved off the top-level package. A Shiny app now installs
> `csiapps[shiny]` and imports `from csiapps.shiny import ui_wrapper,
> server_wrapper` instead of `from csiapps import ui_wrapper, server_wrapper`.
> The core API (`make_request`, `fetch_*`, `token_ready`, the sandbox helpers)
> is unchanged.

## Quickstart

```python
import csiapps

csiapps.register_sandbox_schema("demo", {
    "type": "object", "required": ["id"],
    "properties": {"id": {"type": "string"}},
})
csiapps.make_request("api/warehouse/ingestion/primary/", method="POST",
    body={"source": "demo", "records": [{"id": "a1"}], "subject_field": "id"})
page = csiapps.make_request("api/warehouse/data-records", query={"source_uuid": "demo"})
print(page["count"])   # 1
```

## Shiny apps

Install `csiapps[shiny]` and wrap the UI and server. `ui_wrapper` adds the CSI
navbar, footer, auth-status line and sandbox banner; `server_wrapper` runs the
OAuth2 PKCE login (simulated in sandbox mode) and stores the per-session token
so `fetch_*` helpers resolve it automatically.

```python
from shiny import App, reactive, ui
from csiapps.shiny import server_wrapper, ui_wrapper
import csiapps

app_ui = ui_wrapper(
    ui.input_select("org", "Organisation", choices={}),
)

def app_server(input, output, session):
    @reactive.effect
    def _load_orgs():             # gates itself until login; no token handling
        ui.update_select("org", choices=csiapps.fetch_org_options())

app = App(app_ui, server_wrapper(app_server))
```

See the runnable [`examples/app.py`](examples/app.py).

## Dash apps

`attach()` puts the OAuth2 PKCE flow in front of every route, so the redirect
happens before Dash renders and callbacks never run for an unauthenticated user.
No `dcc.Interval` login bounce, no per-page `Authorization` header.

```python
from dash import Dash, Input, Output, callback, dcc
from csiapps.dash import attach, layout_wrapper
import csiapps

app = Dash(__name__)
attach(app)                       # auth guard + chrome; no-op in sandbox mode

app.layout = layout_wrapper(      # CSI navbar, footer, sandbox banner
    dcc.Dropdown(id="org", options=csiapps.fetch_org_options()),
)

@callback(Output("people", "data"), Input("org", "value"))
def load(org):                    # no token handling; the helper resolves it
    return [csiapps.flatten_profile(p)
            for p in csiapps.fetch_profiles(filters={"sport_org_id": org})]
```

In production set `CSIAPPS_CLIENT_ID`, `CSIAPPS_CLIENT_SECRET`,
`CSIAPPS_REDIRECT_URI` (pointing at `/redirect`), and
`CSIAPPS_SECRET_KEY` — a stable ≥32-character value generated separately for
each deployed Dash app and shared by that app's workers. It signs the session
cookie. `attach()` raises at startup if it is missing, since the alternative is
an unexplained login loop.

Sandbox mode skips the guard entirely, so a Dash app is fully developable with
no credentials and no network. Deploy sandbox apps with `--workers 1`: the
sandbox registry is per-process, so extra workers each invent their own dummy
athletes.

See the [cross-language documentation](https://csiontario.github.io/csiapps/)
(R and Python side by side, plus a [parity checklist](https://csiontario.github.io/csiapps/parity/))
and the runnable [`examples/`](examples/) (`warehouse_ingest.py`, `app.py`,
`dash_app.py`).

## Development

Uses [uv](https://docs.astral.sh/uv/).

```bash
uv sync                        # install deps + dev tools (both framework extras)
uv run pytest                  # run tests (framework suites skip if their extra is absent)
uv run ruff check .            # lint
uv build                       # build sdist + wheel

# Prove the isolation locally, matching the CI jobs:
uv sync --no-group shiny-tests --no-group dash-tests && uv run --no-sync pytest  # core only
uv sync --no-group dash-tests  && uv run --no-sync pytest                        # Shiny only
uv sync --no-group shiny-tests && uv run --no-sync pytest                        # Dash only
uv sync                                                                          # restore both
```

The documentation site lives in its own repo,
[`csiapps`](https://github.com/CSIOntario/csiapps) — there is no `mkdocs.yml`
here. Its Python API reference autodocs from this package's docstrings on
`main`, so a docstring change lands on the site the next time that repo is
pushed. To preview, clone it alongside this one and run `mkdocs serve` there.

Shipping a change to the package? Follow
[Releasing csiapps](https://csiontario.github.io/csiapps/releasing/).
