diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..e02377b --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,59 @@ +name: Deploy Documentation + +on: + push: + branches: + - main + pull_request: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Check out repo + uses: actions/checkout@v6 + - name: Setup environment + uses: ./.github/actions/environment + with: + app-id: ${{ secrets.CI_BOT_ID }} + private-key: ${{ secrets.CI_BOT_KEY }} + + - name: Install dependencies + run: | + uv sync --frozen --group docs + + - name: Build documentation + run: | + uv run mkdocs build + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: site + + deploy: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + with: + publish_dir: ./site + publish_branch: gh-pages diff --git a/.gitignore b/.gitignore index 505a3b1..f42feb8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ wheels/ # Virtual environments .venv + +site/ \ No newline at end of file diff --git a/docs/_static/altrove_logo.png b/docs/_static/altrove_logo.png new file mode 100755 index 0000000..f76270c Binary files /dev/null and b/docs/_static/altrove_logo.png differ diff --git a/docs/_static/custom_css.css b/docs/_static/custom_css.css new file mode 100644 index 0000000..5cdd94c --- /dev/null +++ b/docs/_static/custom_css.css @@ -0,0 +1,178 @@ +/* Fix /page#foo going to the top of the viewport and being hidden by the navbar */ +html { + scroll-padding-top: 50px; +} + +.md-header__button.md-logo img { + height: 1.8rem; /* adjust as needed */ +} + +/* Fit the Twitter handle alongside the GitHub one in the top right. */ + +div.md-header__source { + width: revert; + max-width: revert; +} + +a.md-source { + display: inline-block; +} + +.md-source__repository { + max-width: 100%; +} + +/* Emphasise sections of nav on left hand side */ + +nav.md-nav { + padding-left: 5px; +} + +nav.md-nav--secondary { + border-left: revert !important; +} + +.md-nav__title { + font-size: 0.9rem; +} + +.md-nav__item--section > .md-nav__link { + font-size: 0.9rem; +} + +/* Indent autogenerated documentation */ + +div.doc-contents { + padding-left: 25px; + border-left: 4px solid rgba(230, 230, 230); +} + +/* Increase visibility of splitters "---" */ + +[data-md-color-scheme="default"] .md-typeset hr { + border-bottom-color: rgb(0, 0, 0); + border-bottom-width: 1pt; +} + +[data-md-color-scheme="slate"] .md-typeset hr { + border-bottom-color: rgb(230, 230, 230); +} + +/* More space at the bottom of the page */ + +.md-main__inner { + margin-bottom: 1.5rem; +} + +/* Remove prev/next footer buttons */ + +.md-footer__inner { + display: none; +} + +/* Change font sizes */ + +html { + /* Decrease font size for overall webpage + Down from 137.5% which is the Material default */ + font-size: 110%; +} + +.md-typeset .admonition { + /* Increase font size in admonitions */ + font-size: 100% !important; +} + +.md-typeset details { + /* Increase font size in details */ + font-size: 100% !important; +} + +.md-typeset h1 { + font-size: 1.6rem; +} + +.md-typeset h2 { + font-size: 1.5rem; +} + +.md-typeset h3 { + font-size: 1.3rem; +} + +.md-typeset h4 { + font-size: 1.1rem; +} + +.md-typeset h5 { + font-size: 0.9rem; +} + +.md-typeset h6 { + font-size: 0.8rem; +} + +/* Bugfix: remove the superfluous parts generated when doing: + +??? Blah + + ::: library.something +*/ + +.md-typeset details .mkdocstrings > h4 { + display: none; +} + +.md-typeset details .mkdocstrings > h5 { + display: none; +} + +/* Change default colours for tags */ + +[data-md-color-scheme="default"] { + --md-typeset-a-color: rgb(0, 189, 164) !important; +} +[data-md-color-scheme="slate"] { + --md-typeset-a-color: rgb(0, 189, 164) !important; +} + +/* Highlight functions, classes etc. type signatures. Really helps to make clear where + one item ends and another begins. */ + +[data-md-color-scheme="default"] { + --doc-heading-color: #DDD; + --doc-heading-border-color: #CCC; + --doc-heading-color-alt: #F0F0F0; +} +[data-md-color-scheme="slate"] { + --doc-heading-color: rgb(25,25,33); + --doc-heading-border-color: rgb(25,25,33); + --doc-heading-color-alt: rgb(33,33,44); + --md-code-bg-color: rgb(38,38,50); +} + +h4.doc-heading { + /* NOT var(--md-code-bg-color) as that's not visually distinct from other code blocks.*/ + background-color: var(--doc-heading-color); + border: solid var(--doc-heading-border-color); + border-width: 1.5pt; + border-radius: 2pt; + padding: 0pt 5pt 2pt 5pt; +} +h5.doc-heading, h6.heading { + background-color: var(--doc-heading-color-alt); + border-radius: 2pt; + padding: 0pt 5pt 2pt 5pt; +} + +/* Make errors in notebooks have scrolling */ +.output_error > pre { + overflow: auto; +} + +@font-face { + font-family: 'Ingeo'; + src: url('./fonts/Ingeo-SemiBold.ttf') format('truetype'); + font-weight: 600; + font-style: normal; +} diff --git a/docs/_static/fonts/Ingeo-SemiBold.ttf b/docs/_static/fonts/Ingeo-SemiBold.ttf new file mode 100644 index 0000000..9566e0f Binary files /dev/null and b/docs/_static/fonts/Ingeo-SemiBold.ttf differ diff --git a/docs/api-reference/cli.md b/docs/api-reference/cli.md new file mode 100644 index 0000000..79f9594 --- /dev/null +++ b/docs/api-reference/cli.md @@ -0,0 +1,65 @@ +# CLI API + +Command-line interface functions. + +## Overview + +The CLI module provides the `parse_cli()` function for easy command-line integration. + +## parse_cli() + +Parse configuration from command-line arguments: + +```python +import fiddledyn as dyn +import fiddle as fdl + +# Parse and build +config = dyn.parse_cli() +obj = fdl.build(config) +``` + +## Usage Examples + +### Basic Usage + +```python +# main.py +import fiddledyn as dyn +import fiddle as fdl + +config = dyn.parse_cli() +obj = fdl.build(config) +``` + +Run with: + +```bash +python main.py -f config.yaml model.lr=0.001 +``` + +### With Custom Processing + +```python +config = dyn.parse_cli() + +# Apply custom transformations +if config.get('debug'): + # Enable debug mode + pass + +obj = fdl.build(config) +``` + +## Command-Line Syntax + +- `-f config.yaml` - Load configuration file +- `key=value` - Override parameter value +- `key=@file.yaml` - Load value from file + +## See Also + +- [CLI Overrides](../features/cli-overrides.md) +- [Quick Start](../getting-started/quick-start.md) + + diff --git a/docs/api-reference/core.md b/docs/api-reference/core.md new file mode 100644 index 0000000..1cddae5 --- /dev/null +++ b/docs/api-reference/core.md @@ -0,0 +1,38 @@ +# Core API + +Core components of FiddleDyn for managing configuration state and references. + +## Overview + +The core module provides the fundamental building blocks for FiddleDyn: + +- **ParserContext** - Manages state during parsing including registry and references +- **Reference** - Represents DAG references with IDs +- **Types** - Core type definitions + +## ParserContext + +The `ParserContext` class tracks state during configuration parsing: + +```python +from fiddledyn.core import ParserContext + +ctx = ParserContext() +ctx.registry["key"] = "value" # Add to registry +``` + +### Methods + +- `registry` - Dictionary for storing placeholder values +- `references` - Dictionary for tracking DAG references + +## Key Classes + +See the source code in `src/fiddledyn/core/` for detailed implementation. + +## See Also + +- [Getting Started](../getting-started/installation.md) +- [Basic Concepts](../getting-started/basic-concepts.md) + + diff --git a/docs/api-reference/io.md b/docs/api-reference/io.md new file mode 100644 index 0000000..75689df --- /dev/null +++ b/docs/api-reference/io.md @@ -0,0 +1,58 @@ +# IO API + +File loading and configuration I/O functions. + +## Overview + +The IO module provides convenient functions for loading and saving configurations: + +- **load_yaml()** - Load YAML configuration files +- **parse_cli()** - Parse command-line arguments for configuration +- **serialize()** - Serialize configurations to different formats + +## Main Functions + +### load_yaml() + +Load a YAML configuration file: + +```python +import fiddledyn as dyn + +config = dyn.load_yaml("config.yaml") +``` + +With a parser context: + +```python +ctx = dyn.ParserContext() +config = dyn.load_yaml("config.yaml", ctx=ctx) +``` + +### parse_cli() + +Parse configuration from command-line arguments: + +```python +config = dyn.parse_cli() +``` + +This automatically handles: +- `-f` flag for config file path +- Override arguments like `key=value` +- File references with `@file.yaml` + +### resolve_placeholders() + +Resolve placeholder references in configuration: + +```python +dyn.resolve_placeholders(config, ctx.registry) +``` + +## See Also + +- [Quick Start](../getting-started/quick-start.md) +- [CLI Overrides](../features/cli-overrides.md) + + diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..ea43d9b --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,166 @@ +# Contributing + +We welcome contributions to FiddleDyn! This guide will help you get started. + +## Setting Up Development Environment + +### Prerequisites + +- Python 3.12+ +- [uv](https://github.com/astral-sh/uv) package manager + +### Install from Source + +```bash +git clone https://github.com/altrove-ai/fiddledyn.git +cd fiddledyn +uv sync +``` + +This installs the project in editable mode with all development dependencies. + +## Running Tests + +Run the test suite: + +```bash +pytest +``` + +Run with coverage: + +```bash +pytest --cov=src/fiddledyn --cov-report=html +``` + +Run specific tests: + +```bash +pytest tests/test_cli.py -v +``` + +## Code Quality + +We use several tools to maintain code quality: + +### Linting with Ruff + +```bash +ruff check src/ tests/ +``` + +Auto-fix issues: + +```bash +ruff check --fix src/ tests/ +``` + +### Type Checking with Pyright + +```bash +pyright +``` + +## Documentation + +Documentation is built with [MkDocs](https://www.mkdocs.org/) and the [Material theme](https://squidfunk.github.io/mkdocs-material/). + +### Build Locally + +```bash +pip install mkdocs mkdocs-material mkdocstrings +mkdocs serve +``` + +Visit `http://localhost:8000` to see the docs. + +### Add New Documentation + +1. Create a new `.md` file in the `docs/` directory +2. Update `mkdocs.yml` to include the new page in the navigation +3. Write your documentation using Markdown +4. Run `mkdocs serve` to preview + +## Development Workflow + +1. Create a feature branch: + ```bash + git checkout -b feature/your-feature-name + ``` + +2. Make your changes and add tests + +3. Run tests and code quality checks: + ```bash + pytest + ruff check --fix src/ tests/ + pyright + ``` + +4. Commit your changes: + ```bash + git add . + git commit -m "Add descriptive commit message" + ``` + +5. Push to your fork and open a pull request + +## Project Structure + +``` +fiddledyn/ +├── src/fiddledyn/ # Main source code +│ ├── core/ # Core classes (ParserContext, Reference, etc.) +│ ├── parsing/ # YAML parsing logic +│ ├── resolution/ # Placeholder resolution +│ ├── serialization/ # Serialization to different backends +│ ├── cli.py # Command-line interface +│ ├── io.py # File I/O functions +│ └── utils.py # Utility functions +├── tests/ # Test suite +├── docs/ # Documentation source +├── pyproject.toml # Project configuration +├── ruff.toml # Ruff configuration +└── README.md # Project README +``` + +## Key Concepts + +Before contributing, familiarize yourself with these concepts: + +- **ParserContext** - Tracks state during parsing (registry, references) +- **Reference** - DAG reference with `_id_` and `_ref_` +- **Placeholder** - Unresolved value marked with `$` +- **Partial** - Configuration marked with `_partial_: true` +- **CLI Overrides** - Modifications via command-line arguments + +See [Basic Concepts](../getting-started/basic-concepts.md) for more details. + +## Reporting Issues + +Found a bug? Please open an issue on GitHub with: + +- Clear description of the problem +- Steps to reproduce +- Expected vs actual behavior +- Python version and environment + +## Asking Questions + +Have a question? + +- Check the [documentation](https://github.com/altrove-ai/fiddledyn) +- Search existing issues +- Open a new discussion on GitHub + +## Code of Conduct + +Please be respectful and constructive in all interactions. We aim to maintain a welcoming and inclusive community. + +## License + +By contributing, you agree that your contributions will be licensed under the project's license. + +## Questions? + +Feel free to open an issue or start a discussion on GitHub. We're happy to help! diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 0000000..64d71cd --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,247 @@ +# Examples + +Practical examples demonstrating FiddleDyn features. + +## Basic Configuration + +A simple example with a model and optimizer: + +```yaml +# config.yaml +model: + type: SimpleModel + hidden_dim: 256 + dropout: 0.1 + +optimizer: + type: Adam + lr: 0.001 + betas: [0.9, 0.999] + +batch_size: 32 +num_epochs: 10 +``` + +Load and use: + +```python +import fiddledyn as dyn +import fiddle as fdl + +config = dyn.parse_cli() +obj = fdl.build(config) + +print(f"Learning rate: {obj.optimizer.lr}") +print(f"Model dimension: {obj.model.hidden_dim}") +``` + +Run with overrides: + +```bash +python train.py -f config.yaml model.hidden_dim=512 optimizer.lr=0.01 +``` + +## Multi-File Configuration + +Organize large projects with multiple files. + +**Directory structure:** + +``` +configs/ +├── base.yaml +├── models/ +│ ├── small.yaml +│ └── large.yaml +├── optimizers/ +│ ├── adam.yaml +│ └── sgd.yaml +└── datasets/ + ├── mnist.yaml + └── cifar10.yaml +``` + +**configs/base.yaml:** + +```yaml +model: {} # Will be overridden +optimizer: {} # Will be overridden +dataset: {} # Will be overridden +num_epochs: 100 +``` + +**configs/models/large.yaml:** + +```yaml +type: ResNet +depth: 152 +width_multiplier: 2 +``` + +**configs/optimizers/adam.yaml:** + +```yaml +type: Adam +lr: 0.001 +weight_decay: 0.0001 +``` + +Run with different combinations: + +```bash +# Small model with SGD +python train.py -f configs/base.yaml \ + model=@configs/models/small.yaml \ + optimizer=@configs/optimizers/sgd.yaml + +# Large model with Adam +python train.py -f configs/base.yaml \ + model=@configs/models/large.yaml \ + optimizer=@configs/optimizers/adam.yaml +``` + +## Shared Components with DAG References + +Define a reusable component: + +```yaml +# configs/shared/encoder.yaml +_id_: shared_encoder +type: TransformerEncoder +hidden_dim: 512 +num_layers: 6 +num_heads: 8 +``` + +Use it in multiple places: + +```yaml +# configs/model.yaml +encoder: + _ref_: shared_encoder + +decoder: + _ref_: shared_encoder # Same instance! + +projection: + input_dim: 512 # From encoder +``` + +Load with context: + +```python +import fiddledyn as dyn + +ctx = dyn.ParserContext() +config = dyn.load_yaml("configs/model.yaml", ctx=ctx) +# encoder and decoder now share the same object +``` + +## Partial Configurations for Flexibility + +Define model factories: + +```yaml +# configs/models/factory.yaml +factory: + _partial_: true + type: TransformerModel + hidden_dim: 512 + num_layers: 6 +``` + +Use with different input sizes: + +```python +import fiddledyn as dyn +import fiddle as fdl + +config = dyn.load_yaml("configs/models/factory.yaml") +model_fn = fdl.build(config.factory) + +# Create models with different input dimensions +small_model = model_fn(input_dim=128) +large_model = model_fn(input_dim=1024) +``` + +## Complex Training Configuration + +A complete training example: + +```yaml +# configs/train.yaml +data: + train_split: 0.8 + batch_size: 32 + shuffle: true + num_workers: 4 + +model: + type: TransformerModel + hidden_dim: 512 + num_layers: 6 + dropout: 0.1 + +optimizer: + type: AdamW + lr: 0.001 + weight_decay: 0.01 + +scheduler: + type: CosineAnnealingLR + T_max: 100 + eta_min: 1.0e-6 + +training: + num_epochs: 100 + warmup_epochs: 5 + log_interval: 100 + save_interval: 1000 + +callbacks: + - type: EarlyStopping + monitor: val_loss + patience: 10 + - type: ModelCheckpoint + dirpath: ./checkpoints +``` + +Load and train: + +```python +import fiddledyn as dyn +import fiddle as fdl + +config = dyn.parse_cli() +cfg = fdl.build(config) + +# Access config values +train_loader = create_dataloader(**cfg.data) +model = create_model(**cfg.model) +optimizer = create_optimizer(model.parameters(), **cfg.optimizer) +scheduler = create_scheduler(optimizer, **cfg.scheduler) + +# Train with your framework +for epoch in range(cfg.training.num_epochs): + # Your training loop here + pass +``` + +Run with different configurations: + +```bash +# Default configuration +python train.py -f configs/train.yaml + +# Quick experiment with smaller model +python train.py -f configs/train.yaml model.hidden_dim=256 + +# Production run with larger batch size +python train.py -f configs/train.yaml data.batch_size=128 +``` + +## See Also + +- [Quick Start](getting-started/quick-start.md) +- [Features](features/cli-overrides.md) - Learn more about each feature +- [API Reference](api-reference/core.md) - Detailed API documentation diff --git a/docs/features/callable-references.md b/docs/features/callable-references.md new file mode 100644 index 0000000..7a43c4e --- /dev/null +++ b/docs/features/callable-references.md @@ -0,0 +1,178 @@ +# Callable References + +Pass functions, classes, and other callables as configuration values. + +## Motivation + +Sometimes you need to pass a callable (function, class, method) as a configuration value, rather than the result of calling it. FiddleDyn makes this easy with the `_call_: false` syntax. + +## Basic Usage + +Mark callables with `_call_: false`: + +```yaml +loss_fn: + _call_: false + _target_: torch.nn.CrossEntropyLoss +``` + +When built, this returns the class itself, not an instance: + +```python +import fiddledyn as dyn +import fiddle as fdl + +config = dyn.load_yaml("config.yaml") +obj = fdl.build(config) + +# obj.loss_fn is the CrossEntropyLoss class, not an instance +loss = obj.loss_fn(reduction='mean') # Call it yourself +``` + +## Examples + +### Custom Loss Functions + +```yaml +model: + loss_fn: + _call_: false + _target_: my_module.CustomLoss + metrics: + - _call_: false + _target_: sklearn.metrics.accuracy_score + - _call_: false + _target_: sklearn.metrics.f1_score +``` + +### Activation Functions + +```yaml +layers: + - type: Linear + dim_out: 512 + activation: + _call_: false + _target_: torch.nn.ReLU + + - type: Linear + dim_out: 256 + activation: + _call_: false + _target_: torch.nn.GELU +``` + +### Callbacks and Hooks + +```yaml +training: + callbacks: + - _call_: false + _target_: pytorch_lightning.callbacks.EarlyStopping + monitor: val_loss + patience: 10 + + - _call_: false + _target_: pytorch_lightning.callbacks.ModelCheckpoint + dirpath: ./checkpoints +``` + +## Combined with Partial + +Use `_call_: false` with partial configurations: + +```yaml +optimizer_factory: + _partial_: true + _call_: false + _target_: torch.optim.Adam +``` + +This returns the Adam class itself (not built or instantiated), which you can then use as a partial later. + +## Use Cases + +### 1. Pluggable Components + +Allow users to provide custom implementations: + +```python +import fiddledyn as dyn +import fiddle as fdl + +config = dyn.load_yaml("config.yaml") +obj = fdl.build(config) + +# User-provided loss function +my_loss = obj.loss_fn(weight=[1, 2, 3]) +``` + +### 2. Callbacks and Hooks + +Pass callbacks to frameworks: + +```yaml +trainer: + callbacks: + - _call_: false + _target_: my_callbacks.LoggingCallback + - _call_: false + _target_: my_callbacks.ValidateCallback +``` + +```python +config = dyn.load_yaml("config.yaml") +trainer_config = fdl.build(config.trainer) + +# trainer_config.callbacks contains the callback classes +trainer = Trainer(**trainer_config) +``` + +### 3. Dynamic Function Selection + +Choose behavior based on configuration: + +```yaml +data_processing: + preprocessor: + _call_: false + _target_: my_module.StandardScaler + + augmentation: + _call_: false + _target_: my_module.RandomAugmentation +``` + +```python +config = dyn.load_yaml("config.yaml") +processing = fdl.build(config.data_processing) + +# Get the classes +preprocessor = processing.preprocessor() +augmenter = processing.augmentation(seed=42) +``` + +## Comparison: `_call_: true` vs `_call_: false` + +| Setting | Returns | Use Case | +|---------|---------|----------| +| `_call_: true` (default) | Instance | You want the object immediately | +| `_call_: false` | Callable (class/function) | You want to call it yourself later | + +## Type Hints + +When using callable references, type hints help your IDE: + +```python +from typing import Type, Callable +import torch.nn as nn + +class Config: + loss_fn: Type[nn.Module] # The class itself + activation: Callable[[torch.Tensor], torch.Tensor] # Any callable +``` + +## See Also + +- [Partial Configs](partial-configs.md) +- [Basic Concepts](../getting-started/basic-concepts.md) diff --git a/docs/features/cli-overrides.md b/docs/features/cli-overrides.md new file mode 100644 index 0000000..9f4083c --- /dev/null +++ b/docs/features/cli-overrides.md @@ -0,0 +1,102 @@ +# CLI Overrides + +Modify configuration values from the command line without editing files. + +## Basic Syntax + +Use dot notation to specify nested values: + +```bash +python main.py -f config.yaml key=value +``` + +## Nested Values + +Access nested parameters with dots: + +```bash +python main.py -f config.yaml model.encoder.dim=512 +``` + +This modifies: + +```yaml +model: + encoder: + dim: 512 # Changed from original value +``` + +## Type Inference + +FiddleDyn automatically infers types from the original config: + +```bash +# Original config has model.lr: 0.001 (float) +python main.py -f config.yaml model.lr=0.01 + +# Correctly parsed as float, not string +``` + +## Boolean Values + +Use `true` or `false` for boolean values: + +```bash +python main.py -f config.yaml model.use_dropout=true +``` + +## Lists and Collections + +Specify list indices using bracket notation: + +```bash +python main.py -f config.yaml model.layers.0=128 +``` + +## File References + +Replace entire sections with another configuration file using `@`: + +```bash +python main.py -f config.yaml optimizer=@adam.yaml +``` + +This merges `adam.yaml` at the `optimizer` key. + +## Multiple Overrides + +Chain multiple overrides: + +```bash +python main.py -f config.yaml \ + model.lr=0.01 \ + batch_size=64 \ + optimizer=@adam.yaml +``` + +## Practical Examples + +### Change Learning Rate and Batch Size + +```bash +python train.py -f configs/base.yaml model.lr=0.001 batch_size=128 +``` + +### Use Alternative Optimizer + +```bash +python train.py -f configs/base.yaml optimizer=@configs/optimizers/adam.yaml +``` + +### Experiment with Architecture + +```bash +python train.py -f configs/base.yaml \ + model.encoder.dim=512 \ + model.encoder.layers=8 \ + model.decoder.dim=512 +``` + +## CLI API Reference + +See [CLI Documentation](../api-reference/cli.md) for programmatic usage. diff --git a/docs/features/dag-references.md b/docs/features/dag-references.md new file mode 100644 index 0000000..9dadd3f --- /dev/null +++ b/docs/features/dag-references.md @@ -0,0 +1,138 @@ +# DAG References + +Share instances and configurations across multiple files using DAG references. + +## Motivation + +In complex systems, you often want to reuse the same component in multiple places. DAG references enable this pattern while maintaining clear data flow. + +## Using DAG References + +### Define a Reference + +Use `_id_` to mark a configuration block as reusable: + +```yaml +# encoders.yaml +_id_: transformer_encoder +type: TransformerEncoder +dim: 512 +num_layers: 6 +num_heads: 8 +``` + +### Reference It Later + +Use `_ref_` to point to a previously defined ID: + +```yaml +# model.yaml +encoder: + _ref_: transformer_encoder + +decoder: + _ref_: transformer_encoder # Same instance! +``` + +Both `encoder` and `decoder` will point to the exact same object after building. + +## Loading Multiple Files + +When loading files with references, use a context to track IDs: + +```python +import fiddledyn as dyn + +ctx = dyn.ParserContext() + +# Load files - they build up the registry +config1 = dyn.load_yaml("encoders.yaml", ctx=ctx) +config2 = dyn.load_yaml("model.yaml", ctx=ctx) + +# Resolve references +dyn.resolve_placeholders(config2, ctx.registry) + +import fiddle as fdl +model = fdl.build(config2) +``` + +## Nested IDs + +You can define IDs at any nesting level: + +```yaml +model: + _id_: shared_model + encoder: + dim: 256 + decoder: + dim: 256 +``` + +Reference the entire sub-config: + +```yaml +trainer: + primary_model: + _ref_: shared_model + backup_model: + _ref_: shared_model +``` + +## Practical Example: Multi-GPU Training + +Define a single encoder configuration: + +```yaml +# modules/encoder.yaml +_id_: main_encoder +type: TransformerEncoder +dim: 768 +layers: 12 +``` + +Use it in multiple models: + +```yaml +# configs/model_a.yaml +encoder: + _ref_: main_encoder +decoder: + type: MLPDecoder + input_dim: 768 + +# configs/model_b.yaml +encoder: + _ref_: main_encoder # Same encoder! +projection: + input_dim: 768 +``` + +## Benefits + +- **Consistency** - Changes to the shared config apply everywhere +- **Memory Efficiency** - Shared instances use less memory +- **Clear Dependencies** - Easy to see which components are shared +- **Maintainability** - Single source of truth for shared components + +## Advanced: Partial Sharing + +Combine DAG references with partial configs: + +```yaml +# base_model.yaml +_id_: base_model +_partial_: true +type: Model +encoder: + dim: 256 + +# experiment.yaml +model: + _ref_: base_model + encoder: + dim: 512 # Override for this instance +``` + +!!! note + References are resolved at build time, not at parse time. This allows flexibility in how and when you use the referenced configurations. diff --git a/docs/features/file-overrides.md b/docs/features/file-overrides.md new file mode 100644 index 0000000..d9110f7 --- /dev/null +++ b/docs/features/file-overrides.md @@ -0,0 +1,118 @@ +# File Overrides + +Replace entire configuration sections with alternative files. + +## Basic Usage + +Use the `@filename.yaml` syntax in CLI overrides: + +```bash +python main.py -f config.yaml optimizer=@optimizers/adam.yaml +``` + +This replaces the `optimizer` section with the contents of `optimizers/adam.yaml`. + +## Directory Structure + +Organize your configs logically: + +``` +configs/ +├── base.yaml +├── optimizers/ +│ ├── adam.yaml +│ ├── sgd.yaml +│ └── adamw.yaml +├── models/ +│ ├── small.yaml +│ ├── base.yaml +│ └── large.yaml +└── schedulers/ + ├── constant.yaml + └── cosine.yaml +``` + +## Examples + +### Swap Optimizer + +```bash +# Use Adam optimizer +python train.py -f configs/base.yaml optimizer=@configs/optimizers/adam.yaml + +# Use SGD optimizer +python train.py -f configs/base.yaml optimizer=@configs/optimizers/sgd.yaml +``` + +### Change Model Size + +```bash +# Small model +python train.py -f configs/base.yaml model=@configs/models/small.yaml + +# Large model +python train.py -f configs/base.yaml model=@configs/models/large.yaml +``` + +### Multiple File Overrides + +```bash +python train.py -f configs/base.yaml \ + model=@configs/models/large.yaml \ + optimizer=@configs/optimizers/adamw.yaml \ + scheduler=@configs/schedulers/cosine.yaml +``` + +## File Contents + +Each file should contain valid YAML: + +```yaml +# configs/optimizers/adam.yaml +type: Adam +lr: 0.001 +betas: [0.9, 0.999] +eps: 1.0e-8 +weight_decay: 0.0 + +# configs/optimizers/sgd.yaml +type: SGD +lr: 0.01 +momentum: 0.9 +weight_decay: 0.0001 +``` + +## Combining with Other Overrides + +Mix file overrides with parameter overrides: + +```bash +python train.py -f configs/base.yaml \ + optimizer=@configs/optimizers/adam.yaml \ + optimizer.lr=0.01 # Override the loaded file's lr +``` + +## Relative Paths + +File references use paths relative to the current working directory: + +```bash +# If running from project root +python train.py -f configs/base.yaml optimizer=@configs/optimizers/adam.yaml + +# If running from configs/ directory +cd configs +python ../train.py -f base.yaml optimizer=@optimizers/adam.yaml +``` + +## Tips + +- **Use descriptive names** - `adamw.yaml` is clearer than `opt1.yaml` +- **Organize hierarchically** - Group related configs in subdirectories +- **Document options** - Add comments to config files explaining the settings +- **Version your configs** - Keep important combinations under source control + +## See Also + +- [CLI Overrides](cli-overrides.md) - Learn about parameter overrides +- [Basic Concepts](../getting-started/basic-concepts.md) - Understand configuration basics diff --git a/docs/features/partial-configs.md b/docs/features/partial-configs.md new file mode 100644 index 0000000..85811b8 --- /dev/null +++ b/docs/features/partial-configs.md @@ -0,0 +1,170 @@ +# Partial Configurations + +Defer object instantiation by using partial configurations. + +## What is a Partial Config? + +A **partial configuration** specifies how to create an object, but doesn't actually create it. When built, it returns a callable (partial function) that you can invoke later with additional arguments. + +## Basic Usage + +Mark a configuration as partial with `_partial_: true`: + +```yaml +model: + _partial_: true + type: MyModel + hidden_dim: 256 +``` + +Build it: + +```python +import fiddledyn as dyn +import fiddle as fdl + +config = dyn.load_yaml("config.yaml") +partial_fn = fdl.build(config) + +# partial_fn is now a callable function +# Call it later with additional arguments +model = partial_fn(input_size=10) +``` + +## Real-World Example + +Define a model factory: + +```yaml +# configs/models/transformer.yaml +model: + _partial_: true + _target_: TransformerModel + hidden_dim: 512 + num_layers: 6 + num_heads: 8 + dropout: 0.1 +``` + +Use it to create models with different input sizes: + +```python +import fiddledyn as dyn +import fiddle as fdl + +config = dyn.load_yaml("configs/models/transformer.yaml") +model_factory = fdl.build(config.model) + +# Create model for different input sizes +small_model = model_factory(input_dim=128) +large_model = model_factory(input_dim=1024) +``` + +## Partial with Positional Arguments + +Use `_args_` to specify positional arguments that should be passed when the partial is called: + +```yaml +optimizer: + _partial_: true + _target_: torch.optim.Adam + _args_: + - lr: 0.001 + betas: [0.9, 0.999] +``` + +When called later: + +```python +optimizer = fdl.build(config.optimizer) +# optimizer now expects the parameters argument +actual_optimizer = optimizer(model.parameters()) +``` + +## Nested Partials + +You can nest partial configurations: + +```yaml +training: + model: + _partial_: true + type: MyModel + dim: 256 + + optimizer: + _partial_: true + type: Adam + lr: 0.001 +``` + +When built, both return partials: + +```python +config = dyn.load_yaml("config.yaml") +training = fdl.build(config.training) + +# Both are partials +model = training.model(...) +optimizer = training.optimizer(...) +``` + +## Use Cases + +### 1. Data Loading + +Create data loaders with variable batch sizes: + +```yaml +data: + _partial_: true + type: DataLoader + dataset: $dataset_config + num_workers: 4 + shuffle: true +``` + +```python +data_loader_fn = fdl.build(config.data) +train_loader = data_loader_fn(batch_size=32) +val_loader = data_loader_fn(batch_size=128) +``` + +### 2. Model Architecture + +Create model factories for different input dimensions: + +```yaml +encoder: + _partial_: true + type: TransformerEncoder + hidden_dim: 512 + num_layers: 6 +``` + +### 3. Deferred Initialization + +Initialize components that depend on runtime information: + +```python +# Config specifies the structure +config = dyn.load_yaml("config.yaml") + +# Get partial function +create_model = fdl.build(config.model) + +# Initialize with runtime information +model = create_model(vocab_size=10000) +``` + +## Benefits + +- **Flexibility** - Delay decisions until runtime +- **Reusability** - Use the same config for different scenarios +- **Type Safety** - Maintain type information through the partial +- **Composition** - Easily combine partial configs + +## See Also + +- [Basic Concepts](../getting-started/basic-concepts.md) +- [Quick Start](../getting-started/quick-start.md) diff --git a/docs/getting-started/basic-concepts.md b/docs/getting-started/basic-concepts.md new file mode 100644 index 0000000..5a1281d --- /dev/null +++ b/docs/getting-started/basic-concepts.md @@ -0,0 +1,92 @@ +# Basic Concepts + +Understand the core terminology and concepts in FiddleDyn. + +## Configuration + +A **configuration** is a YAML or Python structure that defines how to instantiate objects. It contains parameter values, references to other configurations, and metadata about partial instantiation. + +## Fiddle + +FiddleDyn is built on top of [Fiddle](https://github.com/google/fiddle), Google's framework for configuration management. Fiddle provides: + +- **Config objects** - Declarative representations of function/class calls +- **Build** - Convert configs into actual objects +- **Serialization** - Convert configs to/from various formats + +## ParserContext + +A context object that tracks state during parsing: + +```python +ctx = dyn.ParserContext() +``` + +It stores: +- **Registry** - Key-value pairs of placeholder values +- **References** - DAG references and IDs for shared instances + +## Placeholders + +Placeholders are values to be filled later. Use the `@` symbol: + +```yaml +layer_size: 256 +model: + hidden_dim: $layer_size # Reference to layer_size +``` + +Resolve with: + +```python +dyn.resolve_placeholders(config, ctx.registry) +``` + +## Partial Configurations + +A **partial configuration** is one that's not fully instantiated. Mark with `_partial_: true`: + +```yaml +model: + _partial_: true + _target_: MyModel + dim: 256 +``` + +Building returns a partial function, not the actual object: + +```python +partial_fn = fdl.build(config) # Returns partial function +obj = partial_fn(x, y) # Call it later with additional args +``` + +## DAG References + +Use global DAG references to share instances across files: + +```yaml +# encoder.yaml +_id_: shared_encoder +encoder: + dim: 256 + layers: 4 + +# model.yaml +encoder: + _ref_: shared_encoder # Reference the shared encoder +decoder: + similar_to_encoder: true +``` + +## Backends + +FiddleDyn supports multiple serialization backends: + +- **FIDDLE** - Native Fiddle format +- **NEMO** - NeMo Run format (requires `nemo_run` package) + +Specify the backend when serializing: + +```python +dyn.serialize(config, backend=dyn.Backend.FIDDLE) +``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md new file mode 100644 index 0000000..8b62930 --- /dev/null +++ b/docs/getting-started/installation.md @@ -0,0 +1,51 @@ +# Installation + +## Prerequisites + +- Python 3.12 or higher +- pip or uv package manager + +## Core Installation + +To install FiddleDyn with Fiddle backend support: + +```bash +pip install fiddledyn +``` + +## With NeMo Run Support + +To use FiddleDyn with NeMo Run backend: + +```bash +pip install fiddledyn[nemo] +``` + +This installs the optional `nemo_run` dependency, enabling the NEMO backend for configuration serialization. + +## Development Installation + +Clone the repository and install in development mode: + +```bash +git clone https://github.com/altrove-ai/fiddledyn.git +cd fiddledyn +uv sync +``` + +Then run tests to verify the installation: + +```bash +pytest +``` + +## Verifying Installation + +To verify FiddleDyn is installed correctly: + +```python +import fiddledyn as dyn +print(dyn.__version__) +``` + +You should see the version number printed without errors. diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md new file mode 100644 index 0000000..9ba438f --- /dev/null +++ b/docs/getting-started/quick-start.md @@ -0,0 +1,74 @@ +# Quick Start + +Get up and running with FiddleDyn in 5 minutes. + +## 1. Create a Configuration File + +Create a `config.yaml` file: + +```yaml +model: + lr: 0.001 + encoder: + dim: 256 + layers: 4 + +optimizer: adam +batch_size: 32 +``` + +## 2. Load and Use Configuration + +Create `main.py`: + +```python +import fiddledyn as dyn +import fiddle as fdl + +# Parse configuration (from file or CLI) +config = dyn.parse_cli() + +# Build the configured object +trainer = fdl.build(config) +``` + +## 3. Run with CLI Overrides + +```bash +# Use default config +python main.py -f config.yaml + +# Override nested values +python main.py -f config.yaml model.lr=0.01 batch_size=64 + +# Use alternative config file +python main.py -f other_config.yaml +``` + +## 4. Using FiddleDyn Programmatically + +```python +import fiddledyn as dyn + +# Create a context for tracking references +ctx = dyn.ParserContext() + +# Load YAML with context +config = dyn.load_yaml("config.yaml", ctx=ctx) + +# Add registry values for placeholders +ctx.registry["size"] = 256 + +# Resolve any placeholders in the config +dyn.resolve_placeholders(config, ctx.registry) + +# Build the final object +import fiddle as fdl +obj = fdl.build(config) +``` + +## Next Steps + +- Learn about [Basic Concepts](basic-concepts.md) +- Explore [CLI Overrides](../features/cli-overrides.md) +- Check out the [API Reference](../api-reference/core.md) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..822ac21 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,42 @@ +# Welcome to FiddleDyn + +**Structure-Aware Configuration for Fiddle and NeMo Run** + +FiddleDyn extends [Fiddle](https://github.com/google/fiddle) and [NeMo Run](https://github.com/NVIDIA/nemo_run) with powerful features for configuration management: + +## Key Features + +- **CLI Overrides** - Deep nested values via dot notation +- **DAG References** - Share instances across files with `_id_`/`_ref_` +- **Partial Configs** - Deferred instantiation with `_partial_: true` +- **Callable References** - Pass any callable (class, function, method) +- **File Overrides** - Replace branches with `key=@file.yaml` +- **Round-Trip Safe** - Full serialization preserving all metadata + +## Quick Example + +```python +import fiddledyn as dyn +import fiddle as fdl + +# Load configuration from YAML +config = dyn.parse_cli() + +# Build the configured object +trainer = fdl.build(config) +``` + +```bash +# Use from command line with overrides +python main.py -f config.yaml model.lr=0.001 optimizer=@adam.yaml +``` + +## Getting Started + +- [Installation](getting-started/installation.md) - Set up FiddleDyn +- [Quick Start](getting-started/quick-start.md) - Your first configuration +- [Basic Concepts](getting-started/basic-concepts.md) - Core terminology + +## Documentation Structure + +Browse the documentation using the sidebar to explore features, API reference, and examples. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..423372e --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,132 @@ +theme: + name: material + features: + - navigation.tabs + - navigation.sections + - toc.integrate + - header.autohide + - content.action.edit + - content.tabs.link + - content.code.copy + - content.code.annotate + - content.tooltips + - navigation.instant + - navigation.indexes + - navigation.top + - search.highlight + - search.share + - content.tabs + - navigation.expand + - navigation.footer + - navigation.path + - navigation.tracking + - content.buttons + - content.grid + - content.footnotes + - versioning.switcher # header disappears as you scroll + palette: + # Light mode / dark mode + # We deliberately don't automatically use `media` to check a user's preferences. We default to light mode as + # (a) it looks more professional, and (b) is more obvious about the fact that it offers a (dark mode) toggle. + - scheme: default + primary: white + accent: amber + toggle: + icon: material/weather-night + name: Switch to dark mode + - scheme: slate + primary: black + accent: amber + toggle: + icon: material/weather-sunny + name: Switch to light mode + icon: + repo: fontawesome/brands/github # GitHub logo in top right + # Equinox logo in top left + favicon: "_static/altrove_logo.png" + + # These additions are my own custom ones, having overridden a partial. + website_name: "Altrove" + website_url: "https://altrove.ai/" + +extra: + version: + provider: mike + social: + - icon: fontawesome/brands/github + link: https://github.com/altrove-ai/fiddledyn + +extra_css: + - _static/custom_css.css + +site_name: fiddledyn +site_description: Data contracts and I/O utilities for atomic structure datasets +site_url: https://altrove-ai.github.io/fiddledyn +repo_url: https://github.com/altrove-ai/fiddledyn +repo_name: altrove-ai/fiddledyn + +markdown_extensions: + - pymdownx.highlight: + use_pygments: true + pygments_style: github-dark + pygments_lang_class: true + line_spans: __span + anchor_linenums: true + auto_title: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + - admonition + - pymdownx.details + - pymdownx.arithmatex: + generic: true + - footnotes + - pymdownx.critic + - pymdownx.caret + - pymdownx.keys + - pymdownx.mark + - pymdownx.tilde + - attr_list + - md_in_html + - toc: + permalink: true + +plugins: + - search + - autorefs # Cross-links to headings + - mkdocstrings: + #default_handler: python + handlers: + python: + options: + members_order: source + separate_signature: false + filters: ['!^_'] + docstring_options: + ignore_init_summary: true + merge_init_into_class: true + show_signature_annotations: false + signature_crossrefs: true + line_length: 120 + paths: [.src] + +nav: + - Home: index.md + - Getting Started: + - Installation: getting-started/installation.md + - Quick Start: getting-started/quick-start.md + - Basic Concepts: getting-started/basic-concepts.md + - Features: + - CLI Overrides: features/cli-overrides.md + - DAG References: features/dag-references.md + - File Overrides: features/file-overrides.md + - Partial Configs: features/partial-configs.md + - Callable References: features/callable-references.md + - API Reference: + - Core: api-reference/core.md + - IO: api-reference/io.md + - CLI: api-reference/cli.md + - Examples: examples.md + - Contributing: contributing.md diff --git a/pyproject.toml b/pyproject.toml index 0a32cb7..47ab6ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,11 @@ dev = [ "ipykernel>=7.1.0", "matplotlib>=3.10.8", ] +docs = [ + "mkdocs>=1.6.1", + "mkdocs-material>=9.7.1", + "mkdocstrings[python]>=1.0.2", +] [tool.uv] environments = ["sys_platform == 'linux'", "sys_platform == 'darwin'"] diff --git a/uv.lock b/uv.lock index f032370..9031b59 100644 --- a/uv.lock +++ b/uv.lock @@ -150,6 +150,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "backrefs" +version = "6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/e3/bb3a439d5cb255c4774724810ad8073830fac9c9dee123555820c1bcc806/backrefs-6.1.tar.gz", hash = "sha256:3bba1749aafe1db9b915f00e0dd166cba613b6f788ffd63060ac3485dc9be231", size = 7011962, upload-time = "2025-11-15T14:52:08.323Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ee/c216d52f58ea75b5e1841022bbae24438b19834a29b163cb32aa3a2a7c6e/backrefs-6.1-py310-none-any.whl", hash = "sha256:2a2ccb96302337ce61ee4717ceacfbf26ba4efb1d55af86564b8bbaeda39cac1", size = 381059, upload-time = "2025-11-15T14:51:59.758Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9a/8da246d988ded941da96c7ed945d63e94a445637eaad985a0ed88787cb89/backrefs-6.1-py311-none-any.whl", hash = "sha256:e82bba3875ee4430f4de4b6db19429a27275d95a5f3773c57e9e18abc23fd2b7", size = 392854, upload-time = "2025-11-15T14:52:01.194Z" }, + { url = "https://files.pythonhosted.org/packages/37/c9/fd117a6f9300c62bbc33bc337fd2b3c6bfe28b6e9701de336b52d7a797ad/backrefs-6.1-py312-none-any.whl", hash = "sha256:c64698c8d2269343d88947c0735cb4b78745bd3ba590e10313fbf3f78c34da5a", size = 398770, upload-time = "2025-11-15T14:52:02.584Z" }, + { url = "https://files.pythonhosted.org/packages/02/e3/a4fa1946722c4c7b063cc25043a12d9ce9b4323777f89643be74cef2993c/backrefs-6.1-py39-none-any.whl", hash = "sha256:a9e99b8a4867852cad177a6430e31b0f6e495d65f8c6c134b68c14c3c95bf4b0", size = 381058, upload-time = "2025-11-15T14:52:06.698Z" }, +] + [[package]] name = "bcrypt" version = "5.0.0" @@ -269,6 +290,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/96/43/dae06432d0c4b1dc9e9149ad37b4ca8384cf6eb7700cd9215b177b914f0a/cloudpickle-3.0.0-py3-none-any.whl", hash = "sha256:246ee7d0c295602a036e86369c77fecda4ab17b506496730f2f576d9016fd9c7", size = 20088, upload-time = "2023-10-16T12:51:24.415Z" }, ] +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + [[package]] name = "colorful" version = "0.5.8" @@ -501,6 +531,11 @@ dev = [ { name = "pytest", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "ruff", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, ] +docs = [ + { name = "mkdocs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "mkdocs-material", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "mkdocstrings", extra = ["python"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] [package.metadata] requires-dist = [ @@ -518,6 +553,11 @@ dev = [ { name = "pytest", specifier = ">=9.0.2" }, { name = "ruff", specifier = ">=0.14.11" }, ] +docs = [ + { name = "mkdocs", specifier = ">=1.6.1" }, + { name = "mkdocs-material", specifier = ">=9.7.1" }, + { name = "mkdocstrings", extras = ["python"], specifier = ">=1.0.2" }, +] [[package]] name = "filelock" @@ -574,6 +614,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/01/c9/97cc5aae1648dcb851958a3ddf73ccd7dbe5650d95203ecb4d7720b4cdbf/fsspec-2026.1.0-py3-none-any.whl", hash = "sha256:cb76aa913c2285a3b49bdd5fc55b1d7c708d7208126b60f2eb8194fe1b4cbdcc", size = 201838, upload-time = "2026-01-09T15:21:34.041Z" }, ] +[[package]] +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, +] + [[package]] name = "google-api-core" version = "2.29.0" @@ -625,6 +677,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42", size = 47300, upload-time = "2025-06-15T09:35:04.433Z" }, ] +[[package]] +name = "griffe" +version = "1.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/0c/3a471b6e31951dce2360477420d0a8d1e00dea6cf33b70f3e8c3ab6e28e1/griffe-1.15.0.tar.gz", hash = "sha256:7726e3afd6f298fbc3696e67958803e7ac843c1cfe59734b6251a40cdbfb5eea", size = 424112, upload-time = "2025-11-10T15:03:15.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl", hash = "sha256:6f6762661949411031f5fcda9593f586e6ce8340f0ba88921a0f2ef7a81eb9a3", size = 150705, upload-time = "2025-11-10T15:03:13.549Z" }, +] + [[package]] name = "grpcio" version = "1.76.0" @@ -1034,6 +1098,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, ] +[[package]] +name = "markdown" +version = "3.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/b1/af95bcae8549f1f3fd70faacb29075826a0d689a27f232e8cee315efa053/markdown-3.10.1.tar.gz", hash = "sha256:1c19c10bd5c14ac948c53d0d762a04e2fa35a6d58a6b7b1e6bfcbe6fefc0001a", size = 365402, upload-time = "2026-01-21T18:09:28.206Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/1b/6ef961f543593969d25b2afe57a3564200280528caa9bd1082eecdd7b3bc/markdown-3.10.1-py3-none-any.whl", hash = "sha256:867d788939fe33e4b736426f5b9f651ad0c0ae0ecf89df0ca5d1176c70812fe3", size = 107684, upload-time = "2026-01-21T18:09:27.203Z" }, +] + [[package]] name = "markdown-it-py" version = "4.0.0" @@ -1107,6 +1180,133 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "ghp-import", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "jinja2", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "markdown", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "markupsafe", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "mergedeep", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "mkdocs-get-deps", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pathspec", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml-env-tag", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "watchdog", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, +] + +[[package]] +name = "mkdocs-autorefs" +version = "1.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "markupsafe", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "mkdocs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/fa/9124cd63d822e2bcbea1450ae68cdc3faf3655c69b455f3a7ed36ce6c628/mkdocs_autorefs-1.4.3.tar.gz", hash = "sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75", size = 55425, upload-time = "2025-08-26T14:23:17.223Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/4d/7123b6fa2278000688ebd338e2a06d16870aaf9eceae6ba047ea05f92df1/mkdocs_autorefs-1.4.3-py3-none-any.whl", hash = "sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9", size = 25034, upload-time = "2025-08-26T14:23:15.906Z" }, +] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mergedeep", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "platformdirs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, +] + +[[package]] +name = "mkdocs-material" +version = "9.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "backrefs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "colorama", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "jinja2", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "markdown", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "mkdocs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "mkdocs-material-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "paginate", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pygments", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pymdown-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "requests", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/e2/2ffc356cd72f1473d07c7719d82a8f2cbd261666828614ecb95b12169f41/mkdocs_material-9.7.1.tar.gz", hash = "sha256:89601b8f2c3e6c6ee0a918cc3566cb201d40bf37c3cd3c2067e26fadb8cce2b8", size = 4094392, upload-time = "2025-12-18T09:49:00.308Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl", hash = "sha256:3f6100937d7d731f87f1e3e3b021c97f7239666b9ba1151ab476cabb96c60d5c", size = 9297166, upload-time = "2025-12-18T09:48:56.664Z" }, +] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, +] + +[[package]] +name = "mkdocstrings" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "markdown", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "markupsafe", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "mkdocs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "mkdocs-autorefs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pymdown-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/4d/1ca8a9432579184599714aaeb36591414cc3d3bfd9d494f6db540c995ae4/mkdocstrings-1.0.2.tar.gz", hash = "sha256:48edd0ccbcb9e30a3121684e165261a9d6af4d63385fc4f39a54a49ac3b32ea8", size = 101048, upload-time = "2026-01-24T15:57:25.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/32/407a9a5fdd7d8ecb4af8d830b9bcdf47ea68f916869b3f44bac31f081250/mkdocstrings-1.0.2-py3-none-any.whl", hash = "sha256:41897815a8026c3634fe5d51472c3a569f92ded0ad8c7a640550873eea3b6817", size = 35443, upload-time = "2026-01-24T15:57:23.933Z" }, +] + +[package.optional-dependencies] +python = [ + { name = "mkdocstrings-python", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] + +[[package]] +name = "mkdocstrings-python" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "griffe", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "mkdocs-autorefs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "mkdocstrings", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/75/d30af27a2906f00eb90143470272376d728521997800f5dce5b340ba35bc/mkdocstrings_python-2.0.1.tar.gz", hash = "sha256:843a562221e6a471fefdd4b45cc6c22d2607ccbad632879234fa9692e9cf7732", size = 199345, upload-time = "2025-12-03T14:26:11.755Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/06/c5f8deba7d2cbdfa7967a716ae801aa9ca5f734b8f54fd473ef77a088dbe/mkdocstrings_python-2.0.1-py3-none-any.whl", hash = "sha256:66ecff45c5f8b71bf174e11d49afc845c2dfc7fc0ab17a86b6b337e0f24d8d90", size = 105055, upload-time = "2025-12-03T14:26:10.184Z" }, +] + [[package]] name = "msgpack" version = "1.1.2" @@ -1329,6 +1529,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] +[[package]] +name = "paginate" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, +] + [[package]] name = "paramiko" version = "4.0.0" @@ -1353,6 +1562,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, ] +[[package]] +name = "pathspec" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, +] + [[package]] name = "pexpect" version = "4.9.0" @@ -1630,6 +1848,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] +[[package]] +name = "pymdown-extensions" +version = "10.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1e/6c/9e370934bfa30e889d12e61d0dae009991294f40055c238980066a7fbd83/pymdown_extensions-10.20.1.tar.gz", hash = "sha256:e7e39c865727338d434b55f1dd8da51febcffcaebd6e1a0b9c836243f660740a", size = 852860, upload-time = "2026-01-24T05:56:56.758Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/6d/b6ee155462a0156b94312bdd82d2b92ea56e909740045a87ccb98bf52405/pymdown_extensions-10.20.1-py3-none-any.whl", hash = "sha256:24af7feacbca56504b313b7b418c4f5e1317bb5fea60f03d57be7fcc40912aa0", size = 268768, upload-time = "2026-01-24T05:56:54.537Z" }, +] + [[package]] name = "pynacl" version = "1.6.2" @@ -1736,6 +1967,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, ] +[[package]] +name = "pyyaml-env-tag" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, +] + [[package]] name = "pyzmq" version = "27.1.0" @@ -2125,6 +2368,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6a/2a/dc2228b2888f51192c7dc766106cd475f1b768c10caaf9727659726f7391/virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f", size = 6008258, upload-time = "2026-01-09T18:20:59.425Z" }, ] +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, +] + [[package]] name = "wcwidth" version = "0.5.0"