Skip to main content

filestate

Version: 17.5.0

filestate declares a desired filesystem state — files, directories, permissions, ownership, and content — as a YAML configuration, then reconciles the disk to match it. It works through a provider-based architecture of options (desired properties such as name casing, line endings, or YAML key order) and operations (the writes that close the gap when a property is not already met). Python developers who need to enforce consistent file structure across a codebase, or who manage file trees programmatically, are its primary audience.

Table of Contents

Installation

pip install wexample-filestate

Requires Python >=3.10.

Quickstart

Install the package:

pip install wexample-filestate

The example below declares that /tmp/demo/hello.txt must exist and contain Hello, world!. apply() creates the file if it is absent and writes the content if it differs:

from wexample_filestate.utils.file_state_manager import FileStateManager
from wexample_filestate.const.disk import DiskItemType
from wexample_prompt.common.io_manager import IoManager

manager = FileStateManager.create_from_path(path="/tmp/demo", io=IoManager())
manager.configure({
    "children": [
        {
            "name": "hello.txt",
            "type": DiskItemType.FILE,
            "should_exist": True,
            "content": "Hello, world!",
        }
    ]
})
result = manager.apply()

apply() returns a FileStateResult. Its .operations list contains one entry per change made on disk — an empty list means the file already matched the declared state and nothing was touched.

To preview changes without writing anything, call dry_run() instead:

from wexample_filestate.enum.scopes import Scope

result = manager.dry_run(scopes=set(Scope))
print(len(result.operations), "operation(s) pending")

Tests

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

Installation

First, install the required testing dependencies:

.venv/bin/python -m pip install pytest pytest-cov

Basic Usage

Run all tests with coverage:

.venv/bin/python -m pytest --cov --cov-report=html

Common Commands

# 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:

.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

filestate enforces a declared filesystem state against the real disk. The caller describes what files and directories should look like; the library compares that description to what is on disk, plans the mutations needed, and applies them — or simulates them in a dry run. Every concept that appears in configuration maps to a concrete Python class.

Entry point

src/wexample_filestate/utils/file_state_manager.py — FileStateManager is the public surface. It is a thin subclass of ItemTargetDirectory and adds nothing beyond a configure() override that optionally forces eager tree materialisation. Callers construct it, call configure(dict) or configure_from_file(path), then call apply() or dry_run().

Items

The item layer models the desired state as a tree.

Target items — what the caller declares:

  • src/wexample_filestate/item/abstract_item_target.py — AbstractItemTarget. Holds base_path/base_name, a reference to the source snapshot, the options dict, and the operations history stack. Implements apply(), build_operations(), dry_run(), and rollback().
  • src/wexample_filestate/item/item_target_directory.py — ItemTargetDirectory. Extends the base with lazy child materialisation (_tree_built flag). get_children_list() calls build_item_tree() on first access; deeper levels are built lazily in turn. build_operations() recurses into children.
  • src/wexample_filestate/item/item_target_file.py — ItemTargetFile. Leaf item; adds no behaviour beyond the mixin it carries.

Source items — read-only snapshot of the current disk state:

  • src/wexample_filestate/item/abstract_item_source.py — AbstractItemSource. Base class carrying WithPathMixin.
  • src/wexample_filestate/item/item_source_file.py, src/wexample_filestate/item/item_source_directory.py — concrete variants.

When AbstractItemTarget.configure() runs, it calls locate_source(path), which inspects the real filesystem and attaches an ItemSourceFile or ItemSourceDirectory (or leaves source = None when the path does not yet exist). Operations read the source to know the current state before writing the target.

Both hierarchies share ItemMixin (src/wexample_filestate/item/mixin/item_mixin.py), which owns base_name, base_path, and abstract is_file() / is_directory().

Options

Options declare what a target should look like. Each option is a config-option class that attaches to a target item and can examine the current state and create an operation when the state is wrong.

The contract lives in src/wexample_filestate/option/mixin/option_mixin.py:

  • create_required_operation(target, scopes) → AbstractOperation | None — compare desired to actual; return an operation if a change is needed, None if satisfied.
  • prepare(root, scopes, filter_paths) — called once per unique option type before the scan; used for expensive upfront work (batch tool runs, cache warm-up).
  • applicable_on_file(), applicable_on_directory(), applicable_on_missing() — guard methods controlling when the option is even considered.

All built-in options are registered in src/wexample_filestate/options_provider/default_options_provider.py. The canonical list (alphabetical):

