Metadata-Version: 2.4
Name: prisma_api
Version: 0.4.7
Summary: A package for interacting with PrISMa Platform APIs. PrISMa is a holistic system for numerical synthesis and assessment of Metal-Organic Frameworks and their applications, for Direct Air Capture of CO2. For more information, see: https://prisma.hw.ac.uk.
Author-email: RCCS-CaptureTeam <team@prisma-platform.org>
License: GNUv3
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.21.0
Requires-Dist: pandas>=1.3.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: platformdirs>=2.5.0
Requires-Dist: requests>=2.27.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: responses>=0.25; extra == "dev"
Dynamic: license-file

# PrISMa API

[![CI](https://github.com/RCCS-CaptureTeam/prisma_api/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/RCCS-CaptureTeam/prisma_api/actions/workflows/ci.yml)

Python client for the PrISMa platform APIs.

This package provides:

- legacy v1 wrappers on the main `api` object
- a larger v2 REST client on `api.v2`
- local config-based API-key management

PrISMa is a platform for numerical synthesis and assessment of metal-organic frameworks and related carbon-capture workflows.

## Installation

### From PyPI

```bash
pip install prisma_api
```

### Requirements

- Python 3.10+
- Runtime dependencies are installed automatically from `pyproject.toml`

## Authentication and configuration

The client uses an API key stored in a local config file.

### First-time setup

```python
import prisma_api

api = prisma_api.init()
```

On first use, the package creates a config file and prompts for an API key.
Without a terminal (e.g. CI) it raises `RuntimeError` naming the environment
variables to set instead (see below).

`init()` prints whether it connected to production or a local dev server.
Production is the default; use `prisma_api.init(local_dev=True)` for a local
dev server on `localhost:<dev_host_port>`.

### Upgrades

```python
pip install --upgrade prisma_api
```

### Find the config path

```python
from prisma_api.config import locate_config

print(locate_config())
```

Typical locations:

- macOS/Linux: `~/.config/prisma_api/config.yaml`
- Windows: `%APPDATA%/prisma-api/prisma_api/config.yaml`

### Create or update the config programmatically

```python
from prisma_api.config import create_config_file

create_config_file(api_key="YOUR_API_KEY_HERE")
```

### Environment-variable mode

When no config file exists, `init()` reads these environment variables
instead (nothing is written to disk):

```bash
export PRISMA_API_KEY="YOUR_API_KEY_HERE"
export PRISMA_API_DEV_API_KEY="YOUR_DEV_KEY"     # optional; defaults to PRISMA_API_KEY
export PRISMA_API_DEV_HOST_PORT="8000"           # optional; used with local_dev=True
```

To ignore any config file and use only the environment variables:

```python
import prisma_api

api = prisma_api.init(use_config_file=False)
```

The target server is chosen only by `init(local_dev=...)`; there is no
environment variable for it.

## Quick start

```python
import prisma_api

api = prisma_api.init()

# v2 list endpoints return JSON by default in the main client
materials = api.v2.list_materials(name="ABEX")
print(materials[:2])

# switch list endpoints to pandas DataFrames if preferred
api.set_return_format("dataframe")
materials_df = api.v2.list_materials(name="ABEX")
print(materials_df.head())
```

### Flowsheets

```python
import prisma_api

api = prisma_api.init()

flowsheet = api.v2.get_flowsheet(name="dac_min")
```


## Bring your own dataset

Send a folder of your own results (CSV, JSON or YAML, organised any way) to the platform as one zip. Your files stay as they are; you add one file, `prisma_manifest.yaml`, that says what each file is and where its data goes.

1. **Install and connect.** You need your API key from your platform profile page.
   ```bash
   pip install prisma_api
   python -c "import prisma_api; prisma_api.init()"
   ```
2. **Find the dataset on the platform.** Data always goes to a dataset that already exists there. If yours doesn't, create it first on the web portal (from its project's page). On the dataset's page, **Add un-managed dataset** gives you the command for step 4, with the dataset's slug and a tag for its records.
3. **Leave files out** (optional). The listing skips whatever your `.gitignore` files match, and always leaves out `.git` and cache folders and anything named `*.claude`, `*.venv` or `*.DS_Store` (such as the `.claude` folder the skill is installed in), even if `.gitignore` doesn't list them. No other ignore file counts.
4. **Send the folder tree.** This sends only the folder structure: each file's path and size. No file contents, zip or manifest are sent. In your folder, paste the command from step 2:
   ```bash
   cd my_dataset_folder
   prisma-dataset tree . --prod --dataset my-dataset --tag "My dataset"
   ```
5. **Tick the files.** On the dataset's page, open **View folder tree** and tick the files to send. Every file starts unticked; ticks save automatically.
6. **Build the manifest and send, with Claude Code.** Copy the command shown above the folder tree, which has the dataset's slug and tag. In your folder:
   ```bash
   prisma-dataset install-skill .      # once
   claude                              # then paste the command, e.g.:
   /prisma-dataset my-dataset --tag "My dataset" --prod
   ```
   Claude writes (or updates) `prisma_manifest.yaml` for the ticked files: what each file holds and where its data goes. It checks the manifest with the platform's dry run (nothing is written) until it is clean, summarises what would be written, and sends the data once you say yes. Agree your first write with the platform team.

   To write the manifest yourself, read `prisma-dataset guide` and `prisma-dataset targets --prod`; `prisma-dataset ticked . --prod --dataset my-dataset` lists the ticked files it must cover. Then `prisma-dataset send . --prod` checks and packs the ticked files, shows the dry run and asks before writing.

When your results change, resend the tree if files were added or removed (`prisma-dataset tree . --prod`), tick any new files, and run the Claude Code command (or `send`) again. The platform updates the dataset to match and keeps a version history:

- **Same files, new data:** the tree is refreshed with your ticks kept. If the upload changes any records, the dataset goes up a minor version (1.0 → 1.1). If it changes nothing, the version stays as it is.
- **Files added or removed:** if the dataset already has data, the platform makes a new major version: a copy of the dataset called `<slug>-v2`, at version 2.0 and tagged `"<tag> v2"`. Its authors, project links and Zenodo record are copied, and your ticks are carried over. The command prints the new tree's id; `send` uploads to it. Your manifest can keep the original slug.

Each version records what its upload changed against the one before it: files added, removed or modified, and records created, updated or deleted per table and per run. A dry run reports the version it would make. To list a dataset's versions:
```bash
prisma-dataset versions my-dataset --prod
```
Older versions' values are not kept, only this record of the changes.

`send` does these steps in one go; you can also run them one at a time:
```bash
prisma-dataset validate . --prod                         # local check of the ticked files
prisma-dataset pack . --prod --out ../my_dataset.zip     # write the zip next to the folder, not inside it
prisma-dataset upload ../my_dataset.zip --prod           # dry run
prisma-dataset upload ../my_dataset.zip --prod --apply   # write
```
`pack` and `upload` take `--tag "Name"` (repeatable) to tag the records with more names than `dataset.tag`.

The same steps are available in Python: `prisma_api.dataset_package.send_tree`, `ticked_files`, `send`, `fetch_selection`, `validate`, `pack`, `upload` and `load_targets`.

## Return formats

All v2 list endpoints support two formats:

- `json`: returns `list[dict]`
- `dataframe`: returns `pandas.DataFrame`

Detail endpoints always return `dict`.

```python
api.set_return_format("json")
records = api.v2.get_molecules()

api.set_return_format("dataframe")
df = api.v2.get_molecules()
```

## Main objects

- `prisma_api.init()`
    Returns the main client object.
- `api`
    Legacy v1 wrappers plus configuration helpers.
- `api.v2`
    `PrismaAPIv2` instance with the v2 REST surface.

## v1 wrappers

The main client exposes these v1 methods:

- `get_mofs(payload={})`
- `get_carbon_isotherms(payload={})`
- `get_carbon_data_nested(payload={}, safe_names=False)`
- `get_materials_data(payload={}, separate_experimental=True)`
- `update_adsorption_singlepoint(df)`
- `update_heat_capacity_all_tidy(df)`
- `update_isotherm_h2(df)` — deprecated; still works (the server stores its rows in `adsorption_isotherm`)
- `update_mofchecker(df)`
- `update_zeopp_metrics(df)`

Configuration helpers on the main client:

- `set_return_format(fmt)`
- `update_dev_mode(dev)` — deprecated and no longer changes the target; use `prisma_api.init(local_dev=True|False)`

Module-level helpers exported from the package:

- `prisma_api.init`
- `prisma_api.__version__`
- `prisma_api.PrismaAPIv2`
- `prisma_api.update_dev_mode` — deprecated, as above
- `prisma_api.update_dev_host_port`
- `prisma_api.locate_config`
- `prisma_api.PrismaUpsertError`, `prisma_api.PrismaRowErrorWarning`, `prisma_api.PrismaUnknownFieldsWarning`, `prisma_api.PrismaNewStructureWarning`

## v2 wrapper list

All methods below are available on `api.v2`.

### Health and flowsheets

- `health()`
- `get_flowsheet(name="dac_min")`
- `get_flowsheet_bundle(name="dac_min")`
- `upsert_flowsheets(flowsheets, screening_analysis_name=None, on_exists="append", appendix="_v4")`

### Materials and bundles

- `list_materials(name=None, limit=10000)`
- `get_material(material_id=None, *, name=None, bundle=['isotherms','zeopp','water_kpis','cif'], sim_or_exp=None, good_structure=None, limit=500, offset=0, include_cif_text=False, cif_timeout=60)` (`bundle=None` returns root-only material detail; `name` may be a list)
- `list_cifs(tag=None)`
- `get_cifs(mof, structured=True, pandas=True, save_dir=None)` — one name or a list; `save_dir` writes the `.cif` files
- `get_materials_psdi(name=None, limit=500, offset=0)`
- `get_material_psdi(material_id)`
- `get_material_property_bundle(mof, sim_or_exp=None, good_structure=None, limit=500, offset=0, query=None)`
- `get_material_bundle(mof, sim_or_exp=None, good_structure=None, limit=500, offset=0, query=None, include_cif=False, include_cif_text=False, cif_timeout=60)`
- `get_material_bundles(names=None, ids=None, sections=None, exclude=None, include_cif_content=False, match='exact', output='json', save_path=None, use_post=False, timeout=300)` — server-side bundle endpoints; accepts one name or a list of names
- `upsert_material_bundles(bundles, cif_files=None, create_materials=True, strip_ids=False, inline_cifs=False, derive_cif_metadata=True, tag_names=None, method=None, timeout=300)` — write counterpart of `get_material_bundles`; takes the same document the read endpoint returns, with optional CIF upload
- `preflight_material_check(name)`

### Case bundles and pack builders

- `list_case_studies(name=None, limit=500, offset=0)`
- `get_cases_bundle(name=None, source=None, sink=None, region=None, limit_cases=100, limit_props=2000)`
- `build_case_spec(case_id)`
- `build_scenario_spec(scenario_id)`
- `build_case_pack(case_id, scenario_id=None)`
- `list_case_packs(source=None, sink=None, region=None, study=None, include_scenarios=False, limit=100, offset=0)`

### Case-study library (PrISMa_Platform sync)

- `upsert_case_study_library(document, dry_run=False, max_request_bytes=2_400_000, raise_on_error=False, timeout=600)` — sends a `prisma_v2.case_study_library.bundle.v1` document, split into requests if needed; replaces the pipeline 11 ZIP upload
- `get_case_study_library_bundle(sections=None, timeout=300)`
- `get_case_study_bundle(case_id, timeout=120)`
- `latest_case_study_library_sync()` — `None` before the first sync
- `list_case_study_library_sync_runs(status=None, limit=50, git_commit=None, offset=0)`
- `list_stale_case_study_library_records()`

### Datasets and screening outputs

- `get_dataset_targets()` — what a dataset manifest may write on the server: targets, kinds, allowed keys and fields
- `register_dataset_tree(tree, dataset, manifest=None, tags=None, package=None, timeout=600)` — sends a folder's structure (paths and sizes only) so files can be ticked on the dataset's page; `package` refreshes an existing tree, keeping its ticks. Used by `prisma-dataset tree`
- `list_dataset_trees(dataset=None, limit=50)` / `get_dataset_tree(package)` — registered trees, and one tree's files with their ticks
- `ingest_dataset(zip_path, dry_run=True, timeout=1800, tags=None, package=None)` — sends a dataset package (files + `prisma_manifest.yaml`, built with `prisma-dataset pack`) to `POST /api/v2/datasets/ingest/`; returns the server report, including on rejection. Dry run unless `dry_run=False`. The format: `prisma-dataset guide`
- `list_dataset_versions(dataset, limit=50)` — a dataset's versions, newest first, each with what its upload changed
- `list_analysis_runs(dataset=None, limit=500, offset=0)` — runs loaded from dataset packages, with conditions, parameter set and row counts
- `get_analysis_run_rows(run_key, table='results', limit=500, offset=0)` — a run's free-form rows (`results`: metrics with no typed table; `inputs`: per-run inputs), each with its material or reference-system subject
- `ingest_screening_outputs(zip_path, timeout=7200, background=True, poll_every=15, progress=None)` — sends one batch of v1 case-study screening results (built with `prisma_api.screening_outputs.pack`) to `POST /api/v2/screening-outputs/ingest/` (pipeline 13); each case study / conditions / type becomes a scenario of the batch and its OutputKpi rows are replaced. Writes; no dry run. The server ingests in the background and the client polls until it is done, printing progress
- `screening_outputs_ingest_status(zip_upload)` — status of a screening-output batch sent earlier

From the command line: `prisma-outputs list <screening_results>` shows the batches, `prisma-outputs send <screening_results> --dev --batch NAME` (or `--all`) sends them after asking (`--yes` skips the question; `--prod` targets production; a failed batch is reported with the server's error and the next is still sent), and `prisma-outputs status <upload> --prod` checks a batch sent earlier.

### Catalog and reference data

- `get_molecules(name=None, limit=500, offset=0)`
- `get_molecule(molecule_id)`
- `get_elements(symbol=None, name=None, limit=500, offset=0)`
- `get_element(element_id)`
- `get_regions(code=None, name=None, limit=500, offset=0)`
- `get_region(region_id)`
- `get_sources(name=None, limit=500, offset=0)`
- `get_source(source_id)`
- `get_scopes(name=None, limit=500, offset=0)`
- `get_scope(scope_id)`
- `get_sinks(name=None, limit=500, offset=0)`
- `get_sink(sink_id)`
- `get_transport_scenarios(name=None, limit=500, offset=0)`
- `get_transport_scenario(ts_id)`
- `get_utilities(name=None, limit=500, offset=0)`
- `get_utility(utility_id)`
- `get_references(name=None, doi=None, limit=500, offset=0)`
- `get_reference(ref_id)`
- `get_transports(name=None, limit=500, offset=0)`
- `get_transport(transport_id)`
- `get_subsystems(name=None, type=None, limit=500, offset=0)`
- `get_subsystem(subsystem_id)`
- `get_properties(name=None, domain=None, category=None, object_id=None, limit=500, offset=0)`
- `get_property(property_id)`
- `get_equipment(name=None, group=None, limit=500, offset=0)`
- `get_equipment_item(equipment_id)`
- `get_equipment_costs(equipment_id=None, limit=500, offset=0)`
- `get_equipment_cost(cost_id)`
- `get_equipment_designs(equipment_id=None, key=None, limit=500, offset=0)`
- `get_equipment_design(design_id)`
- `get_process_conditions(name=None, type=None, limit=500, offset=0)`
- `get_process_condition(condition_id)`
- `get_process_configurations(name=None, type=None, limit=500, offset=0)`
- `get_process_configuration(config_id)`
- `get_contactor_configurations(name=None, type=None, limit=500, offset=0)`
- `get_contactor_configuration(config_id)`
- `get_cost_indices(year=None, limit=500, offset=0)`
- `get_cost_index(index_id)`
- `get_constants(param=None, limit=500, offset=0)`
- `get_constant(constant_id)`
- `get_mea_baselines(name=None, limit=500, offset=0)`
- `get_mea_baseline(mea_id)`
- `get_mea_kpis(name=None, category=None, limit=500, offset=0)`
- `get_mea_kpi(kpi_id)`

### Science data

- `get_isotherm(mof=None, molecule=None, temperature_min=None, temperature_max=None, sim_or_exp=None, good_structure=None, limit=500, offset=0)`
- `get_water_kpis(mof=None, molecule=None, source=None, sim_or_exp=None, good_structure=None, limit=500, offset=0)`
- `get_carbon_zeopp(mof=None, good_structure=None, limit=500, offset=0)`
- `get_carbon_zeopp_item(item_id)`
- `get_carbon_zeopp_experimental(mof=None, limit=500, offset=0)`
- `get_carbon_zeopp_experimental_item(item_id)`

### AutoPrism tables

- `get_computation_runs(workflow_id=None, step=None, status=None, limit=500, offset=0)` / `get_computation_run(run_id)`
- `upsert_computation_runs(payload, timeout=None, raise_on_error=False)`
- `get_adsorption_singlepoint(...)` / `get_adsorption_singlepoint_item(row_id)` / `upsert_adsorption_singlepoint(payload, meta_provenance=None, repo_dir=None, timeout=None, raise_on_error=False, check_ids=True)`
- `get_heat_capacity(...)` / `get_heat_capacity_item(row_id)` / `upsert_heat_capacity(payload, meta_provenance=None, repo_dir=None, timeout=None, raise_on_error=False, check_ids=True)`
- `get_isotherm_h2(...)` / `get_isotherm_h2_item(row_id)` / `upsert_isotherm_h2(payload, meta_provenance=None, repo_dir=None, timeout=None, raise_on_error=False, check_ids=True)` — deprecated; use the `adsorption_isotherm` methods with `component='H2'`
- `get_adsorption_isotherm(structure=None, isotherm_id=None, component=None, temperature_K=None, pressure_bar=None, limit=500, offset=0, md5=None, match=None)` / `get_adsorption_isotherm_item(row_id)` / `upsert_adsorption_isotherm(payload, meta_provenance=None, repo_dir=None, timeout=None, raise_on_error=False, check_ids=True)` — every gas, H2 included; needs prisma_cloud **>= 0.6.16** (older servers return 404)
- `get_mofchecker(...)` / `get_mofchecker_item(row_id)` / `upsert_mofchecker(payload, meta_provenance=None, repo_dir=None, timeout=None, raise_on_error=False, check_ids=True)`
- `get_zeopp_metrics(...)` / `get_zeopp_metrics_item(row_id)` / `upsert_zeopp_metrics(payload, meta_provenance=None, repo_dir=None, timeout=None, raise_on_error=False, check_ids=True)`
- `upsert_autoprism_cifs(cifs, tags=None, create_materials=True, batch_size=50, raise_on_error=False, timeout=300)` — AutoPrism's own CIFs, stored as `{structure}__autoprism_{md5[:12]}.cif`
- `get_autoprism_collection(...)` / `upsert_autoprism_collection(payload, meta_provenance=None, repo_dir=None, timeout=None, raise_on_error=False, check_ids=True, cif_tags=None)`

The getters take `match="exact"` for an exact (case-insensitive) name match
instead of the default substring match.

Rejected rows (HTTP 207, or 400 when nothing is stored) raise
`PrismaUpsertError` with `raise_on_error=True`, else emit
`PrismaRowErrorWarning`. Ignored payload fields and newly created MOF records
emit `PrismaUnknownFieldsWarning` / `PrismaNewStructureWarning`. See
[API_REFERENCE_V2.md](API_REFERENCE_V2.md#autoprism-tables).

### TEA, LCA, cases, and scenarios

- `get_output_kpis(scenario_id=None, mof=None, good_structure=None, limit=500, offset=0)`
- `get_output_kpi(kpi_id)`
- `upsert_output_kpis(df)`
- `get_region_costs(region=None, name=None, year=None, limit=500, offset=0)`
- `get_region_cost(rc_id)`
- `upsert_region_costs(df)`
- `get_ambient_parameters(name=None, limit=500, offset=0)`
- `get_ambient_parameter(ap_id)`
- `upsert_ambient_parameters(df)`
- `get_cases(source=None, sink=None, region=None, study=None, limit=500, offset=0)`
- `get_case(case_id)`
- `get_scenarios(case_id=None, name=None, type=None, limit=500, offset=0)`
- `get_scenario(scenario_id)`
- `get_screening_analysis_bundle(analysis_id)`
- `get_screening_summaries(scenario_id=None, limit=500, offset=0)`
- `get_screening_summary(summary_id)`

## Examples

### Materials

```python
import prisma_api

api = prisma_api.init()

materials = api.v2.list_materials(name="ABEX")
first = materials[0]
detail = api.v2.get_material(first["id"])
```

### PSDI detail

```python
psdi = api.v2.get_materials_psdi(name="Lewatit")
first_psdi = psdi[0]
record = api.v2.get_material_psdi(first_psdi["id"])
```

### Flowsheet retrieval

```python
flowsheet = api.v2.get_flowsheet(name="dac_min")
print(flowsheet["template_id"])
```

### Advanced property-bundle query

`get_material_property_bundle` supports a generic query dictionary for more complex endpoint-specific filtering.

Supported keys are:

- `common`
- `materials`
- `isotherms`
- `zeopp_simulated`
- `zeopp_experimental`
- `water_kpis`

Merge precedence is:

- defaults
- `common`
- endpoint-specific filters

```python
bundle = api.v2.get_material_property_bundle(
        "HKUST",
        good_structure=True,
        query={
                "common": {"limit": 50},
                "materials": {"limit": 20},
                "isotherms": {
                        "molecule": "CO2",
                        "temperature_min": 273,
                        "temperature_max": 350,
                },
                "water_kpis": {"source": "DAC"},
        },
)

print(bundle.keys())
```

### Cases and scenarios

```python
cases = api.v2.get_cases(region="GB", limit=10)
case_id = cases[0]["id"]

case_detail = api.v2.get_case(case_id)
scenario_list = api.v2.get_scenarios(case_id=case_id)
case_pack = api.v2.build_case_pack(case_id)
```

## Testing

Run the test suite with:

```bash
pytest tests/ -v --cov=prisma_api --cov-report=term-missing
```

## Additional documentation

- [API_REFERENCE_V1.md](API_REFERENCE_V1.md)
- [API_REFERENCE_V2.md](API_REFERENCE_V2.md)
- [DEVELOPMENT_SUMMARY.md](DEVELOPMENT_SUMMARY.md)
- [user_guide.md](user_guide.md)
- Release notes, one file per minor series: [v0.4.x](https://github.com/RCCS-CaptureTeam/prisma_api/blob/main/docs/release_notes/v0.4.x.md), [v0.3.x](https://github.com/RCCS-CaptureTeam/prisma_api/blob/main/docs/release_notes/v0.3.x.md), [v0.2.x](https://github.com/RCCS-CaptureTeam/prisma_api/blob/main/docs/release_notes/v0.2.x.md)

## Release notes

Plain-language notes for every release, newest first, are in
[docs/release_notes/](https://github.com/RCCS-CaptureTeam/prisma_api/tree/main/docs/release_notes). Each release section links to its
GitHub release and to the full diff from the previous release. Check them
before upgrading for breaking changes and deprecations.

## Notes

- Production v2 base URL: `https://prisma-platform.org/api/v2`
- Authentication header: `X-API-Key`
- Local development routing is selected with `prisma_api.init(local_dev=True)`; the default targets production
- Server compatibility notes for recent releases are below; see [docs/release_notes/](https://github.com/RCCS-CaptureTeam/prisma_api/tree/main/docs/release_notes) for the full history
- 0.4.4: the `adsorption-isotherm` endpoints and the 400/notice handling need prisma_cloud >= 0.6.16
- 0.4.5: H2 isotherms go through `upsert_adsorption_isotherm` (prisma_cloud >= 0.6.16); the H2-only methods and the `isotherm_H2s` collection key are deprecated. New `upsert_autoprism_cifs` uses the existing bundle upsert endpoint. AutoPrism getters take `match="exact"`.
- 0.4.6: case-study library sync methods (`upsert_case_study_library` and the export, sync-run and stale-record readers) for the prisma_cloud `case-study-library` endpoints, which replace ingest pipeline 11.
