Metadata-Version: 2.1
Name: wexample-file
Version: 9.1.6
Summary: Wraps local file and directory paths in typed objects with mtime-based read caching, safe write/touch/rename, and idempotent removal
Author-Email: weeger <contact@wexample.com>
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Project-URL: homepage, https://github.com/wexample/python-file
Requires-Python: >=3.10
Requires-Dist: attrs>=23.1.0
Requires-Dist: cattrs>=23.1.0
Requires-Dist: wexample-helpers>=20.0.0
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Description-Content-Type: text/markdown

# file

Version: 9.1.6

`wexample-file` wraps local file and directory paths in typed Python objects — `LocalFile` and `LocalDirectory` — so each path is always stored as a resolved absolute `pathlib.Path` and carries a consistent set of operations: mtime-keyed read caching, write with automatic parent creation, touch, rename, and idempotent removal. It is aimed at Python 3.10+ projects that want predictable, typed filesystem access without raw `pathlib` calls scattered through application code.

## Table of Contents

- [Installation](#installation)
- [Quickstart](#quickstart)
- [Tests](#tests)
- [Architecture](#architecture)
- [Integration in the Suite](#integration-in-the-suite)
- [Dependencies](#dependencies)
- [Versioning & Compatibility Policy](#versioning--compatibility-policy)
- [License](#license)
- [About us](#about-us)
- [Known Limitations & Roadmap](#known-limitations--roadmap)
- [Status & Compatibility](#status--compatibility)
- [Useful Links](#useful-links)
- [Migration Notes](#migration-notes)

## Installation

```bash
pip install wexample-file
```

Requires Python >=3.10.

## Quickstart

Install the package:

```bash
pip install wexample-file
```

### Working with a file

```python
from wexample_file.common.local_file import LocalFile

f = LocalFile("/tmp/hello.txt")
f.write("hello, world")          # creates parent dirs automatically
content = f.read()               # "hello, world"
f.remove()                       # idempotent — no error if already gone
```

`write` creates any missing parent directories. `read` returns `None` when the file does not exist, and caches the content keyed on the file's mtime so repeated calls are cheap.

### Working with a directory

```python
from wexample_file.common.local_directory import LocalDirectory

d = LocalDirectory("/tmp/myapp/data")
d.create()    # mkdir -p equivalent
d.remove()    # shutil.rmtree, idempotent
```

Both classes accept a plain string or a `pathlib.Path` and always store a resolved absolute path in `.path`. Passing `check_exists=True` to either constructor raises `FileNotFoundException` or `DirectoryNotFoundException` when the path is absent.

## Tests

This project uses `pytest` for testing and `pytest-cov` for code coverage analysis.

### Installation

First, install the required testing dependencies:
```bash
.venv/bin/python -m pip install pytest pytest-cov
```

### Basic Usage

Run all tests with coverage:
```bash
.venv/bin/python -m pytest --cov --cov-report=html
```

### Common Commands
```bash
# Run tests with coverage for a specific module
.venv/bin/python -m pytest --cov=your_module

# Show which lines are not covered
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing

# Generate an HTML coverage report
.venv/bin/python -m pytest --cov=your_module --cov-report=html

# Combine terminal and HTML reports
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html

# Run specific test file with coverage
.venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing
```

### Viewing HTML Reports

After generating an HTML report, open `htmlcov/index.html` in your browser to view detailed line-by-line coverage information.

### Coverage Threshold

To enforce a minimum coverage percentage:
```bash
.venv/bin/python -m pytest --cov=your_module --cov-fail-under=80
```

This will cause the test suite to fail if coverage drops below 80%.

## Architecture

The package (`wexample-file`, declared in pyproject.toml) lives entirely under `src/wexample_file/` and splits into four namespaces: `common`, `mixin`, `enum`, and `exception`, plus a `helper` module for standalone utilities. There are no services, no I/O threads, and no global state — every call is synchronous and scoped to the instance it is made on.

### `common` — the two concrete types

src/wexample_file/common/abstract_local_item_path.py defines `AbstractLocalItemPath`, which extends `WithPathMixin` from `wexample_helpers`. Its `__init__` accepts a `str` or `pathlib.Path`, calls `.expanduser().resolve(strict=False)`, and stores the result in `self.path`. That normalisation happens once at construction; all subsequent operations compare against the resolved path. The constructor also calls `_validate_existing_type()` before the optional `check_exists` guard, so a `LocalFile` pointing at an existing directory is always rejected, regardless of whether existence is being asserted.

The two concrete classes each implement the two abstract methods (`item_type` and `remove`) and add their own surface:

**src/wexample_file/common/local_file.py** — `LocalFile`

Holds `_cached_content: str | None` and `_cached_mtime_ns: int | None`. Every call to `read()` stats the file first; if the on-disk `st_mtime_ns` matches the cached value the content is returned without I/O. Any write or removal operation updates — or clears — both cache fields before returning, so the cache is always coherent with what the instance last wrote. `read()` returns `None` (not an exception) when the file is absent; `check_exists=True` at construction time is the opt-in for eager failure. `_validate_existing_type()` raises `NotAFileException` when the path exists but is a directory.

**src/wexample_file/common/local_directory.py** — `LocalDirectory`

Has no cache. `create()` calls `mkdir(parents=True, exist_ok=True)`. `remove()` delegates to `shutil.rmtree` and is idempotent. `_validate_existing_type()` raises `NotADirectoryException` when the path exists but is not a directory.

### `mixin` — composable entry points

src/wexample_file/mixin/with_local_file_mixin.py provides `WithLocalFileMixin`. Any class that also inherits `WithPathMixin` (and therefore has `get_path()`) gains `get_local_file()`, which constructs a `LocalFile` lazily and caches it on `_local_file`; if `get_path()` changes between calls the stale instance is discarded and a fresh one built.

src/wexample_file/mixin/with_local_directory_mixin.py provides `WithLocalDirectoryMixin`. `get_local_directory()` constructs a fresh `LocalDirectory` on every call — no per-instance cache, because `LocalDirectory` carries no mutable state.

### `enum` — type discriminator

src/wexample_file/enum/local_path_type.py declares `LocalPathType` with two members, `FILE` and `DIRECTORY`. It is returned by `item_type()` on each concrete class and is available for any external code that needs to branch on kind without `isinstance`.

### `exception` — typed errors

All four exceptions are decorated with `@base_class` from `wexample_helpers` and use its structured field/message protocol:

- src/wexample_file/exception/file_not_found_exception.py — `FileNotFoundException` (error code `FILE_NOT_FOUND`), raised by `LocalFile` when `check_exists=True` and the path is absent.
- src/wexample_file/exception/directory_not_found_exception.py — `DirectoryNotFoundException` (error code `DIRECTORY_NOT_FOUND`), raised by `LocalDirectory` under the same condition.
- src/wexample_file/exception/not_a_file_exception.py — `NotAFileException` (error code `FILE_EXPECTED`), raised on construction when an existing path is a directory, and on `write()` for the same reason.
- src/wexample_file/exception/not_a_directory_exception.py — `NotADirectoryException` (error code `DIRECTORY_EXPECTED`), raised on construction when an existing path is a regular file.

The not-found pair inherits `LocalPathNotFoundException` from `wexample_helpers`; the type-mismatch pair inherits `UndefinedException`.

### `helper` — standalone utilities

src/wexample_file/helper/line.py exports `line_count_recursive(path, pattern="*")`, which walks a directory with `rglob`, opens each file with `errors="ignore"`, and sums line counts. It operates on a plain `pathlib.Path` rather than the typed wrappers, so it can be used independently of the rest of the package.

### Call path through a `LocalFile.read()`

1. `LocalFile("/some/path")` — `AbstractLocalItemPath.__init__` resolves the path, `_validate_existing_type` rejects it if it is a live directory.
2. `f.read()` — `is_file()` returns `False` → `None` immediately; otherwise the method stats for `st_mtime_ns`.
3. If mtime matches `_cached_mtime_ns` and `_cached_content` is set, the cached string is returned.
4. Otherwise `path.read_text(encoding=encoding)` is called, followed by a second stat; both fields are updated before the method returns.

`write()` follows: `mkdir` for parents → `write_text` → stat → update `_cached_content` and `_cached_mtime_ns`. `remove()` calls `unlink(missing_ok=True)` and nulls both cache fields.

### Tests

Tests live under `tests/package/` and map directly to the source namespaces:

- tests/package/common/test_local_file.py — covers instantiation with str/Path, extension parsing, read/write/remove/touch, cache coherence edge cases, and type-rejection.
- tests/package/common/test_local_directory.py — covers instantiation, `create`, recursive `remove`, idempotency, and type-rejection.
- tests/package/helper/test_line.py — covers empty directory, multi-file recursive count, and glob pattern filtering.

## Integration in the Suite

This package is part of the Wexample Suite — a collection of high-quality, modular tools designed to work seamlessly together across multiple languages and environments.

### Related Packages

The suite includes packages for configuration management, file handling, prompts, and more. Each package can be used independently or as part of the integrated suite.

Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.

## Dependencies

- attrs: >=23.1.0
- cattrs: >=23.1.0
- wexample-helpers: >=20.0.0

## Versioning & Compatibility Policy

Wexample packages follow **Semantic Versioning** (SemVer):

- **MAJOR**: Breaking changes
- **MINOR**: New features, backward compatible
- **PATCH**: Bug fixes, backward compatible

We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

Free to use in both personal and commercial projects.

## About us

[Wexample](https://wexample.com) stands as a cornerstone of the digital ecosystem — a collective of seasoned engineers, researchers, and creators driven by a relentless pursuit of technological excellence. More than a media platform, it has grown into a vibrant community where innovation meets craftsmanship, and where every line of code reflects a commitment to clarity, durability, and shared intelligence.

This packages suite embodies this spirit. Trusted by professionals and enthusiasts alike, it delivers a consistent, high-quality foundation for modern development — open, elegant, and battle-tested. Its reputation is built on years of collaboration, refinement, and rigorous attention to detail, making it a natural choice for those who demand both robustness and beauty in their tools.

Wexample cultivates a culture of mastery. Each package, each contribution carries the mark of a community that values precision, ethics, and innovation — a community proud to shape the future of digital craftsmanship.

## Known Limitations & Roadmap

Current limitations and planned features are tracked in the GitHub issues.

See the [project roadmap](https://github.com/wexample/python-file/issues) for upcoming features and improvements.

## Status & Compatibility

**Maturity**: Production-ready

**Python Support**: >=3.10

**OS Support**: Linux, macOS, Windows

**Status**: Actively maintained

## Useful Links

- **Homepage**: https://github.com/wexample/python-file
- **Documentation**: [docs.wexample.com](https://docs.wexample.com)
- **Issue Tracker**: https://github.com/wexample/python-file/issues
- **Discussions**: https://github.com/wexample/python-file/discussions
- **PyPI**: [pypi.org/project/wexample-file](https://pypi.org/project/wexample-file/)

## Migration Notes

When upgrading between major versions, refer to the migration guides in the documentation.

Breaking changes are clearly documented with upgrade paths and examples.
