Metadata-Version: 2.4
Name: fschema
Version: 0.1.0
Summary: Marshmallow-like schematization of a directory structure
Author: Grigorii Statsenko
License-Expression: MIT
Project-URL: Homepage, https://github.com/altvod/fschema
Project-URL: Repository, https://github.com/altvod/fschema
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: build>=1; extra == "dev"
Requires-Dist: ruff>=0.8; extra == "dev"
Requires-Dist: twine>=5; extra == "dev"
Provides-Extra: marshmallow
Requires-Dist: marshmallow>=3; extra == "marshmallow"
Provides-Extra: yaml
Requires-Dist: PyYAML>=6; extra == "yaml"
Provides-Extra: all
Requires-Dist: marshmallow>=3; extra == "all"
Requires-Dist: PyYAML>=6; extra == "all"
Dynamic: license-file

# fschema
Marshmallow-like schematization of a directory structure

## Quickstart Example

Let's say you have the following directory/file structure:
```
config/
  + plugins/
  |   + java/
  |   |   + plugin.yaml
  |   + python/
  |   |   + plugin.yaml
  + profiles/
  |   + new.yaml
  |   + old.yaml
  + env
  + config.yaml
```

You can describe it as a Python model and load everything into a single structure.
Schemas describe the structure, fields describe how each node is loaded, and filesystem access goes
through an `FSInterface`. `FSLoader` uses `LocalFSInterface` by default, but custom filesystem-like
backends can be provided with `FSLoader(schema=..., fs=...)`.

```python
from fschema.fields import meta, node
from fschema.schema import Schema
from fschema.fs_loader import FSLoader

class PluginConfigSchema(Schema):
    name = meta.NodeName()
    config = node.File(fs_name="plugin.yaml")

class ProfileConfigSchema(Schema):
    name = meta.NodeName()
    config = meta.Content()

class ServiceConfigSchema(Schema):
    config = node.File(fs_name="config.yaml")
    env = node.File(fs_name="env")
    plugins = node.ListDirectory(node.SchematizedDirectory(PluginConfigSchema()))
    profiles = node.ListDirectory(node.SchematizedFile(ProfileConfigSchema()))

data = FSLoader(schema=ServiceConfigSchema()).load("/path/to/config")
print(data)
```

This will load the following data:
```json
{
  "config": "<file-content>",
  "env": "<file-content>",
  "plugins": [
    {"name": "java", "config":  "<file-content>"},
    {"name": "python", "config":  "<file-content>"}
  ],
  "profiles": [
    {"name": "new.yaml", "config":  "<file-content>"},
    {"name": "old.yaml", "config":  "<file-content>"}
  ]
}
```

If you want to add post-processing of the data to your schema
(e.g. validate it or convert it to an object), you can define a `__fschema_post_load__` method:
```python
class ServiceConfigSchema(Schema):
    ...
    def __fschema_post_load__(self, data: dict) -> ServiceConfiguration:
        return ServiceConfiguration(**data)
```


## Reference

### Fields

#### Meta Fields

Meta fields are the fields that use the metadata of the respective filesystem node (directory/file)
and provide access to its various properties.
All meta fields inherit from `MetaField`.

Meta field types:
- `NodeName()` - special type of field that loads the name of the current node (directory or file)
- `Content(reader: Reader, data_transformer: DataTransformer)` - for use inside a sub-schema of a `SchematizedFile`;
  `reader` parses content provided by `FSLoader` to JSON-like data;
  `data_transformer` loads it into an object and/or validates the data

#### Node Fields

Node fields correspond to actual filesystem nodes (directories/fields).
All node fields inherit from `NodeField`.

All node fields have the optional argument `fs_name` - this is the name of the filesystem node
the field corresponds to - useful if the filename has a period (`.`) in it,
and, therefore cannot be used as the field's attribute name.

Exposed properties:
- `effective_fs_name` - the resolved filesystem name: the explicit `fs_name` when provided,
  otherwise the schema attribute name.

Node field types:
- `SchematizedDirectory(directory_schema: Schema, fs_name: str | None)` - load directory as a key-value mapping
  and apply the given sub-schema to the directory itself;
  this means nested files and directories must have fixed names
- `DictDirectory(nested_field: Field, fs_name: str | None)` - load directory as a free mapping, without fixed key values;
  the given field instance is applied to all nested nodes
- `ListDirectory(nested_field: Field, fs_name: str | None)` - load directory as a list of nodes;
  the given field instance is applied to all nested nodes
- `File(fs_name: str | None, reader: Reader, data_transformer: DataTransformer)` - load file content;
  `reader` parses content provided by `FSLoader` to JSON-like data;
  `data_transformer` loads it into an object and/or validates the data
- `SchematizedFile(file_schema: Schema, fs_name: str | None)` - load the file as a schematized mapping instead of a single flat object;
  this is useful if you need access to its metadata (e.g. via `NodeName`);

### Content Readers

Available content readers:
- `JSONReader` - parses content as JSON (as a `dict`)
- `YamlReader` - parses content as YAML (as a `dict`)
- `TextReader` - returns content as text (`str`); this is the default reader

### Filesystem Interface

Filesystem access is abstracted behind `FSInterface`, available from `fschema.fs`.
The default `LocalFSInterface`, also available from `fschema.fs`, supports local paths via `pathlib`.
Custom backends can implement:
- `node_name(path: Path) -> str`
- `child_path(path: Path, fs_name: str) -> Path`
- `list_directory(path: Path) -> list[Path]`
- `require_file(path: Path) -> None`
- `require_directory(path: Path) -> None`
- `read_file(path: Path, encoding="utf-8") -> str`

### Data Transformers

Available data transformers:
- `MarshmallowLoader(schema: Any)` - loads the file data via a `marshmallow` schema
