Metadata-Version: 2.4
Name: strict-suite
Version: 0.5.0
Summary: Consolidated strict Python linting and static analysis tools - AST-based DTO discipline, facade-ban enforcement, and complexity metrics.
Project-URL: Homepage, https://pypi.org/project/strict-suite/
Project-URL: Repository, https://github.com/jekhator/strict-suite
Project-URL: Issues, https://github.com/jekhator/strict-suite/issues
Project-URL: Changelog, https://github.com/jekhator/strict-suite/blob/main/CHANGELOG.md
Author: James Ekhator
License: Apache-2.0
License-File: LICENSE
Keywords: ast,code-quality,dataclass,dto,linter,static-analysis,strict
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Typing :: Typed
Requires-Python: >=3.11
Provides-Extra: dev
Requires-Dist: mypy; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# strict-suite

[![PyPI](https://img.shields.io/pypi/v/strict-suite.svg?style=flat)](https://pypi.org/project/strict-suite/)
[![CI](https://github.com/jekhator/strict-suite/workflows/CI/badge.svg)](https://github.com/jekhator/strict-suite/actions)
[![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)

AST-based linter for Python DTO discipline and facade-ban enforcement. Consolidated strict Python linting and static analysis tools with complexity metrics.

**Consolidated from strict-module 0.5.0.** This monorepo consolidates the strict-module package and related linting infrastructure. Backward-compatible CLI entry points (`strict-module` and `dto-strict`) are preserved.

> **Python only.** The strict-component (npm/TypeScript) stays in its own repo. This distribution is Python 3.11+ only.

## Overview

Data Transfer Objects (DTOs) and facade discipline are critical for clean API boundaries, especially in regulated environments (healthcare, compliance, financial systems). **stricts** enforces these patterns via static AST analysis, with 6 focused rules:

1. **R001 (HIGH)**: Detect `Dict[str, Any]` or bare `dict`/`list`/`tuple` in service-layer function signatures.
2. **R002 (MEDIUM)**: Flag inline dict literals with 3+ string keys; exception tags can require justification.
3. **R003 (MEDIUM)**: Flag `repr=False` in dataclasses (canonical: plain `@dataclass(frozen=True, slots=True)`).
4. **R004 (HIGH)**: Demand exception tags on module-level functions (e.g., `# facade - celery schedule`).
5. **R005 (LOW)**: Encourage validators to use `DTO.from_dict()` pattern.
6. **R006 (HIGH)**: Detect `typing.Any` in function signatures (parameters and return types).

All rules are configurable; violations can be disabled, severity overridden, or paths scoped.

## Install

```bash
pip install strict-suite
```

Or with optional development dependencies:

```bash
pip install strict-suite[dev]
```

Both CLI entry points (strict-module and dto-strict) are available post-install.

## Quick Start

### Basic CLI Usage

```bash
# Lint a single file
strict-module apps/compliance/services.py

# Lint a directory
strict-module apps/

# Output as GitHub Actions annotations
strict-module apps/ --format github

# Output as JSON
strict-module apps/ --format json

# Use the compat alias
dto-strict apps/
```

### Run-Verified Example

Create a test file `example.py` with conformant code:

```python
from dataclasses import dataclass

@dataclass(frozen=True, slots=True)
class UserDTO:
    """User data transfer object."""
    user_id: int
    email: str

class UserService:
    """Service to process users."""

    def process_user(self, user: UserDTO) -> None:
        """Process a user."""
        print(f"Processing {user.email}")
```

Then lint it:

```bash
$ strict-module example.py
# (no output = clean)
```

Now create a violating example. Save this as `apps/test/services/bad_user.py`:

```python
from typing import Any

class UserService:
    def process_user(self, config: dict[str, Any]) -> None:  # R001: Dict[str, Any] in signature
        """Process a user."""
        return config.get("user_id")
```

Running the linter (from the repo root):

```bash
$ strict-module apps/test/services/bad_user.py
apps/test/services/bad_user.py:4: R001 Dict[str, Any] in signature: process_user
apps/test/services/bad_user.py:4: R006 typing.Any in parameter: process_user
```

### All Rules Demonstrated

**R002** (inline dict literals with 3+ keys):
```python
# violation
def example():
    data = {"key1": "val1", "key2": "val2", "key3": "val3"}
```

**R003** (anti-canonical repr=False on dataclass):
```python
# violation
from dataclasses import dataclass

@dataclass(repr=False)
class MyDTO:
    value: int
```

**R004** (module-level function without exception tag):
```python
# violation (in a non-conftest file)
def process_data(item):
    return item["id"]
```

**R007** (pytest fixtures outside conftest.py):
```python
# violation
import pytest

@pytest.fixture
def my_fixture():
    return "value"
```

**R008** (bare module-level test functions):
```python
# violation
def test_something():
    assert True
```

### LOC Cap Enforcement

Check lines-of-code limits:

```bash
$ strict-module loc-cap src/
src/long_module.py: soft-target 500 exceeded (523 lines)
src/another.py: hard-cap 694 exceeded (750 lines) FAIL
```

Generate baseline:

```bash
$ strict-module loc-cap src/ --generate-baseline > .loc-cap-baseline.txt
```

### Using Both CLI Entry Points

Both `strict-module` and `dto-strict` are equivalent aliases:

```bash
strict-module apps/
dto-strict apps/
```

## Configuration

Configuration lives in `pyproject.toml`:

```toml
[tool.strict-module]
service_paths = ["apps/*/services/*.py", "**/services/*.py"]
dto_paths = ["**/dtos.py", "**/dtos/*.py"]
exception_tags = [
    "facade - celery schedule",
    "FRAMEWORK",
    "aws-boundary"
]
disabled_rules = []
severity_overrides = {}
```

- **service_paths**: Glob patterns for files to lint with R001 (strict mode).
- **dto_paths**: Glob patterns for files expected to contain DTO definitions.
- **exception_tags**: Recognized justification tags for module-level functions (R004 and R009). Example: `aws-boundary` exempts a function from the module-level function rules.
- **disabled_rules**: List of rule IDs to disable (e.g., ["R005"]).
- **severity_overrides**: Dict mapping rule IDs to override severity (e.g., {"R002": "HIGH", "R011": "INFO"}).

Backward compatibility: `[tool.strict-module]` config and `.strict-module-baseline.json` baseline files are supported.

### Suppressing Violations

Use `# noqa` comments to suppress violations on specific lines. Supported forms:

- **Bare `# noqa`**: Suppress any rule on that line.
  ```python
  x = {1: 2, 3: 4, 5: 6}  # noqa
  ```

- **Rule-specific `# noqa: R006`**: Suppress a single rule by ID.
  ```python
  def process(x: Any) -> None:  # noqa: R006
      pass
  ```

- **Namespaced `# noqa: strict-module-R006`**: Use the ruff-external-friendly namespaced form (recommended when running ruff alongside).
  ```python
  def process(x: Any) -> None:  # noqa: strict-module-R006
      pass
  ```

- **Multiple rules `# noqa: R006, R011`**: Suppress multiple rules on one line.
  ```python
  def process(x: Any) -> None:  # noqa: R006, R011
      pass
  ```

**Special note on R014 (kwarg grouping):** R014 findings report at the offending kwarg line, but noqa suppression only works on the line where the call begins. Place the noqa comment on the CALL line, not the kwarg line:
```python
self.log_event(  # noqa: R014
    const.LOG_EVENT_SUCCESS, a=1, b=2, c=3, d=4,
)
```

When using ruff in parallel, declare `[tool.ruff.lint] external = ["strict-module"]` in `pyproject.toml` to prevent ruff from warning on the external rule codes.

## Public API

The package exports the following public symbols:

- **`__version__`**: Package version string (e.g., "0.1.0")
- **`DtoStrictLinter`**: Main linter class for running AST analysis
- **`Rule`**: Rule definition dataclass
- **`RuleSeverity`**: Enum for rule severity levels (HIGH, MEDIUM, LOW)

Example usage:

```python
from strict_module import DtoStrictLinter, Rule, RuleSeverity, __version__

print(f"strict-suite {__version__}")

linter = DtoStrictLinter()
violations = linter.lint_file("mymodule.py")
for v in violations:
    print(f"{v.rule_id} ({v.severity.value}): {v.message}")
```

## Documentation

Full documentation, including rule details, configuration options, and integration guides, is available in the `docs/` directory.

Historical changelog: see `CHANGELOG-history.md` for strict-module v0.1.0 through v0.5.0 release history.

## Development

Install dev dependencies:

```bash
pip install -e ".[dev]"
```

Run tests:

```bash
python3.12 -m pytest strict_module/tests/ -v
```

Run the self-lint check:

```bash
strict-module strict_module/ --format text
dto-strict strict_module/
```

Run format and style checks:

```bash
ruff check strict_module/
ruff format --check strict_module/
```

Run LOC cap check:

```bash
strict-module loc-cap strict_module/
```

## License

Apache License 2.0. See [LICENSE](LICENSE) for full text.

## Contributing

Contributions are welcome. Please ensure:

- All tests pass.
- Code follows the project's linting rules (run `strict-module` on your changes).
- Commit messages are clear and describe the change.
- No AI attribution in commit messages.