Key Class Scope
active ActiveOption —
children ChildrenOption LOCATION
children_file_factory ChildrenFileFactoryOption LOCATION
class ClassOption —
content ContentOption CONTENT
default_content DefaultContentOption CONTENT
mode ModeOption PERMISSIONS, OWNERSHIP
name NameOption NAME
on_bad_format OnBadFormatOption NAME
should_contain_lines ShouldContainLinesOption CONTENT
should_exist ShouldExistOption LOCATION
should_have_extension ShouldHaveExtensionOption NAME
should_not_contain_lines ShouldNotContainLinesOption CONTENT
sidecar_of SidecarOfOption LOCATION
structured_keys StructuredKeysOption CONTENT
text TextOption CONTENT
type TypeOption —
yaml YamlOption CONTENT

Notable options in more detail:

  • src/wexample_filestate/option/should_exist_option.py — True creates a missing item; False removes an existing one; omitting the key enforces nothing.
  • src/wexample_filestate/option/content_option.py — sets the full file content; accepts a string, a ConfigValue, or a callable receiving the target.
  • src/wexample_filestate/option/text_option.py — nested option composing TrimOption, EndNewLineOption, SortLinesOption, UniqueLinesOption; accepts a list shorthand (["trim", "sort_lines"]) or a dict.
  • src/wexample_filestate/option/yaml_option.py — YAML-specific transforms (currently sort_recursive); never applies to directories or missing files.
  • src/wexample_filestate/option/name_option.py — nested option composing ValueOption, CaseFormatOption, RegexOption, PrefixOption, SuffixOption, OnBadFormatOption; accepts a string, a Path, a dict, or a callable.
  • src/wexample_filestate/option/children_option.py — list of child item configs; each entry is resolved by create_child_item(), which picks ItemTargetFile or ItemTargetDirectory by the type key or by inspecting the real filesystem.

Scopes

src/wexample_filestate/enum/scopes.py defines Scope: CONTENT, LOCATION, NAME, OWNERSHIP, PERMISSIONS, REMOTE, TIMESTAMPS. Every option and operation declares which scopes it belongs to by overriding get_scopes() from src/wexample_filestate/common/mixin/with_scope_mixin.py.

matches_scope_filter(scopes) returns True when the declared scopes intersect the requested set. apply() and dry_run() accept an optional scopes argument; an unqualified call uses Scope.default() (all scopes). Restricting scopes skips prepare() and create_required_operation() on options outside the requested scopes, making partial passes (e.g., content-only) cheap.

Operations

Operations are the mutations. Each one is created by an option, holds enough state to undo itself, and implements two abstract methods from src/wexample_filestate/operation/abstract_operation.py:

  • apply_operation() — perform the disk change.
  • undo() — reverse it.

src/wexample_filestate/operation/abstract_file_manipulation_operation.py — intermediate base for operations that touch file content or mode; provides _backup_target_file() and _restore_target_file() so undo() can restore the original bytes and permissions.

Concrete operations shipped by the library:

Class Scope What it does
FileCreateOperation LOCATION touch() or makedirs(); writes default_content if provided
FileRemoveOperation LOCATION remove() or shutil.rmtree()
FileRenameOperation LOCATION rename on disk, then updates base_name and invalidates cached paths
FileWriteOperation CONTENT writes a string to a file via LocalFile.write()
FileChangeModeOperation PERMISSIONS, OWNERSHIP chmod (and optionally chown) in one pass; chowns first to acquire ownership before chmod
FileChownOperation OWNERSHIP sudo chown only, without touching mode

Each operation carries target (the AbstractItemTarget it acts on), option (the option that created it), description, and applied. applied is set to True by the result after execution; applied operations are pushed to AbstractItemTarget.operations_history for sequential rollbacks.

Results

src/wexample_filestate/result/abstract_result.py — AbstractResult holds operations: list[AbstractOperation] and drives apply_operations(). The loop deduplicates by object identity, respects rollback (reverses order and calls undo() instead of apply_operation()), and delegates the actual execution to _apply_single_operation().

Two concrete subclasses:

  • src/wexample_filestate/result/file_state_result.py — FileStateResult. Calls operation.apply_operation() directly; in interactive mode, prompts for confirmation first and dispatches operation.<name>.pre / operation.<name>.post events around the call.
  • src/wexample_filestate/result/file_state_dry_run_result.py — FileStateDryRunResult. _apply_single_operation() returns True without touching disk.

