---
description: |
  [TOPIC] Three-tier dependency policy for every scitex-* package and
  the canonical helper for optional imports.
  [DETAILS] Two install modes (default + [all]) plus [dev]. No
  fragmented extras. `[all]` is CLOSED over every public extra —
  including [dev] — via self-references (enforced as PS-221).
  `scitex_dev.try_import_optional` is the
  ecosystem-wide handler for genuinely optional dependencies — raw
  try/except ImportError is forbidden. Auditors and reviewers check
  this every commit.
tags: [scitex-general-development-dependency-tiers]
---

# Three-tier dependency policy

Every scitex-* package's `pyproject.toml` has exactly three dependency
buckets — no more, no less.

## The three buckets

```toml
[project]
dependencies = [
    # Tier 1 (hard) — required for every public surface to work out of
    # `pip install <pkg>`. Python API + CLI + MCP server + Skills must
    # all start with nothing more than these.
]

[project.optional-dependencies]
dev = [
    # Tier 3 — what the maintainer needs to develop the package
    # itself. pytest, pytest-cov, pre-commit, nbconvert, ipykernel,
    # etc.
]

all = [
    # Tier 2 — fully-featured. `pip install <pkg>[all]` enables every
    # graceful-degradation feature the package offers. Heavy or
    # platform-specific packages live here — PLUS a self-reference to
    # every other public extra, e.g. "<pkg>[dev]", so that `all` is
    # CLOSED: every public extra is a subset of [all] (PS-221).
]
```

That is the *entire* taxonomy. **No** `[plot]`, `[mcp]`, `[figrecipe]`,
`[logging]`, `[pingouin]`, `[torch]` etc. They all collapse into `[all]`.

## Why two install modes (not five)

Fragmented extras (`[plot]` + `[mcp]` + `[figrecipe]` + …) cost more
than they save:

- Users have to read the README to know which combination they want.
- Adding a new feature means adding a new extra; old releases drift.
- `[all]` ends up depending on `<pkg>[plot]` + `<pkg>[mcp]` + … which
  is hard to keep consistent.
- Most users want either "minimal" or "everything." Forcing them to
  pick the right subset is bureaucracy.

Two modes — default and `[all]` — cover both. `[dev]` is the third
because development tools are conceptually different (they're for
maintainers, not consumers).

## What goes in `[project.dependencies]` (hard)

A package belongs in hard if **any** of these holds:

- The Python API's "obvious" entry points require it. Not the entire
  surface, but the entry points a first-time user will hit.
- The CLI's `--help` or first subcommand needs it for import.
- The MCP server (`<pkg>._mcp.server`) tops at it (server start path).
- A skill file says "this works out of the box" and would lie without it.

If the package is **statistically central** (stats packages: `numpy`,
`scipy`, `pandas`, `statsmodels`, `pingouin`), it goes hard regardless
of whether the API can run without it. Promise of out-of-box function
beats install-size optimization for our scientific-Python use case.

`scitex-dev` and `scitex-logging` are **always** hard. Try-import,
config cascade, linter plugin loading, and logger semantics all break
without them.

`mcp` and `fastmcp` are hard for any package shipping an MCP server —
the server top-level imports `fastmcp`, so without it `mcp start` is
broken on bare install. (Users not using MCP pay ~3MB; cheap.)

## What goes in `[all]`

Everything else that's optional but useful:

- Heavy / platform-specific (`torch`, `tensorflow`, `cuda-*`).
- Niche features (one statistical test out of dozens).
- Integrations with other ecosystems.

Rule of thumb: if a feature has a graceful-degradation path that's
documented, the dep goes in `[all]`.

## What goes in `[dev]`

Things only the **maintainer** of this package needs:

- `pytest`, `pytest-cov`, `pytest-xdist`, `pytest-mock`
- `pre-commit`
- `nbconvert`, `ipykernel` (for notebook tests / docs)
- `openpyxl` (test fixtures touching xlsx)

`[dev]` does **not** include packages that are already in `default`
(hard) — `pip install -e ".[dev]"` walks `default` automatically.

`[dev]` is still a **public extra**, so the PS-221 `[all]`-closure rule
applies to it like any other: `all` must reference it (`"<pkg>[dev]"`),
so `pip install <pkg>[all]` pulls the dev tooling too. That is the
enforced, shipped shape (scitex-dev / scitex-scholar / scitex-session /
scitex-logging all do this). The conceptual maintainer-vs-consumer split
lives in which extra you *ask for* (`[dev]` vs `[all]`), not in `[all]`
excluding it. Do not try to carve out a non-public group by
underscore-prefixing its name (`_dev`): PEP 508/685 forbids
leading-underscore extra names — setuptools/hatchling/pip/uv all reject
them.

## Optional imports must use `try_import_optional`

`scitex_dev._core.imports.try_import_optional` is the only sanctioned way to
import a `[all]`-tier dependency — raw `try/except ImportError` is forbidden
ecosystem-wide. The usage pattern, why the helper (install hints, ecosystem-wide
error improvements, grep target), and what to do when a package is promoted from
`[all]` to hard are in [20_optional-import-helper.md](20_optional-import-helper.md).

## Audit hooks

```bash
# pyproject section count check (default + [all] + [dev] only) and
# PS-221 [all]-closure (every public extra must be a subset of [all];
# severity E, blocking).
scitex-dev ecosystem audit-project <pkg>

# Raw try/except-on-import grep (catch bypass attempts).
scitex-dev ecosystem audit-skills <pkg>     # SK-future, file an issue if missing
```

If either auditor flags violations, fix the dep taxonomy *before*
opening the PR. Pre-push lint gate (`.githooks/pre-push`) doesn't
catch this yet — manual review still required.

## Migration of an existing package

Step-by-step for a package with fragmented extras:

1. Audit what each public surface needs (run all four: Python API
   import, CLI --help, MCP server import, skills entry-point).
2. Promote any package needed by a public surface to `default`.
3. Collapse every remaining extra (`[plot]`, `[mcp]`, …) into a single
   `[all]`. Delete the old keys.
4. Refactor raw `try/except ImportError` to `try_import_optional`.
5. Delete `try_import_optional` for packages promoted to `default` —
   plain `import` is now correct.
6. Drop any package that has 0 hits in `src/` (genuine dead deps).
7. Run `pytest tests/` and `<pkg> --help` on a fresh venv to verify
   the default install works end-to-end.

## Cross-references

- `05_development/10_package-maintenance-prompt.md` — where this slot
  fits in the standing maintenance loop.
- `01_ecosystem/02_dependency-and-version-pinning.md` — version range
  conventions inside each bucket.
- `01_ecosystem/03_modules-and-standalone-packages.md` — how to decide
  whether to vendor or depend on a sibling scitex-* package.
- `04_docs/01_readme.md` — README must document the two install modes
  (default + [all]).
