Metadata-Version: 2.5
Name: qforge-sdk
Version: 0.1.0
Summary: Python client for QForge: generate crystal structures with target properties.
Project-URL: Documentation, https://github.com/Hilbert-Forge/QForge-python#readme
Author: QForge
License-Expression: MIT
License-File: LICENSE
Keywords: ase,crystal structures,generative models,materials,pymatgen
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Chemistry
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: >=3.9
Requires-Dist: httpx>=0.25
Provides-Extra: all
Requires-Dist: ase>=3.22; extra == 'all'
Requires-Dist: pymatgen>=2023.1; extra == 'all'
Provides-Extra: ase
Requires-Dist: ase>=3.22; extra == 'ase'
Provides-Extra: dev
Requires-Dist: mypy>=1.8; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Provides-Extra: pymatgen
Requires-Dist: pymatgen>=2023.1; extra == 'pymatgen'
Description-Content-Type: text/markdown

# qforge-sdk

Python client for QForge: generate crystal structures with the properties you want, then work with them in pymatgen or ASE.

```bash
pip install qforge-sdk              # the client
pip install "qforge-sdk[pymatgen]"  # + Material.to_pymatgen()
pip install "qforge-sdk[ase]"       # + Material.to_ase()
```

The package installs as `qforge-sdk` and imports as `qforge`.

## Setup

1. In QForge, open **Developers → API keys** and create a key.
   - Give it only the scopes it needs. "Run" spends credits.
   - Set a monthly credit cap.
2. Set two environment variables:

```bash
export QFORGE_API_KEY=qfk_…
export QFORGE_URL=https://your-qforge   # the address you open QForge at
```

You can also pass them in code: `QForge(api_key=..., base_url=...)`.

## Generate

```python
from qforge import QForge

qf = QForge()

job = qf.generate(
    {"band_gap": 3.0},  # property ids: Developers → Properties
    elements=["Fe", "O"],
    n_structures=8,
    model="frost",  # "auto", a model id, or pinned: "frost@1"
    check_novelty=True,
)
job.wait(on_progress=lambda j: print(j.status, j.progress))

for m in job.materials:
    print(m.formula, m.space_group, m.e_above_hull, m.novelty)
    m.save()  # FeO.cif
    s = m.to_pymatgen()  # pymatgen Structure
```

The **code builder** in QForge (Developers → Code builder) writes this call for any query you build in the browser.

## Campaigns

A campaign keeps generating until it finds enough materials that meet a goal, or it hits a credit or batch limit.

```python
c = qf.campaign(
    "Wide-gap oxides",
    {"band_gap": 4.0},
    goal={"count": 5, "novel": True, "stable": True},
    max_credits=30,
    max_batches=10,
)
c.wait()
print(c.status, [m.formula for m in c.materials])
```

## Library and DFT

```python
for m in qf.library(search="Fe-O", favourite=True):
    print(m.formula, m.library_id)

check = qf.verify_dft(m, max_credits=20)  # starts at the quoted price, never above 20
check.wait()  # DFT takes hours; poll every minute
print(check.verdict, check.e_above_hull)
```

## Errors and retries

- **Every API error is a `qforge.QForgeError`**, with `.status`, `.code` and `.request_id`. Quote the request id to support.
- **Specific error classes:**
  - `AuthenticationError` (401)
  - `InsufficientCredits` (402)
  - `PermissionDenied` (403, the key lacks a scope)
  - `NotFound` (404)
  - `Conflict` (409)
  - `RateLimited` (429)
  - `ServerError` (5xx)
  - `JobFailed` (a run, campaign or DFT check failed; failed runs are refunded)
  - `Timeout` (`wait(timeout=…)` ran out)
- **Automatic retries:** busy (429) and unavailable (502, 503, 504) responses are retried with backoff, honouring `Retry-After`.
- **No double charges:** calls that spend credits send an `Idempotency-Key`, so a retry is never charged twice.

## Development

```bash
python -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/pytest
.venv/bin/ruff check . && .venv/bin/mypy src
```

The tests use an in-memory fake server and never call a real one.

### Releasing

1. Bump `version` in `pyproject.toml` and `src/qforge/__init__.py`.
2. Tag `vX.Y.Z` and push the tag.
3. GitHub Actions builds the package and publishes it to PyPI. This uses trusted publishing: add this repo and the `release.yml` workflow as a trusted publisher for `qforge-sdk` on PyPI first.

Until it's on PyPI, install from the repo:

```bash
pip install "git+https://github.com/Hilbert-Forge/QForge-python.git"
```