Call path through apply()

  1. Caller invokes FileStateManager.apply(scopes, filter_paths, interactive).
  2. AbstractItemTarget.apply() creates a FileStateResult and calls _prepare_options(scopes, filter_paths). This walks the entire tree, collects one instance of each unique option type, and calls option.prepare() on it — batch tools run here, with visible log output, before the scan begins.
  3. build_operations(result, scopes, ...) is called on the root. For a directory it recurses: for each item it calls _find_first_operation(scopes, filter_operation).
  4. _find_first_operation() iterates self.options.values() in registration order and calls try_create_operation_from_option(option, scopes) on each. That method checks matches_scope_filter, applicable_on_file/directory/missing, then calls option.create_required_operation(target, scopes). The first non-None result is returned immediately; one operation per item per pass.
  5. Each operation is appended to result.operations.
  6. After the tree walk, result.apply_operations(interactive) iterates the list and calls _apply_single_operation() on each.
  7. Applied operations are collected into a batch and pushed to self.operations_history. A subsequent rollback() call pops the last batch and applies each operation's undo() in reverse order.

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.

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 for the complete package ecosystem.

Dependencies

  • attrs: >=23.1.0
  • cattrs: >=23.1.0
  • jinja2:
  • python-dotenv:
  • ruamel-yaml:
  • tomlkit:
  • wexample-config: >=7.2.0
  • wexample-event: >=8.0.0
  • wexample-file: >=9.2.0
  • wexample-helpers-yaml: >=7.0.0
  • wexample-prompt: >=16.0.0
  • wexample-runner: >=10.0.0
  • xmltodict:

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 file for details.

Free to use in both personal and commercial projects.

About us

Wexample 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 for upcoming features and improvements.

Status & Compatibility

Maturity: Production-ready

Python Support: >=3.10

OS Support: Linux, macOS, Windows

Status: Actively maintained

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.

Metadata

Release files for wexample-filestate 17.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for wexample-filestate 17.5.0
File Size Uploaded
wexample_filestate-17.5.0.tar.gz 134.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for wexample-filestate 17.5.0
File Interpreter ABI Platform
wexample_filestate-17.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 283.6 kB

Release files / wexample_filestate-17.5.0.tar.gz

Download URL wexample_filestate-17.5.0.tar.gz
Size 134.3 kB
Tags Source
SHA-256 checksum
How to use checksums
175b1db644da0a28ce44e356a32884753a237042c00a61300bd88ebed9d2bc5d
BLAKE2b-256 checksum
How to use checksums
290d1a32c318822a6ff9a7895cfa81e77c687b1142f8fa8e868f68b118db6abe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via pdm/2.25.9 CPython/3.12.3 Linux/6.8.0-142-generic

Release files / wexample_filestate-17.5.0-py3-none-any.whl

Download URL wexample_filestate-17.5.0-py3-none-any.whl
Size 149.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b1645c6a43d902232211b0091d5b8945c6a31556b92caafb077f710aff382dae
BLAKE2b-256 checksum
How to use checksums
f57c8824daf0241446605e99f8dda587827fdd8771388f499b35b7146936c25b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via pdm/2.25.9 CPython/3.12.3 Linux/6.8.0-142-generic

Release history Release notifications | RSS feed

This release

17.5.0 This release

2 release files

17.3.0

2 release files

17.2.0

2 release files

17.0.0

2 release files

16.0.2

2 release files

15.0.0

2 release files

14.2.0

2 release files

14.1.0

2 release files

14.0.0

2 release files

13.0.0

2 release files

12.2.0

2 release files

12.1.2

2 release files

12.1.1

2 release files

12.1.0

2 release files

12.0.0

2 release files

11.3.2

2 release files

11.3.0

2 release files

11.2.1

2 release files

11.2.0

2 release files

11.1.2

2 release files

11.1.1

2 release files

11.1.0

2 release files

11.0.0

2 release files

9.0.0

2 release files

7.4.0

2 release files

7.3.0

2 release files

7.2.0

2 release files

7.1.0

2 release files

7.0.1

2 release files

7.0.0

2 release files

6.3.0

2 release files

6.2.0

2 release files

6.1.0

2 release files

1.1.0

2 release files

1.0.2

2 release files

1.0.0

2 release files

0.0.75

2 release files

0.0.71

2 release files

0.0.70

2 release files

0.0.56

2 release files

0.0.54

2 release files

0.0.53

2 release files

0.0.49

2 release files

0.0.47

2 release files

0.0.46

2 release files

0.0.45

2 release files

0.0.44

2 release files

0.0.43

2 release files

0.0.42

2 release files

0.0.41

2 release files

0.0.35

2 release files

0.0.34

2 release files

0.0.33

2 release files

0.0.32

1 release file

0.0.31

1 release file

0.0.30

2 release files

0.0.22

2 release files

0.0.21

2 release files

0.0.20

2 release files

0.0.19

2 release files

0.0.18

2 release files

0.0.9

2 release files

0.0.8

2 release files

0.0.6

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page