diff --git a/.github/workflows/publish_docs.yml b/.github/workflows/publish_docs.yml deleted file mode 100644 index 008726d7..00000000 --- a/.github/workflows/publish_docs.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Publish Docs - -on: - push: - branches: - - main - tags: - - "**" - pull_request: {} - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: Install Poetry - run: | - curl -sSL https://install.python-poetry.org | python3 - - echo "$HOME/.local/bin" >> $GITHUB_PATH - - - name: Install dependencies - run: make install-deps-docs-website - - - name: Build - run: | - cd docs_website - poetry run mkdocs build - - publish: - needs: [test] - if: "success() && (startsWith(github.ref, 'refs/tags/') || contains(github.event.pull_request.labels.*.name, 'Publish Docs'))" - - permissions: - contents: write - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: Install Poetry - run: | - curl -sSL https://install.python-poetry.org | python3 - - echo "$HOME/.local/bin" >> $GITHUB_PATH - - - name: Install dependencies - run: make install-deps-docs-website - - - name: Build - run: | - cd docs_website - poetry run mkdocs gh-deploy --force diff --git a/Makefile b/Makefile index 501bad57..06b63909 100644 --- a/Makefile +++ b/Makefile @@ -47,10 +47,6 @@ install-deps-create-mountaineer-app: @echo "Installing dependencies for $(CREATE_MOUNTAINEER_APP_DIR)..." @(cd $(CREATE_MOUNTAINEER_APP_DIR) && poetry install) -install-deps-docs-website: - @echo "Installing dependencies for $(DOCS_WEBSITE_DIR)..." - @(cd $(DOCS_WEBSITE_DIR) && poetry install --no-root) - install-deps-scripts: @echo "Installing dependencies for $(SCRIPTS_DIR)..." @(cd $(SCRIPTS_DIR) && poetry install) diff --git a/create_mountaineer_app/pyproject.toml b/create_mountaineer_app/pyproject.toml index 0ea17975..4bb7f735 100644 --- a/create_mountaineer_app/pyproject.toml +++ b/create_mountaineer_app/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "create-mountaineer-app" version = "0.1.0" -description = "" +description = "Easily get started with a new Mountaineer project." authors = ["Pierce Freeman "] readme = "README.md" diff --git a/docs_website/.zed/settings.json b/docs_website/.zed/settings.json deleted file mode 100644 index 2f5c674c..00000000 --- a/docs_website/.zed/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "format_on_save": "off" -} diff --git a/docs_website/README.md b/docs_website/README.md deleted file mode 100644 index 7f05f0c0..00000000 --- a/docs_website/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Mountaineer Website - -## Getting Started - -```bash -poetry install -``` - -```bash -poetry run mkdocs serve -``` diff --git a/docs_website/docs/CNAME b/docs_website/docs/CNAME deleted file mode 100644 index 073ccec0..00000000 --- a/docs_website/docs/CNAME +++ /dev/null @@ -1 +0,0 @@ -mountaineer.sh diff --git a/docs_website/docs/api/actions.md b/docs_website/docs/api/actions.md deleted file mode 100644 index 77539791..00000000 --- a/docs_website/docs/api/actions.md +++ /dev/null @@ -1,15 +0,0 @@ -# Actions - -::: mountaineer.actions.passthrough_dec.passthrough - options: - members: - - register - show_source: false - show_root_heading: true - -::: mountaineer.actions.sideeffect_dec.sideeffect - options: - members: - - register - show_source: false - show_root_heading: true diff --git a/docs_website/docs/api/api_exception.md b/docs_website/docs/api/api_exception.md deleted file mode 100644 index ce3c87c5..00000000 --- a/docs_website/docs/api/api_exception.md +++ /dev/null @@ -1,9 +0,0 @@ -# API Exception - -The `APIException` is the root exception that you should inherit for errors that are thrown within action functions. This syntax allows your frontend to pick up on the error parameters. APIExceptions are just Pydantic BaseModels with a few helpful defaults, and a metaclass that allows them to work natively with HTTPExceptions. - -::: mountaineer.exceptions.APIException - options: - members: - - status_code - - detail diff --git a/docs_website/docs/api/app-controller.md b/docs_website/docs/api/app-controller.md deleted file mode 100644 index cb44a60f..00000000 --- a/docs_website/docs/api/app-controller.md +++ /dev/null @@ -1,6 +0,0 @@ -# App Controller - -::: mountaineer.app.AppController - options: - members: - - register diff --git a/docs_website/docs/api/build_plugins/base.md b/docs_website/docs/api/build_plugins/base.md deleted file mode 100644 index 5c3028de..00000000 --- a/docs_website/docs/api/build_plugins/base.md +++ /dev/null @@ -1,5 +0,0 @@ -# Build Plugin Base - -Use the `ClientBuilderBase` to implement a new stage or custom handling during the frontend build lifecycle. Note that this API is still under development and might change between minor Mountaineer versions. - -::: mountaineer.client_compiler.base.ClientBuilderBase diff --git a/docs_website/docs/api/build_plugins/postcss.md b/docs_website/docs/api/build_plugins/postcss.md deleted file mode 100644 index dbc54c2e..00000000 --- a/docs_website/docs/api/build_plugins/postcss.md +++ /dev/null @@ -1,3 +0,0 @@ -# PostCSS - -::: mountaineer.client_compiler.postcss.PostCSSBundler diff --git a/docs_website/docs/api/cli.md b/docs_website/docs/api/cli.md deleted file mode 100644 index 5ea92e0c..00000000 --- a/docs_website/docs/api/cli.md +++ /dev/null @@ -1,20 +0,0 @@ -# CLI Plugins - -CLI Plugins provide default handling for the most common lifecycle events during Mountaineer development. Import these if convenient, otherwise you can follow our implementation approaches to implement your own. - -## Webapp CLI - -::: mountaineer.cli.handle_runserver - -::: mountaineer.cli.handle_watch - -::: mountaineer.cli.handle_build - -Building your app will compile your TypeScript into the client-side bundle that will be downloaded -by the browser. It also ahead-of-time generates the server code that will be run as part of [SSR](./ssr.md). -You'll want to do it before deploying your application into production - but since a full build can take up -to 10s, `handle_runserver` provides a better workflow for daily development. - -## Database CLI - -::: mountaineer.database.cli.handle_createdb diff --git a/docs_website/docs/api/config.md b/docs_website/docs/api/config.md deleted file mode 100644 index 8f9382a8..00000000 --- a/docs_website/docs/api/config.md +++ /dev/null @@ -1,16 +0,0 @@ - -## Application Config - -::: mountaineer.config.ConfigBase - -::: mountaineer.config.get_config - -## Internal Functions - -Used in unit testing or some other scenarios where you need specific control over the configuration that is mounted into the global registry. - -::: mountaineer.config.register_config - -::: mountaineer.config.unregister_config - -::: mountaineer.config.get_config diff --git a/docs_website/docs/api/controller.md b/docs_website/docs/api/controller.md deleted file mode 100644 index 1347c357..00000000 --- a/docs_website/docs/api/controller.md +++ /dev/null @@ -1,7 +0,0 @@ -# View Controller - -::: mountaineer.controller.ControllerBase - -# Layout Controller - -::: mountaineer.controller_layout.LayoutControllerBase diff --git a/docs_website/docs/api/core_dependencies.md b/docs_website/docs/api/core_dependencies.md deleted file mode 100644 index c3281180..00000000 --- a/docs_website/docs/api/core_dependencies.md +++ /dev/null @@ -1,3 +0,0 @@ -# Core Dependencies - -::: mountaineer.dependencies.CoreDependencies diff --git a/docs_website/docs/api/database/config.md b/docs_website/docs/api/database/config.md deleted file mode 100644 index e8af442e..00000000 --- a/docs_website/docs/api/database/config.md +++ /dev/null @@ -1,3 +0,0 @@ -# Database Config - -::: mountaineer.database.config.DatabaseConfig diff --git a/docs_website/docs/api/database/dependencies.md b/docs_website/docs/api/database/dependencies.md deleted file mode 100644 index f4b67937..00000000 --- a/docs_website/docs/api/database/dependencies.md +++ /dev/null @@ -1,3 +0,0 @@ -# Database Dependencies - -::: mountaineer.database.DatabaseDependencies diff --git a/docs_website/docs/api/database/migrations.md b/docs_website/docs/api/database/migrations.md deleted file mode 100644 index 932cda9a..00000000 --- a/docs_website/docs/api/database/migrations.md +++ /dev/null @@ -1,20 +0,0 @@ -# Database Migrations - -## CLI - -::: mountaineer.migrations.cli.handle_generate -::: mountaineer.migrations.cli.handle_apply -::: mountaineer.migrations.cli.handle_rollback - -## Migrations - -::: mountaineer.migrations.migration.MigrationRevisionBase -::: mountaineer.migrations.migrator.Migrator -::: mountaineer.migrations.migrator.NoCommitAsyncSession - -## Actions - -::: mountaineer.migrations.actions.DatabaseActions - -::: mountaineer.migrations.actions.ColumnType -::: mountaineer.migrations.actions.ConstraintType diff --git a/docs_website/docs/api/logging.md b/docs_website/docs/api/logging.md deleted file mode 100644 index b514bcee..00000000 --- a/docs_website/docs/api/logging.md +++ /dev/null @@ -1,3 +0,0 @@ -::: mountaineer.logging.setup_logger - -::: mountaineer.logging.log_time_duration diff --git a/docs_website/docs/api/render.md b/docs_website/docs/api/render.md deleted file mode 100644 index b35b9a26..00000000 --- a/docs_website/docs/api/render.md +++ /dev/null @@ -1,21 +0,0 @@ -::: mountaineer.render.RenderBase - -::: mountaineer.render.Metadata - -## Headers: Scripts - -::: mountaineer.render.ScriptAttribute - -## Headers: Links - -::: mountaineer.render.LinkAttribute - -## Headers: Metas - -We provide support for all `` arguments via the `MetaAttribute` class. We also provide some convenient shortcuts more more complicated, common meta tags. - -::: mountaineer.render.MetaAttribute - -::: mountaineer.render.ViewportMeta - -::: mountaineer.render.ThemeColorMeta diff --git a/docs_website/docs/api/ssr.md b/docs_website/docs/api/ssr.md deleted file mode 100644 index 16829fbf..00000000 --- a/docs_website/docs/api/ssr.md +++ /dev/null @@ -1,13 +0,0 @@ -# SSR - -Most SSR logic is handled in our embedded Rust layer. In Rust we spin up a V8 isolate, handle logging, catch and process exceptions, etc. This page only covers the client functions that are exposed to Python. - -## SSR - -::: mountaineer.ssr.render_ssr - -::: mountaineer.ssr.V8RuntimeError - -## Source Maps - -::: mountaineer.client_compiler.source_maps.SourceMapParser diff --git a/docs_website/docs/api/watch_server.md b/docs_website/docs/api/watch_server.md deleted file mode 100644 index 303b303a..00000000 --- a/docs_website/docs/api/watch_server.md +++ /dev/null @@ -1,9 +0,0 @@ -## Watch Server - -Used by Mountaineer CLI hooks to isolate your server and push updates to live-reload browsers. You probably won't have to deal with this class explicitly. - -::: mountaineer.app_manager.HotReloadManager - -::: mountaineer.watch.PackageWatchdog - -::: mountaineer.watch_server.WatcherWebservice diff --git a/docs_website/docs/client_actions.md b/docs_website/docs/client_actions.md deleted file mode 100644 index 9d609b06..00000000 --- a/docs_website/docs/client_actions.md +++ /dev/null @@ -1,229 +0,0 @@ -# Client Actions - -Conventionally, server mutations that affect a page state are a bit of a hassle. You need to keep track of modified attributes, issue an API call, then reload or merge them into the current page. All of this to mostly just update the state that the original page was initialized with in the first place. - -Actions are a way to simplify this process. They allow you to define server-side mutations that can be called from the client as if they're regular Javascript functions. Their request and response payloads are typehinted so you can statically verify their behavior. - -## Action Types - -Anytime clients need to modify the server state, we denote these requests as "actions". Internally, they're just POST requests that are sent to the server. There's no black magic to this syntax. You can inspect them all in your browser's console as regular JSON. - -Your choice of action type depends on how you want to update _client_ data after your _server_ action has been executed. - -**@sideeffect:** Update the server data that initialized the client view. Sideeffects are also passthroughs, by definition, so they can pass back arbitrary data that is outside of the scope of the render() data payload. Use a sideeffect whenever the client modifies some data that lives on the server. - -**@passthrough:** Expose an action to the client caller but don't update the server data once the action has been completed. Use a passthrough whenever you need to fetch additional data from the server, or the mutation on the server doesn't affect the local frontend state. - -## Side Effect - -Side-effects work as if you have reloaded the page. They update the server state and then push the updated state back to the client. They will call a fully fresh `render()` on the server so you can provide the latest data state to the client. However, unlike a true refresh on the client side, there's no actual browser refresh that is performed. This allows you to receive the updated data definition and keep the rest of your variables saved in local state. - -`@sideeffect` can either be called with default behavior or customized via decorator arguments: - -```python -# no parameters, refresh all state -@sideeffect -def increment_count(self) -> None: - self.global_count += 1 -``` - -```python -@sideeffect( - # only push the updated current_count variable to the client - reload=(CountRender.current_count,) -) -def increment_count(self) -> None: - self.global_count += 1 -``` - -```python -# execute a sideeffect and then return additional data from a custom data model -@sideeffect -def increment_count(self) -> CustomDataModel: - self.global_count += 1 - ... - return CustomDataModel(...) -``` - -Keyword arguments can be chained as you expect to customize the sideeffect behavior. - -You call the sideeffect from the client through the serverState variable: - -```typescript -const response = await serverState.increment_count({ - requestBody: { - count: 5, - }, -}); -``` - -You can also manually inspect the sideeffect payload by accessing `response.sideeffect`. - -If the action has a passthrough, it will be supplied in `response.passthrough`. Otherwise it will be undefined. - -### Experimental Render Reloader - -!!! warning - - This feature is experimental and only supports relatively simple render() function implementations. If you use it for a more complicated render() function and it doesn't work as expected, report a bug to improve the test coverage. - -Render functions sometimes have heavy logic overhead: they need to fetch multiple objects from the database, do some roll-up computation, etc. If you're issuing a sideeffect that only affects a small portion of the initial data, this is wasted computation. - -Passing a `reload` filter to `sideeffect` prevents this redundant data from being sent to the frontend. But this feature only saves bandwidth; it doesn't actually prevent the server from doing the computation in the first place. This is where the `experimental_render_reload` comes in. - -When this flag is set to `True`, Mountaineer will inspect the AST ([Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree)) of your render function. It creates a new synthetic render function that only does the computation required to calculate the `reload` parameters. If some intensive compute isn't required for your sideeffect, it will be ignored. - -We compile this function into the Python runtime so it runs with the same performance as if you had implemented an alternative `render()` function yourself. Depending on your full render function complexity, this can lead to significant performance improvements. - -Let's consider the following render function: - -```python -def render( - self, - query_id: int, -) -> ExampleRenderModel: - a = calculate_primes(10000) - b = calculate_primes(1000000) - return ExampleRenderModel( - value_a=f"Hello {a}", - value_b=f"World {b}", - ) - -@sideeffect( - reload=(ExampleRenderModel.value_a,), - experimental_render_reload=use_experimental, -) -def call_sideeffect(self, payload: dict) -> None: - pass -``` - -Benchmarked on a Macbook M1, calling this initial render will perform ~1.84s of compute. It results in: - -```json -{ - "value_a": "Hello 1229", - "value_b": "Hello 78498" -} -``` - -When `call_sideeffect` is called with `experimental_render_reload=True`, the compute only takes `0.010s`. It results in: - -```json -{ - "value_a": "Hello 1229" -} -``` - -## Passthrough - -Passthrough is conceptually much simpler, since it doesn't perform any server->client data syncs once it's finished. Instead, it provides a simple decorator that optionally accepts a ResponseModel: - -```python -# no parameters, no response model -@passthrough -def server_action(self) -> None: - pass -``` - -```python -# execute a passthrough and then return additional data from a custom data model -@passthrough -def server_action(self) -> CustomDataModel: - pass -``` - -Like passthrough values in sideeffects, you can access it on the client side like: - -```typescript -const response = await serverState.server_action({}); -console.log(response.passthrough); -``` - -### Server Events - -[Server-event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) streams are a useful paradigm in webapp design, and increasing utilized to provide realtime Server->Client data for use in user notifications, CI feedback, and model inference. - -Mountaineer supports these natively on the server and client side. Simply decorate your response_model with a `typing.AsyncIterator` annotation and implement an async generator like you normally do: - -```python -from typing import AsyncIterator -from mountaineer import passthrough, ControllerBase -from pydantic import BaseModel - -class MyMetadata(BaseModel): - state: int - -class MyController(ControllerBase): - ... - - @passthrough - async def stream_metadata(self) -> AsyncIterator[MyMetadata]: - yield MyMetadata(state=1) - await asyncio.sleep(10) - yield MyMetadata(state=2) -``` - -For the time being we only support server-events in `@passthrough` functions, not `@sideeffect`. It's ill-defined whether we should re-render() content every yield or when the iterator has finished. Yielding in passthrough makes it clear that you just want to stream the yielded value to the client. - -In your frontend, you can iterate over these responses with an async generator loop. Each response object will be parsed into your typed schema for you, so you can see typehints like you would for any regular Mountaineer action. - -```typescript -import React, { useState, useEffect } from "react"; - -const Page = () => { - const [currentState, setCurrentState] = useState(-1); - - useEffect(() => { - const runStream = async () => { - const responseStream = await serverState.stream_metadata({}); - for await (const response of responseStream) { - setCurrentState(response.passthrough.state); - } - }; - runStream(); - }, []); - - return
{currentState}
; -} -``` - -You'll see the first event state `1` for 10 seconds, then it will update to `2`. - -## Action Definitions - -When defining your action functions themselves in your controller, we support typehinting via: - -- Query parameters -- Pydantic models for JSON payloads -- Dependency injection via `fastapi.Depends` - -We also support both sync and async functions. Sync functions will be spawned into a thread pool by default - which processes them in parallel but can tax system resources with GIL locking. Where possible, use async functions with libraries that support await constructs. - -A common sideeffect pattern might look like this: - -```python -from mountaineer import ControllerBase, Depends -from my_website.models import User -from my_website.deps import get_current_user - -class IncrementCountRequest: - count: int - -class MyController(ControllerBase): - def __init__(self): - super().__init__() - self.global_count = 0 - - @sideeffect - async def increment_count( - self, - payload: IncrementCountRequest, - query_param: int, - user: User = Depends(get_current_user) - ) -> None: - self.global_count += payload.count -``` - -!!! warning - - Actions are publicly exposed to the Internet by default. It's up to you to secure them with authentication if they should only be accessible by a certain portion of your userbase. diff --git a/docs_website/docs/cma.md b/docs_website/docs/cma.md deleted file mode 100644 index 7ed5ca4d..00000000 --- a/docs_website/docs/cma.md +++ /dev/null @@ -1,18 +0,0 @@ -# Create Mountaineer App - -Create Mountaineer App 🏔️ (CMA for short) is a utility that makes it easily to get started on a new Mountaineer project. It provides all the scaffolding files and best practices that you'll need to hit the ground running. - -Make sure you have pipx [installed](https://pipx.pypa.io/stable/installation/). - -```bash -$ pipx run create-mountaineer-app - -? Project name [my-project]: my_webapp -? Author [Pierce Freeman ] Default -? Use poetry for dependency management? [Yes] Yes -? Create stub MVC files? [Yes] Yes -? Use Tailwind CSS? [Yes] Yes -? Add editor configuration? [vscode] vscode -``` - -For more details on what files were just created and how to use them, continue to the [Structure](structure.md) page. diff --git a/docs_website/docs/database.md b/docs_website/docs/database.md deleted file mode 100644 index 891ae8da..00000000 --- a/docs_website/docs/database.md +++ /dev/null @@ -1,52 +0,0 @@ -# Database - -Mountaineer bundles common conventions for configuring a Postgres database with async connection handlers. This lets it plug and play easily with the async code that you're already writing for your controllers. - -## Config - -A configuration class is defined in `mountaineer.database` that you can use to configure your database connection. Make sure to register your downstream configuration with the `DatabaseConfig` if you want to use it. For the full list of configuration options and defaults, see the `DatabaseConfig` superclass. - -```python -from mountaineer.database import DatabaseConfig -from mountaineer import ConfigBase - -class AppConfig(DatabaseConfig, ConfigBase): - POSTGRES_HOST: str - POSTGRES_USER: str - POSTGRES_PASSWORD: str - POSTGRES_DB: str - POSTGRES_PORT: int = 5432 -``` - -Since database hosts, usernames, and passwords change for development vs. production you'll always want to store these within an `.env` file in your local directory. During development these will just be injected dynamically via whatever configuration/secrets service you adopt. More on that later. - -## Calling the database - -Within your render and action functions, you'll have access to the `DatabaseDependencies`. The main entrypoint here will be the `get_db_session` dependency, which will give you a new async session to work with. It'll already be opened to a new transaction when your function is called. - -Transactions are an internal concept of Postgres and most other SQL databases. They let you perform logic in an encapsolated chunk without actually writing their data to the database. This has the benefit that if you have an error partially through your logic, you can just rollback the transaction and the database will be left in the same state it was before you started. You can then try again later without the need to clean up any partial writes. - -`SELECT` queries don't modify the database state, so these can be executed as-is within action functions. - -For `INSERT`, `UPDATE`, and `DELETE` requests however - these modify the state of the database. So you'll want perform whatever logic you need and commit your changes at the end. - -```python -from mountaineer.database import DatabaseDependencies - -class HomeController(ControllerBase): - ... - - @sideeffect - async def add_todo( - self, - payload: NewTodoRequest, - session: AsyncSession = Depends(DatabaseDependencies.get_db_session) - ) -> None: - new_todo = TodoItem(description=payload.description) - session.add(new_todo) - await session.commit() -``` - -## Pooling - -Most webapps are deployed in environments where they have multiple processes running, either on the same machine or separate servers. SQLAlchemy's default pool handling isn't well supported in these situations since it relies on a process-delineated connection pool. By default Mountaineer assumes that you'll deploy your database with a pooler closer to the database itself - something like PgBouncer, Odyssey, or HAProxy. diff --git a/docs_website/docs/database_migrations.md b/docs_website/docs/database_migrations.md deleted file mode 100644 index 0e763c59..00000000 --- a/docs_website/docs/database_migrations.md +++ /dev/null @@ -1,165 +0,0 @@ -# Database Migrations - -!!! warning - - This feature is experimental. Explore using it while developing locally but make sure to backup your data before applying changes. It should support all SQLModel definitions, but if you encounter a bug or lack of support, please report it so we can improve the test coverage. - -## Overview - -Once your application is in production, you'll need some method of updating your database schema as you update your application's functionality. You _could_ write raw SQL to accomplish these migrations, or manually modify database table definitions. But the former is inconvenient and the second is risky. Mountaineer ships with a migration tool that can automatically generate migration files for you and apply them to your database. Its features: - -- Fast with no external dependencies outside of Mountaineer core. -- Zero config required, optional programmatic customization. -- Unit-testable migration paths that work with normal `pytest` harnesses. -- Baked-in support for common Postgres types that overlap with Python, most specifically Enums and datetimes. -- File-based groundtruth of migration logic, so it can be audited in source control and customized by you. -- Simple API surface, with atomic Python functions that perform the most common migration operations. Direct database queries (or integration with ORM objects in limited cases) can be used for more complex migration logic. - -## Project Integration - -Following the current standard for Mountaineer CLI integrations, we require client applications to explicitly define their CLI endpoints. We include basic handlers for import in `mountaineer.migrations.cli`. This should look very similar to the default handlers for `runserver` and `build`. - -You can integrate like so in your CLI file: - -```python title="myapp/cli.py" -from click import group, option - -from mountaineer.io import async_to_sync -from mountaineer.migrations.cli import handle_apply, handle_generate, handle_rollback -from myapp.config import AppConfig - -@group -def migrate(): - pass - -@migrate.command() -@option("--message", required=False) -@async_to_sync -async def generate(message: str | None): - _ = AppConfig() # type: ignore - await handle_generate(message=message) - -@migrate.command() -@async_to_sync -async def apply(): - _ = AppConfig() # type: ignore - await handle_apply() - -@migrate.command() -@async_to_sync -async def rollback(): - _ = AppConfig() # type: ignore - await handle_rollback() -``` - -Also modify your project's pyproject.toml file. - -```toml title="pyproject.toml" -[tool.poetry.scripts] -migrate = "myapp.cli:migrate" -``` - -### Generate - -```bash -$ poetry run migrate generate --message "Add author column to article" -``` - -Generate a migration file, to update the database schema to the ones defined in your code. - -### Apply - -```bash -$ poetry run migrate apply -``` - -Apply all migration files that have not been applied to the database. - -### Rollback - -```bash -$ poetry run migrate rollback -``` - -Rollback the last migration that was applied to the database. - -## Migration files - -All data changes live in separate migration files. You can generate them through the Mountaineer CLI and modify them as you need to handle your data migrations. - -The goal of a migration file is to determine the goal database state (ie. what you current have in code). It then figures out how to transition the current database state to the new goal state. As such, before generating your migration file, make sure your local database has the same schema configuration as your production database. Otherwise your migration files might be incorrect and not apply properly. - -```bash -poetry run migrate generate --message "Add author column to article" -``` - -The created migration will be placed into `myapp/migrations` and include a unix timestamp of when the migration was created. Since most IDEs will sort directories by integer value, you can look towards the bottom of your migrations directory to see the most recent migration that will be run. - -```python -from mountaineer.migrations.migrator import Migrator -from mountaineer.migrations.migration import MigrationRevisionBase -from mountaineer.migrations.dependency import MigrationDependencies -from fastapi.param_functions import Depends - -class MigrationRevision(MigrationRevisionBase): - up_revision: str = "1715044020" - down_revision: str | None = None - - async def up( - self, - migrator: Migrator = Depends(MigrationDependencies.get_migrator), - ): - await migrator.actor.add_not_null( - table_name="article", - column_name="author" - ) - - async def down( - self, - migrator: Migrator = Depends(MigrationDependencies.get_migrator), - ): - await migrator.actor.drop_not_null( - table_name="article", - column_name="author" - ) -``` - -Let's break down the migration file that was just generated: - -There's an `up` function that covers the migration to the new application state. These are standard dependency injection functions, so you can use any dependency injector in your application if you want to inject other variables. By default we just supply the migrator: Migrator which is a shallow wrapper that provides a database session (with an open connection) alongside an actor object that includes some common migration recipes. - -The `down` function does the inverse. It takes the database state after your migration has been run and downgrades it to the last version. It's useful to have this specified in case you need to rollback your migration to conform to the previously deployed service. This often requires some care at considering how you can safely rollback your migration, perhaps through keeping temporary columns around inbetween migrations that you know you might have to rollback. - -The `up_revision` and `down_revision` are used to track the migration state. The `up_revision` is the timestamp of the migration file, and the `down_revision` is the timestamp of the previous migration file. If you don't have a down revision, it will be set to `None`. These will be injected into a dynamic "migration_info" table in your database to track the current state of your migrations. - -## Extending Migration Files - -The `Migrator` object is a thin wrapper around the `DatabaseActions` object, which is a collection of common migration operations. If you need to perform a more complex migration operation, you can customize the logic by calling `migrator.actor` yourself. Head over to the [DatabaseActions documentation](./api/database/migrations.md) to see the full list of available migration operations. - -In addition to the actor, you can also access the underlying database session object. This is useful if you need to run raw SQL queries that aren't covered by the actor object. - -```python -async def up( - self, - migrator: Migrator = Depends(MigrationDependencies.get_migrator), -): - result = await migrator.db_session.exec("SELECT * FROM article") -``` - -We recommend using the actor object whenever possible, as it provides a more consistent and safe way to run migrations. If you are using the raw database session object, be aware that we require migrations to be run in a single transaction. This ensures that if a migration fails, the database will be rolled back to its previous state. We therefore disable calling `db_session.commit()` explicitly from within user code. - -## Alternatives - -While the Mountaineer core authors only support its native migration workflow, since the database primitives build off of SQLModel/SQLAlchemy there are other options in the ecosystem for migration generation. - -The industry standard migration package for SQLAlchemy is Alembic, which is a powerful and robust file-based migration library. A quick list of pros and our perceived cons: - -Pros: - -- Mature project with a large user base and extensive documentation. -- It has a rich feature set, including support for multiple database backends and complex migration operations. - -Cons: - -- Non-trivial setup complexity with configuration files and a separate CLI. -- In steady state it's sometimes unclear what migration responsibility Alembic owns, versus what should be delegated to SQLAlchemy. diff --git a/docs_website/docs/deploy.md b/docs_website/docs/deploy.md deleted file mode 100644 index 403a8310..00000000 --- a/docs_website/docs/deploy.md +++ /dev/null @@ -1,155 +0,0 @@ -You can deploy your Mountaineer project using whatever container technology you'd like, on whatever hosting provider you'd like. Most hosts at this point mandate or highly encourage containerization of your dependencies with Docker - so we start there. - -This page contains a reasonable default configuration to get you started. We make heavy use of multi-stage builds to cache dependencies and minimize the size of your final image that your webservers will have to pull down. - -## Docker - -First, add the following to your `.dockerignore` file. This will prevent Docker from trying to copy over heavy artifacts that aren't needed for the build. - -```title=".dockerignore" -**/node_modules -**/_server -**/_ssr -**/_static -**/_metadata -``` - -### Image 1: Frontend Dependencies - -Our first stage uses `npm` to fetch your frontend dependencies. This is an isolated context since it's the only place we need node / npm in the build pipeline. - -```docker -FROM node:20-slim as node-dependencies - -WORKDIR /usr/src/app - -# We only require the dependency definitions -COPY {my_webapp}/views/package.json {my_webapp}/views/package-lock.json ./ -RUN npm install -``` - -### Image 2: Python Dependencies Base - -Our build pipeline requires Poetry and a basic Python configuration in multiple stages. This initial stage sets up the Poetry CLI and a Python 3.11 environment. By default we make use of Docker's `buildx` to compile for linux/amd64 (Intel) since this is what most servers run on. It also lets us leverage our prebuild Mountaineer wheels. - -```docker -FROM --platform=linux/amd64 python:3.11-slim as poetry-base - -WORKDIR /usr/src/app - -# You only need `nodejs` here if you are using the postcss plugin -RUN apt-get update \ - && apt-get install -y --no-install-recommends pipx nodejs - -ENV PATH="/root/.local/bin:${PATH}" - -RUN pipx install poetry -``` - -### Image 3: Python Dependencies - -Fetch Python dependencies and package them into a virtualenv. - -```docker -FROM poetry-base as venv-dependencies - -RUN pipx inject poetry poetry-plugin-bundle - -# Only copy package requirements to cache them in docker layer -# We don't copy poetry.lock since this is tied to the specific architecture -# of our dev machines -COPY pyproject.toml ./ - -# Poetry requires a README.md to be present in the project -COPY README.md ./ - -# Copy the application code -COPY {my_webapp} ./{my_webapp} - -# Gather dependencies and place into a new virtualenv -RUN poetry -vvv bundle venv --python=/usr/local/bin/python --only=main /venv -``` - -### Image 4: Build Frontend to Javascript - -Static frontend plugins, provided by Mountaineer. - -```docker -FROM poetry-base as server-hooks-builder - -COPY pyproject.toml ./ -COPY README.md ./ - -COPY {my_webapp} ./{my_webapp} -COPY --from=node-dependencies /usr/src/app/node_modules ./{my_webapp}/views/node_modules - -# Mount the application CLI handlers and build the artifacts -RUN poetry install -RUN poetry run build -``` - -### Image 5: Final Layer - -Combines the raw python files, python dependencies, and the built frontend. - -```docker -FROM --platform=linux/amd64 python:3.11-slim as final - -# Create and switch to a new user -RUN useradd --create-home appuser -USER appuser - -ENV PATH="/venv/bin:$PATH" - -WORKDIR /usr/src/app - -COPY --from=venv-dependencies /venv /venv -COPY --from=server-hooks-builder /usr/src/app/{my_webapp}/views /venv/lib/python3.11/site-packages/{my_webapp}/views - -# Run the application -CMD ["/venv/bin/uvicorn", "{my_webapp}.main:app", "--host", "0.0.0.0", "--port", "3000"] -``` - -## Local Testing - -Once your Docker image is built, the best way to test it is to run it locally with `docker run`. -However you can also simulate what the production service is doing by running: - -```bash -poetry run build -ENVIRONMENT=PRODUCTION poetry run uvicorn {my_webapp}.main:app --host localhost --port 5006 -``` - -This runs with production-minified assets and the same configuration as the production server. - -## Common Errors - -If you see "required file not found" when you try to run this docker image, double check that your venv is pointing to the correct version of Python within the container: - -```bash -$ ls -ls /venv/bin - -4 -rw-r--r-- 1 root root 2209 Mar 29 00:26 activate -4 -rw-r--r-- 1 root root 1476 Mar 29 00:26 activate.csh -4 -rw-r--r-- 1 root root 3039 Mar 29 00:26 activate.fish -4 -rw-r--r-- 1 root root 2724 Mar 29 00:26 activate.nu -4 -rw-r--r-- 1 root root 1650 Mar 29 00:26 activate.ps1 -4 -rw-r--r-- 1 root root 1337 Mar 29 00:26 activate_this.py -4 -rwxr-xr-x 1 root root 212 Mar 29 00:27 dotenv -4 -rwxr-xr-x 1 root root 204 Mar 29 00:27 httpx -0 lrwxrwxrwx 1 root root 25 Mar 29 00:26 python -> /usr/local/bin/python3.11 -0 lrwxrwxrwx 1 root root 6 Mar 29 00:26 python3 -> python -0 lrwxrwxrwx 1 root root 6 Mar 29 00:26 python3.11 -> python -4 -rwxr-xr-x 1 root root 207 Mar 29 00:27 tqdm -4 -rwxr-xr-x 1 root root 211 Mar 29 00:27 uvicorn -4 -rwxr-xr-x 1 root root 211 Mar 29 00:27 watchfiles -4 -rwxr-xr-x 1 root root 217 Mar 29 00:27 watchmedo -``` - -The path `python -> /usr/local/bin/python3.11` should be executable and run the interpreter: - -```bash -$ /venv/bin/python --version - -Python 3.11.8 -``` diff --git a/docs_website/docs/error_handling.md b/docs_website/docs/error_handling.md deleted file mode 100644 index b3fefd85..00000000 --- a/docs_website/docs/error_handling.md +++ /dev/null @@ -1,144 +0,0 @@ -# Error Handling - -Errors are a fundamental part of computer science, but nowhere is that more evident than when building websites. There are so many factors you don't have control over: client latency, malformed payloads, spiky server load, resource contention for the same user. The list goes on. Any one can bring a user experience to its knees. - -All that to say - it's not a question of _if_ you'll see errors but _when_. Mountaineer provides some handy utilities that make it a bit easier to handle the errors you may encounter in production. - -## Client->Server exceptions - -When client actions call server actions (either sideeffects or passthroughs), their browser needs to make an outgoing fetch request to your server. Your server can throw an error in response to this payload for any reason: validation failures, unexpected state, or just because some internal logic failed. - -When your server returns an error, your async action will raise the relevant error. Let's say you have the following component that issues an invalid call to a server action: - -```typescript - -``` - -When this button is clicked, it will send an `increment_count` action to the server. The server will validate the incoming payload with Pydantic, which will throw a [ValidationError](https://docs.pydantic.dev/latest/errors/validation_errors/) since it expects `count` to be an integer, not a string. The server will then respond with a 422 validation error, which will be passed back to the client and raised in the async function. You can catch this error with a try/catch block: - -```typescript -import { HTTPValidationErrorException } from "./_server/actions"; - -... - - -``` - -Mountaineer will convert the error into a custom error class and expose it in `_server/actions` for you to import. This class helps you switch logic depending on the type of error that was raised. Using a class here also has the benefit of typeguarding your error handling, so you'll see IDE recommendations specific to that ValidationError. - -You can find the error payload itself within `error.body`, which will be typehinted with all the metadata (if any) that the server is expected to return as part of this error code. In the above example, that looks like this: - -``` -Validation Error (2) ['body', 'count'] Input should be a valid integer, unable to parse string as an integer -``` - -Internally, we generate `HTTPValidationErrorException` as a subclass of FetchErrorBase. This provided the common error handling, while typehinting it for your specific API errors. - -```typescript title="_server/actions.ts" -class HTTPValidationErrorException extends FetchErrorBase {} -``` - -!!! tip - - For more information on error typehinting and custom handling, see the FastAPI [documentation](https://fastapi.tiangolo.com/tutorial/handling-errors/). - -## Custom Errors - -A 422 ValidationError is a special error that is included in every action, because your function signature is verified every time a client sends a new payload to your server. To implement a custom error that is specific to your application, you can subclass `APIException`: - -```python -from mountaineer.exceptions import APIException - -class LoginInvalid(APIException): - status_code = 401 - invalid_reason: str - -class LoginController(ControllerBase): - ... - - @passthrough(exception_models=[LoginInvalid]) - def login(self, login_payload: LoginRequest): - raise LoginInvalid(invalid_reason="Login not implemented") -``` - -Provide all the exceptions that your function may throw to `@passthrough(exception_models=[])`. The `@sideeffect` decorator accepts the same argument. - -When specified like this, Mountaineer turns your exception into a client-side exception just like `HTTPValidationErrorException`. You can now use it in the same way. - -## SSR timeouts - -To render each page on the server side, we have to execute your view's Javascript in a V8 engine. This is the same Javascript interpreter that powers Chrome. As such, you have the full freedom to write any Javascript in your view that will help you render your page - loops, calculations, package calls, etc. - -As is the case with Turing-complete languages, with great power comes great responsibility. - -These SSR requests can potentially take a long time. At the extreme, they could even clog up your server by infinite looping and never returning a value. We have a series of safeguards in place to help ensure SSR renders return quickly and keep your server able to chug through additional requests. - -- Debug logging of the duration of each SSR page render, for use in development. -- Warning logs if rendering takes longer than some interval so you can keep an eye on endpoints that might need some optimization. -- Hard timeouts for rendering. If something goes sideways and your server rendering takes longer a maximum threshold, we'll terminate the server-side Javascript executor for you and return an error to the client. - -## SSR exceptions - -Alongside timeouts, it's possible your view's Javascript actually gets into an unrecoverable state and throws an exception during rendering. To help you debug this on the server side, we'll raise this error as a `mountaineer.ssr.V8RuntimeError` and log the stack trace that comes back from the V8 engine. - -The paths reported in the stack trace are found from the sourcemap that's created alongside the compiled SSR files. These should point to the files in your view directory that have produced that exception. - -```bash -{"level": "ERROR", "name": "mountaineer.logging", "message": "Exception encountered in ComplexController rendering"} -ERROR: Exception in ASGI application -Traceback (most recent call last): - File "/Users/piercefreeman/projects/mountaineer/mountaineer/ssr.py", line 37, in render_ssr - render_result = mountaineer_rs.render_ssr( - ^^^^^^^^^^^^^^^^^^^^ -... - - File "/Users/piercefreeman/projects/mountaineer/mountaineer/ssr.py", line 43, in render_ssr - raise V8RuntimeError(e) -mountaineer.ssr.V8RuntimeError: Error calling function 'Index': Error: Example client error -Stack: Error: Example client error - at Page (./my_website/views/app/complex/page.tsx:41:10) - at renderWithHooks (./my_website/views/node_modules/react-dom/cjs/react-dom-server-legacy.browser.development.js:5660:15) - at renderIndeterminateComponent (./my_website/views/node_modules/react-dom/cjs/react-dom-server-legacy.browser.development.js:5733:14) - at renderElement (:6537:17) - at renderNodeDestructiveImpl (:6642:19) - at renderNodeDestructive (./my_website/views/node_modules/react-dom/cjs/react-dom-server-legacy.browser.development.js:6078:13) - at renderIndeterminateComponent (:6417:17) - at renderElement (:6537:17) - at renderNodeDestructiveImpl (:6642:19) - at renderNodeDestructive (./my_website/views/node_modules/react-dom/cjs/react-dom-server-legacy.browser.development.js:6078:13) -``` diff --git a/docs_website/docs/index.md b/docs_website/docs/index.md deleted file mode 100644 index b7362e1a..00000000 --- a/docs_website/docs/index.md +++ /dev/null @@ -1,17 +0,0 @@ -# Introduction - -Mountaineer 🏔️ is a framework to easily build webapps in Python and React. If you've used either of these languages before for development, we think you'll be right at home. - -## Main Features - -Each framework has its own unique features and tradeoffs. Mountaineer focuses on developer productivity above all else, with production speed a close second. - -- 📝 Typehints up and down the stack: frontend, backend, and database -- 🎙️ Trivially easy client<->server communication, data binding, and function calling -- 🌎 Optimized server rendering for better accessibility and SEO -- 🏹 Static analysis of web pages for strong validation: link validity, data access, etc. -- 🤩 Skip the API or Node.js server just to serve frontend clients - -## Getting Started - -Head on to the [Quickstart](./quickstart.md) for a full guide on setting up and implementing a new Mountaineer webapp. Our "Learn" section has a few more guides on how to use Mountaineer to its fullest. Then our "API" section has the full docs on all the features and functions available to you. diff --git a/docs_website/docs/internal/core_library.md b/docs_website/docs/internal/core_library.md deleted file mode 100644 index 3ebc358a..00000000 --- a/docs_website/docs/internal/core_library.md +++ /dev/null @@ -1,87 +0,0 @@ -# Core Library - -Miscellaneous notes on development and testing the core library. - -## Installation - -When doing local development work, use poetry to manage dependencies and maturin to create a build of the combined python/rust project: - -```bash -make install-deps -``` - -This effectively expands to: - -```bash -poetry install -poetry run maturin develop --release -``` - -You can also run maturin just with `poetry run maturin develop`, which will be much faster to compile, but rust execution will be notably slower. - -You'll also need a system-wide installation of esbuild. If you don't have one when you run the build pipline it will install one for you within `~/.cache/mountaineer/esbuild`. - -## External Application - -If you want to test your mountaineer changes against a real webapp, you can build a wheel and then add it -to your project's virtual environment: - -```bash -$ poetry run maturin build --release - - Finished `release` profile [optimized] target(s) in 51.28s -📦 Built wheel for CPython 3.12 to /Users/piercefreeman/projects/mountaineer/target/wheels/mountaineer-0.1.0-cp312-cp312-macosx_11_0_arm64.whl -``` - -Then, in your other project. If you've already installed a previous local mountaineer wheel, you'll have to run with `--force-reinstall` to prompt pip -to actually swap out the wheels versus ignoring the same version number. - -```bash -poetry run pip install [--force-reinstall] /Users/piercefreeman/projects/mountaineer/target/wheels/mountaineer-0.1.0-cp312-cp312-macosx_11_0_arm64.whl -``` - -## Logging - -It's sometimes helpful to run Mountaineer with verbose logging, whether you're developing new functionality -or trying to diagnose a problem. You can set the `MOUNTAINEER_LOG_LEVEL` environment variable to `DEBUG` to -enable verbose logging: - -```bash -$ MOUNTAINEER_LOG_LEVEL=DEBUG poetry run runserver -``` - -## Development Utilities - -1. Python Testing - - ```bash - $ make test - ``` - - During testing we also support providing additional test-args that are passed to pytest. This helps you narrow down the scope of tests, add more verbosity, etc.: - - ```bash - $ make test test-args="-k test_extracts_iterable" - ``` - -1. Python Linting - - ```bash - $ make lint - ``` - -1. Rust Benchmarking - - ```bash - $ cargo bench - ``` - -1. Diagnose errors in CI - - At the moment, our main CI testing flows run on Linux/x86-64 architectures. We've observed some behavior there that isn't reproducable locally. To test locally on OS X you'll need build a representative docker image and then test within it: - - ```bash - $ docker build -t mountaineer . - $ docker run -it mountaineer - $ make install-deps - $ make test-integrations diff --git a/docs_website/docs/links.md b/docs_website/docs/links.md deleted file mode 100644 index e9e2e427..00000000 --- a/docs_website/docs/links.md +++ /dev/null @@ -1,48 +0,0 @@ -# Links - -In a typical webapp, you'll have a lot of links. Most will be internal to your site: detail pages, settings, profiles, etc. Traditionally, developers format these links manually and hope they don't break over time as routes update. - -In Mountaineer, generating links is baked into the client side routes. For every controller you define, we'll generate a link interface that defines the parameters that controller accepts. This interface will update as your route declarations do, so you're guaranteed to always generate the latest links to successfully resolve that controller. - -Controllers set the parameters that they need to render their views by configuring the `render()` function with the required parameters: - -```python -class DetailController: - url = "/detail/{detail_id}" - - def render(self, detail_id: int, checking_out: bool = False) -> MyDetailData: - ... -``` - -Alongside generating the appropriate API and router files, Mountaineer will detect this render signature and produce a link generator. - -This particular generator will require a `detail_id` and support an optional `checking_out` boolean. The `detail_id` is required because it's a part of the controller url. `checking_out` on the other hand is optional, since it has a default keyword argument in the case that another value isn't provided. - -On the client side, you can now create these dynamic links anywhere within your application. Mount the server state of the current view and use the included `linkGenerator`. - -```typescript -const MyHomeRoute = () => { - const serverState = useServer(); - - return ( - - Detail Link - - ) -} -``` - -Not providing the `detail_id`, or providing an incorrect type for the ID, will throw a typescript error at compile time. - -## Link Parameters - -There are two types of dynamic URL parameters: - -- Path Variables: `/product/[product_id]` -- Query Variables: `/posts?page=[page_num]` - -Both path variables and query variables are exposed as interface definitions within the link generator. diff --git a/docs_website/docs/media/final_todo_list.png b/docs_website/docs/media/final_todo_list.png deleted file mode 100644 index b744fd8e..00000000 Binary files a/docs_website/docs/media/final_todo_list.png and /dev/null differ diff --git a/docs_website/docs/media/ide_typehints.png b/docs_website/docs/media/ide_typehints.png deleted file mode 100644 index ba9099dc..00000000 Binary files a/docs_website/docs/media/ide_typehints.png and /dev/null differ diff --git a/docs_website/docs/media/network_debug.png b/docs_website/docs/media/network_debug.png deleted file mode 100644 index d7d853c0..00000000 Binary files a/docs_website/docs/media/network_debug.png and /dev/null differ diff --git a/docs_website/docs/media/server_side_rendering.png b/docs_website/docs/media/server_side_rendering.png deleted file mode 100644 index 17d40e0b..00000000 Binary files a/docs_website/docs/media/server_side_rendering.png and /dev/null differ diff --git a/docs_website/docs/metadata.md b/docs_website/docs/metadata.md deleted file mode 100644 index 1a03d063..00000000 --- a/docs_website/docs/metadata.md +++ /dev/null @@ -1,63 +0,0 @@ -# Page Metadata - -Pages will have metadata associated with them: page title, description, tags, stylesheets, etc. Mountaineer specifies this metadata in the Python layer, during the initial render. - -Each `RenderBase` schema implements a `metadata` attribute. This metadata lets you customize the different fields that are injected in your html tag: - -```python -class Metadata(BaseModel): - title: str | None = None - metas: list[MetaAttribute] = [] - links: list[LinkAttribute] = [] -``` - -## Title - -Setting a page title is pretty straightforward. You just return a custom Metadata instance within your `render()` function. Because the metadata logic is located in-scope of your other business logic, you can access the full suit of calculated values. This is helpful to conditionally generate a dynamic title - like injecting a user's account name, number of new messages, etc. - -```python -class MyRender(RenderBase): - pass - -def render() -> MyRender: - return MyRender( - metadata=Metadata( - title="My Title" - ) - ) -``` - -## Meta and Links - -We provide a pretty vanilla syntax within `Metadata` so you can specify any and elements you'd liek. We do this with the recognition that only some meta tag values have been standardized, and others are left to the browser implementations or web crawlers to parse them appropriately. - -You'll typically instantiate a tag like this: - -```python -class MyRender(RenderBase): - pass - -def render() -> MyRender: - return MyRender( - metadata=Metadata( - metas=MetaAttribute( - "og:meta", - content="Some meta content", - ) - ) - ) -``` - -We have a limited number of helper constructors, particularly in cases where there are standard definitions of complex meta values. For more details on the supported Metadata and rendering options, check out the [API Docs](./api/render.md). - -## Global Metadata - -For metadata that you know should appear on every page (like stylesheets or global scripts), you can add a metadata tag to your app controller: - -```python -controller = AppController( - global_metadata=Metadata( - links=[LinkAttribute(rel="stylesheet", href="/static/main.css")] - ), -) -``` diff --git a/docs_website/docs/postcss.md b/docs_website/docs/postcss.md deleted file mode 100644 index 0b6e1333..00000000 --- a/docs_website/docs/postcss.md +++ /dev/null @@ -1,73 +0,0 @@ -# PostCSS & Tailwind - -## PostCSS - -[PostCSS](https://postcss.org/) is a compiler for CSS. It enables buildtime transformation of CSS files to convert SCSS, LESS, or other CSS-like languages into standard CSS. It also has utilities for polyfills and browser-specific prefixes. I think of it like a swiss-army knife for CSS: it helps assure that your styling intentions are actually rendered uniformily across browsers. - -PostCSS support is handled as a buildtime plugin with Mountaineer. It's disabled by default. To enable, make sure you have `postcss-cli` installed within your `views` project: - -```bash -npm install postcss-cli -``` - -After this you can leverage the `PostCSSBundler` within your custom build pipeline: - -```python -from mountaineer.client_compiler.postcss import PostCSSBundler - -controller = AppController( - custom_builders=[ - PostCSSBundler(), - ], -) -``` - -Adding the PostCSSBundler will find all the `.css` that you have specified within your `views` directory and pass them through PostCSS. Let's say you have the following CSS files: - -``` -/views/app/home/style.css -/views/app/detail/style.css -``` - -The compiler will pass each through PostCSS and deposit these artifacts into: - -``` -/views/_static/home_style.css -/views/_static/detail_style.css -``` - -You can then import this CSS file in whatever tag is relevant to your project. See the [metadata documentation](./metadata.md) for more details on how to do this. - -## Tailwind - -Tailwind uses PostCSS to handle the tree shaking and project analysis that allows it to output the minimal amount of CSS tags to correctly render your project. If you set up the PostCSS extension like described above, you should be able to follow the typical Tailwind [setup](https://tailwindcss.com/docs/installation) steps. - -```typescript title="views/app/tailwind.config.ts" -module.exports = { - content: ["./app/**/*.{html,tsx,jsx,ts,js}"], - theme: { - extend: {}, - }, - plugins: [], -} -``` - -```css title="views/app/main.css" -@tailwind base; -@tailwind components; -@tailwind utilities; -``` - -Then export the built styles into your global metadata: - -```python -controller = AppController( - config=AppConfig(), - global_metadata=Metadata( - links=[LinkAttribute(rel="stylesheet", href="/static/app_main.css")] - ), - custom_builders=[ - PostCSSBundler(), - ], -) -``` diff --git a/docs_website/docs/quickstart.md b/docs_website/docs/quickstart.md deleted file mode 100644 index 3f08d098..00000000 --- a/docs_website/docs/quickstart.md +++ /dev/null @@ -1,303 +0,0 @@ -# Quickstart - -Our quickstart guide walks you through creating a full Mountaineer project. The end application is simple but fully working and ready for deployment. - -### Getting Started - -To get started as quickly as possible, we bundle a project generator that sets up a simple project after a quick Q&A. Make sure you have pipx [installed](https://pipx.pypa.io/stable/installation/). - -```bash -$ pipx run create-mountaineer-app - -? Project name [my-project]: my_webapp -? Author [Pierce Freeman ] Default -? Use poetry for dependency management? [Yes] Yes -? Create stub MVC files? [Yes] Yes -? Use Tailwind CSS? [Yes] Yes -? Add editor configuration? [vscode] vscode -``` - -Mountaineer projects all follow a similar structure. For more on this, see our deep dive on the [structure](structure.md) conventions. - -### Development - -If you're starting a new application from scratch, you'll typically want to create your new database tables. Make sure you have postgres running. We bundle a docker compose file for convenience with `create-mountaineer-app`. - -```bash -docker compose up -d -poetry run createdb -``` - -Of course you can also use an existing database instance, simply configure it in the `.env` file in the project root. - -Mountaineer relies on watching your project for changes and doing progressive compilation. We provide a few CLI commands to help with this. - -While doing development work, you'll usually want to preview the frontend and automatically build dependent files. You can do this with: - -```bash -$ poetry run runserver - -INFO: Started server process [93111] -INFO: Waiting for application startup. -INFO: Application startup complete. -INFO: Uvicorn running on http://127.0.0.1:5006 (Press CTRL+C to quit) -``` - -Navigate to http://127.0.0.1:5006 to see your new webapp running. - -Or, if you just want to watch the source tree for changes without hosting the server. Watching will allow your frontend to pick up API definitions from your backend controllers: - -```bash -$ poetry run watch -``` - -Both of these CLI commands are specified in your project's `cli.py` file. - -### Walkthrough - -Below we go through some of the unique aspects of Mountaineer. Let's create a simple Todo list where we can add new items. - -For the purposes of this walkthrough we assume your project is generated with `create-mountaineer-app` and you've skipped MVC stub files. If not, you'll have to delete some of the pre-existing files. - -Let's get started by creating the data models that will persist app state to the database. These definitions are effectively Pydantic schemas that will be bridged to the database via [SQLModel](https://github.com/tiangolo/sqlmodel). - -```python title="my_webapp/models/todo.py" -from mountaineer.database import SQLModel, Field -from uuid import UUID, uuid4 - -class TodoItem(SQLModel, table=True): - id: UUID = Field(default_factory=uuid4, primary_key=True) - - description: str - completed: bool = False -``` - -Update the index file as well: - -```python title="my_webapp/models/__init__.py" -from .todo import TodoItem # noqa: F401 -``` - -Make sure you have a Postgres database running. We bundle a docker compose file for convenience with `create-mountaineer-app`. Launch it in the background and create the new database tables from these code definitions: - -```bash -docker compose up -d -poetry run createdb -poetry run runserver -``` - -Great! At this point we have our database tables created and have a basic server running. We next move to creating a new controller, since this will define which data you can push and pull to your frontend. - -```python title="my_webapp/controllers/home.py" -from mountaineer import sideeffect, ControllerBase, RenderBase -from mountaineer.database import DatabaseDependencies - -from fastapi import Request, Depends -from sqlalchemy.ext.asyncio import AsyncSession -from sqlmodel import select - -from my_webapp.models.todo import TodoItem - -class HomeRender(RenderBase): - client_ip: str - todos: list[TodoItem] - -class HomeController(ControllerBase): - url = "/" - view_path = "/app/home/page.tsx" - - async def render( - self, - request: Request, - session: AsyncSession = Depends(DatabaseDependencies.get_db_session) - ) -> HomeRender: - todos = (await session.exec(select(TodoItem))).all() - - return HomeRender( - client_ip=( - request.client.host - if request.client - else "unknown" - ), - todos=todos - ) -``` - -The only three requirements of a controller are setting the: - -- URL -- View path -- Initial data payload - -This `render()` function is a core building block of Mountaineer. All Controllers need to have one. It defines all the data that your frontend will need to resolve its view. This particular controller retrieves all Todo items from the database, alongside the user's current IP. - -!!! tip - - render() functions accepts all parameters that FastAPI endpoints do: paths, query parameters, and dependency injected functions. Right now we're just grabbing the `Request` object to get the client IP. - -Note that the database session is provided via dependency injection, which plug-and-plays with [FastAPI's](https://github.com/tiangolo/fastapi) Depends syntax. The standard library provides two main dependency providers: - -- mountaineer.CoreDependencies: helper functions for configurations and general dependency injection -- mountaineer.database.DatabaseDependencies: helper functions for database lifecycle and management - -Now that we've newly created this controller, we wire it up to the application. This registers it for display when you load the homepage. - -```python title="my_webapp/app.py" -from mountaineer.app import AppController -from mountaineer.client_compiler.postcss import PostCSSBundler -from mountaineer.render import LinkAttribute, Metadata - -from my_webapp.config import AppConfig -from my_webapp.controllers.home import HomeController - -controller = AppController( - config=AppConfig(), - global_metadata=Metadata( - links=[LinkAttribute(rel="stylesheet", href="/static/app_main.css")] - ), - custom_builders=[ - PostCSSBundler(), - ], -) - -controller.register(HomeController()) -``` - -Let's move over to the frontend. - -```typescript title="my_webapp/views/app/home/page.tsx" -import React from "react"; -import { useServer, ServerState } from "./_server/useServer"; - -const CreateTodo = ({ serverState }: { serverState: ServerState }) => { - return ( -
- - -
- ); -}; - -const Home = () => { - const serverState = useServer(); - - return ( -
-

- Hello {serverState.client_ip}, you have {serverState.todos.length} todo - items. -

- - { - /* Todo items are exposed as typehinted Typescript interfaces */ - serverState.todos.map((todo) => ( -
-
{todo.description}
-
- )) - } -
- ); -}; - -export default Home; -``` - -We define a simple view to show the data coming from the backend. To accomplish this conventionally, we'd need to wire up an API layer, a Node server, or format the page with Jinja templates. - -Here instead we use our automatically generated `useServer()` hook. This hook payload will provide all the `HomeRender` fields as properties of serverState. And it's available instantly on page load without any roundtrip fetches. Also - if your IDE supports language servers (which most do these days), you should see the fields auto-suggesting for `serverState` as you type. - -

IDE Typehints

- -If you access this in your browser at `localhost:5006/` we can see our welcome message, but we can't really _do_ anything with the todos yet. Let's add some interactivity. - -!!! tip - - Try disabling Javascript in your browser. The page will still render as-is with all variables intact, thanks to our server-side rendering. - -

Server-side rendering

- -What good is todo list that doesn't get longer? We define a `add_todo` function that accepts a pydantic model `NewTodoRequest`, which defines the required parameters for a new todo item. We then cast this to a database object and add it to the postgres table. - -```python title="my_webapp/controllers/home.py" - -from pydantic import BaseModel - -class NewTodoRequest(BaseModel): - description: str - -class HomeController(ControllerBase): - ... - - @sideeffect - async def add_todo( - self, - payload: NewTodoRequest, - session: AsyncSession = Depends(DatabaseDependencies.get_db_session) - ) -> None: - new_todo = TodoItem(description=payload.description) - session.add(new_todo) - await session.commit() -``` - -The important part here is the `@sideeffect`. Once you create a new Todo item, the previous state on the frontend is outdated. It will only show the todos before you created a new one. That's not what we want in an interactive app. This decorator indicates that we want the frontend to refresh its data, since after we update the todo list on the server the client state will be newly outdated. - -Mountaineer detects the presence of this sideeffect function and analyzes its signature. It then exposes this to the frontend as a normal async function. - -```typescript title="my_webapp/views/app/home/page.tsx" -import React, { useState } from "react"; -import { useServer } from "./_server/useServer"; - -/* Replace the existing CreateTodo component definition you have */ -const CreateTodo = ({ serverState }: { serverState: ServerState }) => { - const [newTodo, setNewTodo] = useState(""); - - return ( -
- setNewTodo(e.target.value)} - /> - -
- ); -}; - -... - -export default Home; -``` - -`useServer()` exposes our `add_todo` function so we can call our backend directly from our frontend. Also notice that we don't have to read or parse the output value of this function to render the new todo item to the list. Since the function is marked as a sideeffect, the frontend will automatically refresh its data after the function is called. - -Go ahead and load it in your browser. If you open up your web tools, you can create a new Todo and see POST requests sending data to the backend and receiving the current server state. The actual data updates and merging happens internally by Mountaineer. - -![Getting Started Final TODO App](media/final_todo_list.png){ height="400" } - -![Getting Started Final TODO App](media/network_debug.png){ height="400" } - -You can use these serverState variables anywhere you'd use dynamic React state variables (useEffect, useCallback, etc). But unlike React state, these variables are automatically updated when a relevant sideeffect is triggered. - -And that's it. We've just built a fully interactive web application without having to worry about an explicit API. You specify the data model and actions on the server and the appropriate frontend hooks are generated and updated automatically. It gives you the power of server rendered html and the interactivity of a virtual DOM, without having to compromise on complicated data mutations to keep everything in sync. diff --git a/docs_website/docs/static_analysis.md b/docs_website/docs/static_analysis.md deleted file mode 100644 index 4f4f3173..00000000 --- a/docs_website/docs/static_analysis.md +++ /dev/null @@ -1,92 +0,0 @@ -# Static Analysis - -A core design consideration in Mountaineer is allowing for full _static analysis_ of your webapps. This term just means that we can verify your code from its text alone without having to run it all. As your webapp grows in scope this becomes increasingly valuable, as it allows you to catch errors early before they become runtime issues. And the more complicated your logic becomes, the more switch conditions and edge cases you're necessarily going to have. Manually testing all that is... not going to be fun. - -Since static analysis is a personal choice, we don't yet bundle a type checker within Mountaineer. This page covers some basic suggestions for adding it to your project. - -!!! tip - - Adding a type checker to your project might seem like overkill while the scope is still small. But we find that adding it to CI early helps prevent future headaches by taking small steps to correctly annotate your function signatures, resolve ambiguities, and catch bugs while you still have the context of implementation. - -## Python - -`mypy` and `pyright` are the most popular type checkers for Python. They both effectively do the same thing. `mypy` is a bit more mature and has a larger community, but `pyright` is often preferred by IDEs so you help ensure your dev workflow is the same as your CI verification. We typically use both in our projects to be extra safe. - -```bash -poetry add mypy --group dev -poetry add pyright --group dev -``` - -Then modify your `pyproject.toml` to make as strict as you would like. Here's a good starting point for mypy: - -```toml -[tool.mypy] -warn_return_any = true -warn_unused_configs = true -check_untyped_defs = true -plugins = ["pydantic.mypy"] -``` - -Pyright is already a bit stricter by default, but you can really boost the verbosity by specifying: - -```toml -[tool.pyright] -typeCheckingMode = "strict" -``` - -Then, to run against your project: - -```bash -poetry run mypy -poetry run pyright -``` - -## Typescript - -Typescript's main goal as a language extension to Javascript is to provide typechecking and static analysis. These features are built into the default compiler `tsc`. As such, most frontend issues in your Mountaineer project are typically caught when we compile your typescript code with `poetry run build` or progressively with `runserver` or `watch`. However there are some cases where you don't want to perform a full build, like in CI where you just want to validate the current state of your project. - -```bash -npm install typescript --save-dev -``` - -The `tsconfig.json` specifies the level of strictness you want to enforce. These options set to the strictest level will catch the most errors: - -```json title="views/tsconfig.json" -{ - "compilerOptions": { - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "strictBindCallApply": true, - "strictPropertyInitialization": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true - } -} -``` - -| Parameter | Description | -|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------| -| strict | Enables all strict type-checking options. Setting this to true is the easiest way to ensure you're using TypeScript's strictest settings. | -| noImplicitAny | Raises error on expressions and declarations with an implied any type. | -| strictNullChecks | When enabled, null and undefined are not in the domain of every type. | -| strictFunctionTypes | Ensures functions' parameters are correctly typed. | -| strictBindCallApply | Enforce stricter checking of the bind, call, and apply methods on functions. | -| strictPropertyInitialization | Ensures class properties are initialized in the constructor. | -| noImplicitReturns | Report error when not all code paths in function return a value. | -| noFallthroughCasesInSwitch | Prevent fall-through cases in switch statements. | - -Then, to run against your project: - -```json title="views/package.json" -{ - "scripts": { - "typecheck": "tsc --noEmit" - } -} -``` - -```bash -npm run typecheck -``` diff --git a/docs_website/docs/structure.md b/docs_website/docs/structure.md deleted file mode 100644 index 13ba5a08..00000000 --- a/docs_website/docs/structure.md +++ /dev/null @@ -1,50 +0,0 @@ - -Mountaineer projects all follow a similar structure. This structure is provided by default through `create-mountaineer-app` but you can also construct it yourself. - -For a project called `my_webapp`, you should have something like the following: - -``` -my_webapp/ -├── README.md -├── docker compose.yml -├── my_webapp -│   ├── __init__.py -│   ├── app.py -│   ├── cli.py -│   ├── config.py -│   ├── controllers -│   │   ├── __init__.py -│   │   ├── detail.py -│   │   └── home.py -│   ├── main.py -│   ├── models -│   │   ├── __init__.py -│   │   └── detail.py -│   └── views -│   ├── app -│   ├── node_modules -│   ├── package-lock.json -│   ├── package.json -│   ├── postcss.config.js -│   └── tailwind.config.js -├── poetry.lock -└── pyproject.toml -``` - -Every service file is nested under the `my_webapp` root package. This is just a regular python project - you add new code files wherever you like and import them as you're used to. - -### Mandatory Conventions - -`views` - this disk based directory includes a nested npm project. Within it you write all your frontend logics and components, which are regular typescript/tsx files. - -Nested routes are in nested folders. This folder acts as your React project and is where you can define requirements and build parameters in `package.json` and `tsconfig.json`. - -### Suggested Conventions - -`controllers` - A controller is the python logic that backs your frontend view. You can define controllers however you like: a flat folder, nesting folders, or via no folders at all mixed in with other code. For simplicity we recommend starting with a single flat controllers folder and refactoring into sub-folders as distinct ownership areas of your code emerge. - -`app.py` - Instantiate your controllers and add them to an `AppController` to register them for client access. - -`config.py` - Specify the parameters that your application needs to run, to easily customize them: database host settings, secret keys, etc. Configurations can easily read from env variables and are globally accessible throughout your webapp. - -`main.py` - Expose your application to [uvicorn](https://www.uvicorn.org/) or another async compatible webserver that follows the ASGI specification (Asynchronous Server Gateway Interface). diff --git a/docs_website/docs/stylesheets/extra.css b/docs_website/docs/stylesheets/extra.css deleted file mode 100644 index 3edc7a7f..00000000 --- a/docs_website/docs/stylesheets/extra.css +++ /dev/null @@ -1,68 +0,0 @@ -.md-typeset { - font-size: 16px !important; - color: rgb(53, 64, 78); -} - -.md-header { - background-color: #FFFFFF; - color: rgb(53, 64, 78); - border-bottom: 1px solid #f3f3f3; -} - -.md-search__input { - border-radius: 4px; - background-color: #FFFFFF; - outline: 1px solid #DDDDDD; -} - -.md-search__icon .md-icon { - color: #CCCCCC; -} - -.md-search__output, .md-nav__title { - box-shadow: none !important; -} - -.md-search__input::placeholder { - color: rgb(53, 64, 78); -} - -.md-header--shadow { - box-shadow: none; -} - -.md-main__inner.md-grid { - margin-top: 0px !important; -} - -.md-content__inner, .md-sidebar { - margin-top: 20px; -} - -.md-content { - border-left: 1px solid #f3f3f3 !important; - border-right: 1px solid #f3f3f3 !important; -} - -/*.md-sidebar--primary ul li a { - padding: 2px 10px; - border-radius: 4px; -} - -.md-sidebar--primary ul li a.md-nav__link--active { - background: #3b82f6; - color: #FFFFFF; -} - -.md-sidebar--primary ul li a.md-nav__link--active:hover { - background: #3b82f6; - color: #FFFFFF; - }*/ - -.mountaineer-footer { - background-color: #FFFFFF; - color: rgb(53, 64, 78); - border-top: 1px solid #f3f3f3; - font-size: 16px; - padding: 20px; -} diff --git a/docs_website/docs/views.md b/docs_website/docs/views.md deleted file mode 100644 index edea48c7..00000000 --- a/docs_website/docs/views.md +++ /dev/null @@ -1,285 +0,0 @@ -# Views & Layouts - -Your React app should be initialized in the `/views` folder of your Mountaineer project. This is the directory where we look for package.json and tsconfig.json, and where esbuild looks for specific build-time overrides. In other words, the views folder should look just like your frontend application if you were building a Single-Page-App (SPA). It's just embedded within your larger Mountaineer project and rendered separately. - -We expect that all controller views will be labeled as a `page.tsx`, so they'll typically sit in a folder with themselves and other tightly connected React components. These views should have one default export, which is your page constructor: - -```typescript title="/views/app/home/page.tsx" -import React from "react"; - -const Home = () => { - return ( -
-

Home

-

Welcome to the home page!

-
- ); -} - -export default Home; -``` - -We don't support async functions for these page renders, instead expecting that your backend will provide all data required to serialize the view. - -If you want actions performed on the client-side after the view has been hydrated with Javascript, you can use a `useEffect` hook with an empty dependency list: - -```typescript title="/views/app/home/page.tsx" -import React, { useEffect } from "react"; - -const Home = () => { - useEffect(() => { - console.log("The page has been hydrated with Javascript!"); - }, []); - - return ( -
-

Home

-

Welcome to the home page!

-
- ); -} - -export default Home; -``` - -## Controllers - -A controller backs a view in a 1:1 relationship. It provides the backend plumbing to render the view, and can also provide sideeffects and passthroughs actions. The main entrypoint into this is the `render` function, which is called on your initial view to serialize all the data that your frontend will need when displaying the initial state of the page. All your data heavy lifting (database queries, manipulation, etc) should go here. - -```python title="/controllers/home.py" - -from mountaineer import sideeffect, ControllerBase, RenderBase -from mountaineer.database import DatabaseDependencies -from mountaineer.database.session import AsyncSession - -from sqlmodel import select - -from myapp.models import TodoItem - -class HomeRender(RenderBase): - todos: list[TodoItem] - -class HomeController(ControllerBase): - url = "/" - view_path = "/app/home/page.tsx" - - async def render( - self, - session: AsyncSession = Depends(DatabaseDependencies.get_db_session) - ) -> HomeRender: - todos = (await session.execute(select(TodoItem))).all() - - return HomeRender( - todos=todos - ) -``` - -### Path Parameters - -The `render` function signature is inspected to provide the full URL that can be called. To provide a URL parameter that extracts a given ID identifier and matches the following: - -``` -/details/a687566b-db3e-42e3-9053-4f679abe8277 -/details/4a4c26bc-554a-40dd-aecd-916abd3bc475 -``` - -You can do: - -```python -class DetailController(ControllerBase): - url = "/details/{item_id}" - view_path = "/app/details/page.tsx" - - async def render( - self, - item_id: UUID, - ) -> HomeRender: - ... -``` - -### Query Parameters - -Query parameters are also supported. We support both simple types (str, float, UUID, etc) alongside lists of simple types. To provide a query parameter that matches the following: - -``` -/search?name=Apple&cost=30&cost=50 -/search?name=Banana -``` - -You can do: - -```python -from typing import Annotated -from fastapi import Query - -class SearchController(ControllerBase): - url = "/search" - view_path = "/app/search/page.tsx" - - async def render( - self, - name: str, - cost: Annotated[list[int] | None, Query()] = None, - ) -> HomeRender: - ... -``` - -!!! tip - - Both path and query parameters are validated by FastAPI, so you can use the same validation techniques. For more details, see the FastAPI [guide](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/). - -## Layouts - -We also support the Next.js `layout.tsx` convention, which is a special file that will be used to wrap all containing views in a common layout. This is useful for things like headers, footers, and other common elements. - -The children of the page will be passed as `{children}` to the layout component. Make sure to include this in your rendered view: - -```typescript title="/views/app/layout.tsx" -import React from "react"; - -const Layout = ({ children } : { children: React.ReactNode }) => { - return ( -
-
-

My Website

-
-
- {children} -
-
-

© My Website

-
-
- ); -} - -export default Layout; -``` - -This allows you to chain layouts before rendering the final, most specific page: - -``` -views/ -└── app/ - ├── dashboard/ - │ ├── layout.tsx - │ ├── home/ - │ │ └── page.tsx - │ └── settings/ - │ └── page.tsx - └── layout.tsx -``` - -When rendering `dashboard/home/page.tsx`, the view will be wrapped in the `app/dashboard/layout.tsx` layout alongside `app/layout.tsx`. These layout files will be automatically found by Mountaineer during the build process. They don't require any explicit declaration in your Python backend if you're just using them for styling. - -If you need more server side power and want to define them in Python, you can add a LayoutController that backs the layout. - -### Layout Controllers - -Layouts support most of the same controller logic that regular pages do. They can specify their own actions, both sideeffects and passthroughs, which will re-render the layout as required. - -```python title="/controllers/root_layout.py" -from mountaineer import LayoutControllerBase, RenderBase -from mountaineer.actions import sideeffect - -class RootLayoutRender(RenderBase): - layout_value: int - -class RootLayoutController(LayoutControllerBase): - view_path = "/app/layout.tsx" - - def __init__(self): - super().__init__() - self.layout_value = 0 - - def render(self) -> RootLayoutRender: - return RootLayoutRender( - layout_value=self.layout_value, - ) - - @sideeffect - async def increment_layout_value(self) -> None: - self.layout_value += 1 -``` - -All these functions are now exposed to the frontend layout, including the link generator, state, and any actions specified. - -```typescript title="/views/app/layout.tsx" -import React, { ReactNode } from "react"; -import { useServer } from "./_server"; - -const Layout = ({ children }: { children: ReactNode }) => { - const serverState = useServer(); - - return ( -
-

Layout State: {serverState.layout_value}

-
{children}
-
- -
-
- ); -}; - -export default Layout; -``` - -Once your controller is declared, you'll need to mount your layout into the AppController like you do for regular pages. - -```python title="/app.py" -app_controller = AppController(...) -app_controller.register(RootLayoutController()) -``` - -In general you can implement layout controllers just like you do for pages. But since they're shared across multiple child controllers, make sure the keyword arguments you use in your `render` signature don't have any conflicts. Mountaineer will merge these signatures at runtime and check for duplicate keyword names across the layout's child pages. Arguments are allowed to share the same name _and_ type, in which case they will be resolved to the same value. Arguments with conflicting types will raise a `TypeError`. - -It's also worth noting that layout controllers will resolve their dependencies in the same scope as the page controllers. So if you need database access within your layout, you'll receive the same underlying transaction as the page controller. This makes dependency injection a powerful way to save on resources, but be careful to not treat them as isolated objects. - -## Typescript Configuration - -If you want to customize how Mountaineer builds your view files into raw client-side javascript, add a `tsconfig.json` file. The Typescript website includes a [full list](https://www.typescriptlang.org/tsconfig) of the available options here. A good place to start is: - -```json -{ - "compilerOptions": { - "target": "es2017", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - }, - "exclude": ["node_modules"] -} -``` - -A common convention is importing all your view paths with absolute paths (like `@/components/myfile`) instead of having to do relative imports (`../../components/myfile`). This can be easily achieved by adding a `paths` key to your `tsconfig.json`. Your import becomes relative to all paths in the root directory. - -```json -{ - "compilerOptions": { - "target": "es2017", - ... - "paths": { - "@/*": ["./*"] - } - }, - "exclude": ["node_modules"] -} -``` diff --git a/docs_website/mkdocs.yml b/docs_website/mkdocs.yml deleted file mode 100644 index 34ddaea6..00000000 --- a/docs_website/mkdocs.yml +++ /dev/null @@ -1,75 +0,0 @@ -site_name: "Mountaineer" - -theme: - name: material - custom_dir: overrides - font: - text: "ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji" - -extra_css: - - stylesheets/extra.css - -plugins: - - search - - mkdocstrings: - handlers: - python: - options: - docstring_style: sphinx - show_root_heading: true - show_if_no_docstring: true - inherited_members: true - members_order: source - separate_signature: true - unwrap_annotated: true - merge_init_into_class: true - docstring_section_style: spacy - signature_crossrefs: true - show_symbol_type_heading: true - show_symbol_type_toc: true - show_source: false - -nav: - - Mountaineer: index.md - - Create Mountaineer App: cma.md - - Quickstart: quickstart.md - - Learn: - - structure.md - - views.md - - client_actions.md - - metadata.md - - database.md - - database_migrations.md - - error_handling.md - - links.md - - static_analysis.md - - postcss.md - - deploy.md - - API: - - api/app-controller.md - - api/controller.md - - api/actions.md - - api/cli.md - - api/api_exception.md - - api/render.md - - api/config.md - - api/core_dependencies.md - - api/logging.md - - api/watch_server.md - - api/ssr.md - - Database: - - api/database/config.md - - api/database/dependencies.md - - api/database/migrations.md - - Build Plugins: - - api/build_plugins/postcss.md - - api/build_plugins/base.md - - Internal: - - internal/core_library.md - -markdown_extensions: - - pymdownx.superfences - - admonition - - pymdownx.details - - attr_list - - md_in_html diff --git a/docs_website/overrides/partials/footer.html b/docs_website/overrides/partials/footer.html deleted file mode 100644 index e8e00fbf..00000000 --- a/docs_website/overrides/partials/footer.html +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/docs_website/poetry.lock b/docs_website/poetry.lock deleted file mode 100644 index 1c5d3dc8..00000000 --- a/docs_website/poetry.lock +++ /dev/null @@ -1,1812 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[[package]] -name = "anyio" -version = "4.4.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.8" -files = [ - {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, - {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, -] - -[package.dependencies] -idna = ">=2.8" -sniffio = ">=1.1" - -[package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] - -[[package]] -name = "async-timeout" -version = "4.0.3" -description = "Timeout context manager for asyncio programs" -optional = false -python-versions = ">=3.7" -files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] - -[[package]] -name = "asyncpg" -version = "0.29.0" -description = "An asyncio PostgreSQL driver" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"}, - {file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"}, - {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e6823a7012be8b68301342ba33b4740e5a166f6bbda0aee32bc01638491a22"}, - {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746e80d83ad5d5464cfbf94315eb6744222ab00aa4e522b704322fb182b83610"}, - {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ff8e8109cd6a46ff852a5e6bab8b0a047d7ea42fcb7ca5ae6eaae97d8eacf397"}, - {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97eb024685b1d7e72b1972863de527c11ff87960837919dac6e34754768098eb"}, - {file = "asyncpg-0.29.0-cp310-cp310-win32.whl", hash = "sha256:5bbb7f2cafd8d1fa3e65431833de2642f4b2124be61a449fa064e1a08d27e449"}, - {file = "asyncpg-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:76c3ac6530904838a4b650b2880f8e7af938ee049e769ec2fba7cd66469d7772"}, - {file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"}, - {file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"}, - {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"}, - {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"}, - {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"}, - {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"}, - {file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"}, - {file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"}, - {file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"}, - {file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"}, - {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"}, - {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"}, - {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"}, - {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"}, - {file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"}, - {file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"}, - {file = "asyncpg-0.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9"}, - {file = "asyncpg-0.29.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cad1324dbb33f3ca0cd2074d5114354ed3be2b94d48ddfd88af75ebda7c43cc"}, - {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012d01df61e009015944ac7543d6ee30c2dc1eb2f6b10b62a3f598beb6531548"}, - {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7"}, - {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bfe9c4d3429706cf70d3249089de14d6a01192d617e9093a8e941fea8ee775"}, - {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:642a36eb41b6313ffa328e8a5c5c2b5bea6ee138546c9c3cf1bffaad8ee36dd9"}, - {file = "asyncpg-0.29.0-cp38-cp38-win32.whl", hash = "sha256:a921372bbd0aa3a5822dd0409da61b4cd50df89ae85150149f8c119f23e8c408"}, - {file = "asyncpg-0.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:103aad2b92d1506700cbf51cd8bb5441e7e72e87a7b3a2ca4e32c840f051a6a3"}, - {file = "asyncpg-0.29.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5340dd515d7e52f4c11ada32171d87c05570479dc01dc66d03ee3e150fb695da"}, - {file = "asyncpg-0.29.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e17b52c6cf83e170d3d865571ba574577ab8e533e7361a2b8ce6157d02c665d3"}, - {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f100d23f273555f4b19b74a96840aa27b85e99ba4b1f18d4ebff0734e78dc090"}, - {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48e7c58b516057126b363cec8ca02b804644fd012ef8e6c7e23386b7d5e6ce83"}, - {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9ea3f24eb4c49a615573724d88a48bd1b7821c890c2effe04f05382ed9e8810"}, - {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8d36c7f14a22ec9e928f15f92a48207546ffe68bc412f3be718eedccdf10dc5c"}, - {file = "asyncpg-0.29.0-cp39-cp39-win32.whl", hash = "sha256:797ab8123ebaed304a1fad4d7576d5376c3a006a4100380fb9d517f0b59c1ab2"}, - {file = "asyncpg-0.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:cce08a178858b426ae1aa8409b5cc171def45d4293626e7aa6510696d46decd8"}, - {file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"}, -] - -[package.dependencies] -async-timeout = {version = ">=4.0.3", markers = "python_version < \"3.12.0\""} - -[package.extras] -docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"] - -[[package]] -name = "babel" -version = "2.16.0" -description = "Internationalization utilities" -optional = false -python-versions = ">=3.8" -files = [ - {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, - {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, -] - -[package.extras] -dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] - -[[package]] -name = "black" -version = "24.8.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, - {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, - {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, - {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, - {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, - {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, - {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, - {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, - {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, - {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, - {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, - {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, - {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, - {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, - {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, - {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, - {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, - {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, - {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, - {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, - {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, - {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "certifi" -version = "2024.8.30" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "fastapi" -version = "0.109.2" -description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fastapi-0.109.2-py3-none-any.whl", hash = "sha256:2c9bab24667293b501cad8dd388c05240c850b58ec5876ee3283c47d6e1e3a4d"}, - {file = "fastapi-0.109.2.tar.gz", hash = "sha256:f3817eac96fe4f65a2ebb4baa000f394e55f5fccdaf7f75250804bc58f354f73"}, -] - -[package.dependencies] -pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.36.3,<0.37.0" -typing-extensions = ">=4.8.0" - -[package.extras] -all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] - -[[package]] -name = "ghp-import" -version = "2.1.0" -description = "Copy your docs directly to the gh-pages branch." -optional = false -python-versions = "*" -files = [ - {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, - {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, -] - -[package.dependencies] -python-dateutil = ">=2.8.1" - -[package.extras] -dev = ["flake8", "markdown", "twine", "wheel"] - -[[package]] -name = "greenlet" -version = "3.1.0" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.7" -files = [ - {file = "greenlet-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a814dc3100e8a046ff48faeaa909e80cdb358411a3d6dd5293158425c684eda8"}, - {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a771dc64fa44ebe58d65768d869fcfb9060169d203446c1d446e844b62bdfdca"}, - {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0e49a65d25d7350cca2da15aac31b6f67a43d867448babf997fe83c7505f57bc"}, - {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cd8518eade968bc52262d8c46727cfc0826ff4d552cf0430b8d65aaf50bb91d"}, - {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76dc19e660baea5c38e949455c1181bc018893f25372d10ffe24b3ed7341fb25"}, - {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0a5b1c22c82831f56f2f7ad9bbe4948879762fe0d59833a4a71f16e5fa0f682"}, - {file = "greenlet-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2651dfb006f391bcb240635079a68a261b227a10a08af6349cba834a2141efa1"}, - {file = "greenlet-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3e7e6ef1737a819819b1163116ad4b48d06cfdd40352d813bb14436024fcda99"}, - {file = "greenlet-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:ffb08f2a1e59d38c7b8b9ac8083c9c8b9875f0955b1e9b9b9a965607a51f8e54"}, - {file = "greenlet-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9730929375021ec90f6447bff4f7f5508faef1c02f399a1953870cdb78e0c345"}, - {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:713d450cf8e61854de9420fb7eea8ad228df4e27e7d4ed465de98c955d2b3fa6"}, - {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c3446937be153718250fe421da548f973124189f18fe4575a0510b5c928f0cc"}, - {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ddc7bcedeb47187be74208bc652d63d6b20cb24f4e596bd356092d8000da6d6"}, - {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44151d7b81b9391ed759a2f2865bbe623ef00d648fed59363be2bbbd5154656f"}, - {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cea1cca3be76c9483282dc7760ea1cc08a6ecec1f0b6ca0a94ea0d17432da19"}, - {file = "greenlet-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:619935a44f414274a2c08c9e74611965650b730eb4efe4b2270f91df5e4adf9a"}, - {file = "greenlet-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:221169d31cada333a0c7fd087b957c8f431c1dba202c3a58cf5a3583ed973e9b"}, - {file = "greenlet-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:01059afb9b178606b4b6e92c3e710ea1635597c3537e44da69f4531e111dd5e9"}, - {file = "greenlet-3.1.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:24fc216ec7c8be9becba8b64a98a78f9cd057fd2dc75ae952ca94ed8a893bf27"}, - {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d07c28b85b350564bdff9f51c1c5007dfb2f389385d1bc23288de51134ca303"}, - {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:243a223c96a4246f8a30ea470c440fe9db1f5e444941ee3c3cd79df119b8eebf"}, - {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26811df4dc81271033a7836bc20d12cd30938e6bd2e9437f56fa03da81b0f8fc"}, - {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9d86401550b09a55410f32ceb5fe7efcd998bd2dad9e82521713cb148a4a15f"}, - {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26d9c1c4f1748ccac0bae1dbb465fb1a795a75aba8af8ca871503019f4285e2a"}, - {file = "greenlet-3.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:cd468ec62257bb4544989402b19d795d2305eccb06cde5da0eb739b63dc04665"}, - {file = "greenlet-3.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a53dfe8f82b715319e9953330fa5c8708b610d48b5c59f1316337302af5c0811"}, - {file = "greenlet-3.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:28fe80a3eb673b2d5cc3b12eea468a5e5f4603c26aa34d88bf61bba82ceb2f9b"}, - {file = "greenlet-3.1.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:76b3e3976d2a452cba7aa9e453498ac72240d43030fdc6d538a72b87eaff52fd"}, - {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:655b21ffd37a96b1e78cc48bf254f5ea4b5b85efaf9e9e2a526b3c9309d660ca"}, - {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6f4c2027689093775fd58ca2388d58789009116844432d920e9147f91acbe64"}, - {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76e5064fd8e94c3f74d9fd69b02d99e3cdb8fc286ed49a1f10b256e59d0d3a0b"}, - {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a4bf607f690f7987ab3291406e012cd8591a4f77aa54f29b890f9c331e84989"}, - {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037d9ac99540ace9424cb9ea89f0accfaff4316f149520b4ae293eebc5bded17"}, - {file = "greenlet-3.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:90b5bbf05fe3d3ef697103850c2ce3374558f6fe40fd57c9fac1bf14903f50a5"}, - {file = "greenlet-3.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:726377bd60081172685c0ff46afbc600d064f01053190e4450857483c4d44484"}, - {file = "greenlet-3.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:d46d5069e2eeda111d6f71970e341f4bd9aeeee92074e649ae263b834286ecc0"}, - {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81eeec4403a7d7684b5812a8aaa626fa23b7d0848edb3a28d2eb3220daddcbd0"}, - {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a3dae7492d16e85ea6045fd11cb8e782b63eac8c8d520c3a92c02ac4573b0a6"}, - {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b5ea3664eed571779403858d7cd0a9b0ebf50d57d2cdeafc7748e09ef8cd81a"}, - {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22f4e26400f7f48faef2d69c20dc055a1f3043d330923f9abe08ea0aecc44df"}, - {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13ff8c8e54a10472ce3b2a2da007f915175192f18e6495bad50486e87c7f6637"}, - {file = "greenlet-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9671e7282d8c6fcabc32c0fb8d7c0ea8894ae85cee89c9aadc2d7129e1a9954"}, - {file = "greenlet-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:184258372ae9e1e9bddce6f187967f2e08ecd16906557c4320e3ba88a93438c3"}, - {file = "greenlet-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:a0409bc18a9f85321399c29baf93545152d74a49d92f2f55302f122007cfda00"}, - {file = "greenlet-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9eb4a1d7399b9f3c7ac68ae6baa6be5f9195d1d08c9ddc45ad559aa6b556bce6"}, - {file = "greenlet-3.1.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:a8870983af660798dc1b529e1fd6f1cefd94e45135a32e58bd70edd694540f33"}, - {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfcfb73aed40f550a57ea904629bdaf2e562c68fa1164fa4588e752af6efdc3f"}, - {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9482c2ed414781c0af0b35d9d575226da6b728bd1a720668fa05837184965b7"}, - {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d58ec349e0c2c0bc6669bf2cd4982d2f93bf067860d23a0ea1fe677b0f0b1e09"}, - {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd65695a8df1233309b701dec2539cc4b11e97d4fcc0f4185b4a12ce54db0491"}, - {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:665b21e95bc0fce5cab03b2e1d90ba9c66c510f1bb5fdc864f3a377d0f553f6b"}, - {file = "greenlet-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3c59a06c2c28a81a026ff11fbf012081ea34fb9b7052f2ed0366e14896f0a1d"}, - {file = "greenlet-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415b9494ff6240b09af06b91a375731febe0090218e2898d2b85f9b92abcda0"}, - {file = "greenlet-3.1.0-cp38-cp38-win32.whl", hash = "sha256:1544b8dd090b494c55e60c4ff46e238be44fdc472d2589e943c241e0169bcea2"}, - {file = "greenlet-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:7f346d24d74c00b6730440f5eb8ec3fe5774ca8d1c9574e8e57c8671bb51b910"}, - {file = "greenlet-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:db1b3ccb93488328c74e97ff888604a8b95ae4f35f4f56677ca57a4fc3a4220b"}, - {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44cd313629ded43bb3b98737bba2f3e2c2c8679b55ea29ed73daea6b755fe8e7"}, - {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fad7a051e07f64e297e6e8399b4d6a3bdcad3d7297409e9a06ef8cbccff4f501"}, - {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3967dcc1cd2ea61b08b0b276659242cbce5caca39e7cbc02408222fb9e6ff39"}, - {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d45b75b0f3fd8d99f62eb7908cfa6d727b7ed190737dec7fe46d993da550b81a"}, - {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2d004db911ed7b6218ec5c5bfe4cf70ae8aa2223dffbb5b3c69e342bb253cb28"}, - {file = "greenlet-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9505a0c8579899057cbefd4ec34d865ab99852baf1ff33a9481eb3924e2da0b"}, - {file = "greenlet-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fd6e94593f6f9714dbad1aaba734b5ec04593374fa6638df61592055868f8b8"}, - {file = "greenlet-3.1.0-cp39-cp39-win32.whl", hash = "sha256:d0dd943282231480aad5f50f89bdf26690c995e8ff555f26d8a5b9887b559bcc"}, - {file = "greenlet-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:ac0adfdb3a21dc2a24ed728b61e72440d297d0fd3a577389df566651fcd08f97"}, - {file = "greenlet-3.1.0.tar.gz", hash = "sha256:b395121e9bbe8d02a750886f108d540abe66075e61e22f7353d9acb0b81be0f0"}, -] - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil"] - -[[package]] -name = "griffe" -version = "1.2.0" -description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." -optional = false -python-versions = ">=3.8" -files = [ - {file = "griffe-1.2.0-py3-none-any.whl", hash = "sha256:a8b2fcb1ecdc5a412e646b0b4375eb20a5d2eac3a11dd8c10c56967a4097663c"}, - {file = "griffe-1.2.0.tar.gz", hash = "sha256:1c9f6ef7455930f3f9b0c4145a961c90385d1e2cbc496f7796fbff560ec60d31"}, -] - -[package.dependencies] -colorama = ">=0.4" - -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.7" -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - -[[package]] -name = "httptools" -version = "0.6.1" -description = "A collection of framework independent HTTP protocol utils." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"}, - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"}, - {file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"}, - {file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"}, - {file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"}, - {file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"}, - {file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"}, - {file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"}, -] - -[package.extras] -test = ["Cython (>=0.29.24,<0.30.0)"] - -[[package]] -name = "idna" -version = "3.8" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -files = [ - {file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"}, - {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, -] - -[[package]] -name = "inflection" -version = "0.5.1" -description = "A port of Ruby on Rails inflector to Python" -optional = false -python-versions = ">=3.5" -files = [ - {file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"}, - {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"}, -] - -[[package]] -name = "jinja2" -version = "3.1.4" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "markdown" -version = "3.7" -description = "Python implementation of John Gruber's Markdown." -optional = false -python-versions = ">=3.8" -files = [ - {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, - {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, -] - -[package.extras] -docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] -testing = ["coverage", "pyyaml"] - -[[package]] -name = "markdown-callouts" -version = "0.4.0" -description = "Markdown extension: a classier syntax for admonitions" -optional = false -python-versions = ">=3.8" -files = [ - {file = "markdown_callouts-0.4.0-py3-none-any.whl", hash = "sha256:ed0da38f29158d93116a0d0c6ecaf9df90b37e0d989b5337d678ee6e6d6550b7"}, - {file = "markdown_callouts-0.4.0.tar.gz", hash = "sha256:7ed2c90486967058a73a547781121983839522d67041ae52c4979616f1b2b746"}, -] - -[package.dependencies] -markdown = ">=3.3.3" - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "markupsafe" -version = "2.1.5" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "mergedeep" -version = "1.3.4" -description = "A deep merge function for 🐍." -optional = false -python-versions = ">=3.6" -files = [ - {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, - {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, -] - -[[package]] -name = "mkdocs" -version = "1.6.1" -description = "Project documentation with Markdown." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, - {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, -] - -[package.dependencies] -click = ">=7.0" -colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} -ghp-import = ">=1.0" -jinja2 = ">=2.11.1" -markdown = ">=3.3.6" -markupsafe = ">=2.0.1" -mergedeep = ">=1.3.4" -mkdocs-get-deps = ">=0.2.0" -packaging = ">=20.5" -pathspec = ">=0.11.1" -pyyaml = ">=5.1" -pyyaml-env-tag = ">=0.1" -watchdog = ">=2.0" - -[package.extras] -i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] - -[[package]] -name = "mkdocs-autorefs" -version = "1.2.0" -description = "Automatically link across pages in MkDocs." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocs_autorefs-1.2.0-py3-none-any.whl", hash = "sha256:d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f"}, - {file = "mkdocs_autorefs-1.2.0.tar.gz", hash = "sha256:a86b93abff653521bda71cf3fc5596342b7a23982093915cb74273f67522190f"}, -] - -[package.dependencies] -Markdown = ">=3.3" -markupsafe = ">=2.0.1" -mkdocs = ">=1.1" - -[[package]] -name = "mkdocs-get-deps" -version = "0.2.0" -description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, - {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, -] - -[package.dependencies] -mergedeep = ">=1.3.4" -platformdirs = ">=2.2.0" -pyyaml = ">=5.1" - -[[package]] -name = "mkdocs-material" -version = "9.5.34" -description = "Documentation that simply works" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocs_material-9.5.34-py3-none-any.whl", hash = "sha256:54caa8be708de2b75167fd4d3b9f3d949579294f49cb242515d4653dbee9227e"}, - {file = "mkdocs_material-9.5.34.tar.gz", hash = "sha256:1e60ddf716cfb5679dfd65900b8a25d277064ed82d9a53cd5190e3f894df7840"}, -] - -[package.dependencies] -babel = ">=2.10,<3.0" -colorama = ">=0.4,<1.0" -jinja2 = ">=3.0,<4.0" -markdown = ">=3.2,<4.0" -mkdocs = ">=1.6,<2.0" -mkdocs-material-extensions = ">=1.3,<2.0" -paginate = ">=0.5,<1.0" -pygments = ">=2.16,<3.0" -pymdown-extensions = ">=10.2,<11.0" -regex = ">=2022.4" -requests = ">=2.26,<3.0" - -[package.extras] -git = ["mkdocs-git-committers-plugin-2 (>=1.1,<2.0)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] -imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"] -recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] - -[[package]] -name = "mkdocs-material-extensions" -version = "1.3.1" -description = "Extension pack for Python Markdown and MkDocs Material." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, - {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, -] - -[[package]] -name = "mkdocstrings" -version = "0.26.1" -description = "Automatic documentation from sources, for MkDocs." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocstrings-0.26.1-py3-none-any.whl", hash = "sha256:29738bfb72b4608e8e55cc50fb8a54f325dc7ebd2014e4e3881a49892d5983cf"}, - {file = "mkdocstrings-0.26.1.tar.gz", hash = "sha256:bb8b8854d6713d5348ad05b069a09f3b79edbc6a0f33a34c6821141adb03fe33"}, -] - -[package.dependencies] -click = ">=7.0" -Jinja2 = ">=2.11.1" -Markdown = ">=3.6" -MarkupSafe = ">=1.1" -mkdocs = ">=1.4" -mkdocs-autorefs = ">=1.2" -mkdocstrings-crystal = {version = ">=0.3.4", optional = true, markers = "extra == \"crystal\""} -platformdirs = ">=2.2" -pymdown-extensions = ">=6.3" - -[package.extras] -crystal = ["mkdocstrings-crystal (>=0.3.4)"] -python = ["mkdocstrings-python (>=0.5.2)"] -python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] - -[[package]] -name = "mkdocstrings-crystal" -version = "0.3.7" -description = "Crystal language doc generator for mkdocstrings" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocstrings_crystal-0.3.7-py3-none-any.whl", hash = "sha256:91f25700a1e13ee5157aa5875441d333830c392d5ace7ef1c2106d9e5b2883b6"}, - {file = "mkdocstrings_crystal-0.3.7.tar.gz", hash = "sha256:6d0b2fc8ef1256aec2cc4ff22a7d5aff6398c574cede10a5941e5aa3590012c7"}, -] - -[package.dependencies] -jinja2 = ">=2.11.2" -markdown-callouts = ">=0.1.0" -markupsafe = ">=1.1.1" -mkdocs-autorefs = ">=0.3.1" -mkdocstrings = ">=0.19.0" - -[[package]] -name = "mkdocstrings-python" -version = "1.11.1" -description = "A Python handler for mkdocstrings." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocstrings_python-1.11.1-py3-none-any.whl", hash = "sha256:a21a1c05acef129a618517bb5aae3e33114f569b11588b1e7af3e9d4061a71af"}, - {file = "mkdocstrings_python-1.11.1.tar.gz", hash = "sha256:8824b115c5359304ab0b5378a91f6202324a849e1da907a3485b59208b797322"}, -] - -[package.dependencies] -griffe = ">=0.49" -mkdocs-autorefs = ">=1.2" -mkdocstrings = ">=0.26" - -[[package]] -name = "mountaineer" -version = "0.1.0" -description = "" -optional = false -python-versions = "^3.10" -files = [] -develop = true - -[package.dependencies] -asyncpg = "^0.29.0" -click = "^8.1.7" -fastapi = "^0.109.0" -inflection = "^0.5.1" -packaging = "^23.2" -pydantic = "^2.5.3" -pydantic-settings = "^2.1.0" -rich = "^13.7.1" -sqlalchemy = {version = "^2.0.26", extras = ["asyncio"]} -sqlmodel = "^0.0.20" -uvicorn = {version = "^0.27.0.post1", extras = ["standard"]} -watchdog = "^3.0.0" - -[package.source] -type = "directory" -url = ".." - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "packaging" -version = "23.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.7" -files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, -] - -[[package]] -name = "paginate" -version = "0.5.7" -description = "Divides large result sets into pages for easier browsing" -optional = false -python-versions = "*" -files = [ - {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, - {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, -] - -[package.extras] -dev = ["pytest", "tox"] -lint = ["black"] - -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - -[[package]] -name = "platformdirs" -version = "4.3.2" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.8" -files = [ - {file = "platformdirs-4.3.2-py3-none-any.whl", hash = "sha256:eb1c8582560b34ed4ba105009a4badf7f6f85768b30126f351328507b2beb617"}, - {file = "platformdirs-4.3.2.tar.gz", hash = "sha256:9e5e27a08aa095dd127b9f2e764d74254f482fef22b0970773bfba79d091ab8c"}, -] - -[package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] - -[[package]] -name = "pydantic" -version = "2.9.1" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic-2.9.1-py3-none-any.whl", hash = "sha256:7aff4db5fdf3cf573d4b3c30926a510a10e19a0774d38fc4967f78beb6deb612"}, - {file = "pydantic-2.9.1.tar.gz", hash = "sha256:1363c7d975c7036df0db2b4a61f2e062fbc0aa5ab5f2772e0ffc7191a4f4bce2"}, -] - -[package.dependencies] -annotated-types = ">=0.6.0" -pydantic-core = "2.23.3" -typing-extensions = [ - {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, - {version = ">=4.6.1", markers = "python_version < \"3.13\""}, -] - -[package.extras] -email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] - -[[package]] -name = "pydantic-core" -version = "2.23.3" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic_core-2.23.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7f10a5d1b9281392f1bf507d16ac720e78285dfd635b05737c3911637601bae6"}, - {file = "pydantic_core-2.23.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c09a7885dd33ee8c65266e5aa7fb7e2f23d49d8043f089989726391dd7350c5"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6470b5a1ec4d1c2e9afe928c6cb37eb33381cab99292a708b8cb9aa89e62429b"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9172d2088e27d9a185ea0a6c8cebe227a9139fd90295221d7d495944d2367700"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86fc6c762ca7ac8fbbdff80d61b2c59fb6b7d144aa46e2d54d9e1b7b0e780e01"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0cb80fd5c2df4898693aa841425ea1727b1b6d2167448253077d2a49003e0ed"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03667cec5daf43ac4995cefa8aaf58f99de036204a37b889c24a80927b629cec"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:047531242f8e9c2db733599f1c612925de095e93c9cc0e599e96cf536aaf56ba"}, - {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5499798317fff7f25dbef9347f4451b91ac2a4330c6669821c8202fd354c7bee"}, - {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bbb5e45eab7624440516ee3722a3044b83fff4c0372efe183fd6ba678ff681fe"}, - {file = "pydantic_core-2.23.3-cp310-none-win32.whl", hash = "sha256:8b5b3ed73abb147704a6e9f556d8c5cb078f8c095be4588e669d315e0d11893b"}, - {file = "pydantic_core-2.23.3-cp310-none-win_amd64.whl", hash = "sha256:2b603cde285322758a0279995b5796d64b63060bfbe214b50a3ca23b5cee3e83"}, - {file = "pydantic_core-2.23.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c889fd87e1f1bbeb877c2ee56b63bb297de4636661cc9bbfcf4b34e5e925bc27"}, - {file = "pydantic_core-2.23.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea85bda3189fb27503af4c45273735bcde3dd31c1ab17d11f37b04877859ef45"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7f7f72f721223f33d3dc98a791666ebc6a91fa023ce63733709f4894a7dc611"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b2b55b0448e9da68f56b696f313949cda1039e8ec7b5d294285335b53104b61"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c24574c7e92e2c56379706b9a3f07c1e0c7f2f87a41b6ee86653100c4ce343e5"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2b05e6ccbee333a8f4b8f4d7c244fdb7a979e90977ad9c51ea31261e2085ce0"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2c409ce1c219c091e47cb03feb3c4ed8c2b8e004efc940da0166aaee8f9d6c8"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d965e8b325f443ed3196db890d85dfebbb09f7384486a77461347f4adb1fa7f8"}, - {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f56af3a420fb1ffaf43ece3ea09c2d27c444e7c40dcb7c6e7cf57aae764f2b48"}, - {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b01a078dd4f9a52494370af21aa52964e0a96d4862ac64ff7cea06e0f12d2c5"}, - {file = "pydantic_core-2.23.3-cp311-none-win32.whl", hash = "sha256:560e32f0df04ac69b3dd818f71339983f6d1f70eb99d4d1f8e9705fb6c34a5c1"}, - {file = "pydantic_core-2.23.3-cp311-none-win_amd64.whl", hash = "sha256:c744fa100fdea0d000d8bcddee95213d2de2e95b9c12be083370b2072333a0fa"}, - {file = "pydantic_core-2.23.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e0ec50663feedf64d21bad0809f5857bac1ce91deded203efc4a84b31b2e4305"}, - {file = "pydantic_core-2.23.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db6e6afcb95edbe6b357786684b71008499836e91f2a4a1e55b840955b341dbb"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ccd69edcf49f0875d86942f4418a4e83eb3047f20eb897bffa62a5d419c8fa"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a678c1ac5c5ec5685af0133262103defb427114e62eafeda12f1357a12140162"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01491d8b4d8db9f3391d93b0df60701e644ff0894352947f31fff3e52bd5c801"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fcf31facf2796a2d3b7fe338fe8640aa0166e4e55b4cb108dbfd1058049bf4cb"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7200fd561fb3be06827340da066df4311d0b6b8eb0c2116a110be5245dceb326"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc1636770a809dee2bd44dd74b89cc80eb41172bcad8af75dd0bc182c2666d4c"}, - {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:67a5def279309f2e23014b608c4150b0c2d323bd7bccd27ff07b001c12c2415c"}, - {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:748bdf985014c6dd3e1e4cc3db90f1c3ecc7246ff5a3cd4ddab20c768b2f1dab"}, - {file = "pydantic_core-2.23.3-cp312-none-win32.whl", hash = "sha256:255ec6dcb899c115f1e2a64bc9ebc24cc0e3ab097775755244f77360d1f3c06c"}, - {file = "pydantic_core-2.23.3-cp312-none-win_amd64.whl", hash = "sha256:40b8441be16c1e940abebed83cd006ddb9e3737a279e339dbd6d31578b802f7b"}, - {file = "pydantic_core-2.23.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6daaf5b1ba1369a22c8b050b643250e3e5efc6a78366d323294aee54953a4d5f"}, - {file = "pydantic_core-2.23.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d015e63b985a78a3d4ccffd3bdf22b7c20b3bbd4b8227809b3e8e75bc37f9cb2"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3fc572d9b5b5cfe13f8e8a6e26271d5d13f80173724b738557a8c7f3a8a3791"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f6bd91345b5163ee7448bee201ed7dd601ca24f43f439109b0212e296eb5b423"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc379c73fd66606628b866f661e8785088afe2adaba78e6bbe80796baf708a63"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbdce4b47592f9e296e19ac31667daed8753c8367ebb34b9a9bd89dacaa299c9"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3cf31edf405a161a0adad83246568647c54404739b614b1ff43dad2b02e6d5"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e22b477bf90db71c156f89a55bfe4d25177b81fce4aa09294d9e805eec13855"}, - {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0a0137ddf462575d9bce863c4c95bac3493ba8e22f8c28ca94634b4a1d3e2bb4"}, - {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:203171e48946c3164fe7691fc349c79241ff8f28306abd4cad5f4f75ed80bc8d"}, - {file = "pydantic_core-2.23.3-cp313-none-win32.whl", hash = "sha256:76bdab0de4acb3f119c2a4bff740e0c7dc2e6de7692774620f7452ce11ca76c8"}, - {file = "pydantic_core-2.23.3-cp313-none-win_amd64.whl", hash = "sha256:37ba321ac2a46100c578a92e9a6aa33afe9ec99ffa084424291d84e456f490c1"}, - {file = "pydantic_core-2.23.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d063c6b9fed7d992bcbebfc9133f4c24b7a7f215d6b102f3e082b1117cddb72c"}, - {file = "pydantic_core-2.23.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6cb968da9a0746a0cf521b2b5ef25fc5a0bee9b9a1a8214e0a1cfaea5be7e8a4"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edbefe079a520c5984e30e1f1f29325054b59534729c25b874a16a5048028d16"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbaaf2ef20d282659093913da9d402108203f7cb5955020bd8d1ae5a2325d1c4"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb539d7e5dc4aac345846f290cf504d2fd3c1be26ac4e8b5e4c2b688069ff4cf"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e6f33503c5495059148cc486867e1d24ca35df5fc064686e631e314d959ad5b"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04b07490bc2f6f2717b10c3969e1b830f5720b632f8ae2f3b8b1542394c47a8e"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03795b9e8a5d7fda05f3873efc3f59105e2dcff14231680296b87b80bb327295"}, - {file = "pydantic_core-2.23.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c483dab0f14b8d3f0df0c6c18d70b21b086f74c87ab03c59250dbf6d3c89baba"}, - {file = "pydantic_core-2.23.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b2682038e255e94baf2c473dca914a7460069171ff5cdd4080be18ab8a7fd6e"}, - {file = "pydantic_core-2.23.3-cp38-none-win32.whl", hash = "sha256:f4a57db8966b3a1d1a350012839c6a0099f0898c56512dfade8a1fe5fb278710"}, - {file = "pydantic_core-2.23.3-cp38-none-win_amd64.whl", hash = "sha256:13dd45ba2561603681a2676ca56006d6dee94493f03d5cadc055d2055615c3ea"}, - {file = "pydantic_core-2.23.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:82da2f4703894134a9f000e24965df73cc103e31e8c31906cc1ee89fde72cbd8"}, - {file = "pydantic_core-2.23.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dd9be0a42de08f4b58a3cc73a123f124f65c24698b95a54c1543065baca8cf0e"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89b731f25c80830c76fdb13705c68fef6a2b6dc494402987c7ea9584fe189f5d"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6de1ec30c4bb94f3a69c9f5f2182baeda5b809f806676675e9ef6b8dc936f28"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb68b41c3fa64587412b104294b9cbb027509dc2f6958446c502638d481525ef"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c3980f2843de5184656aab58698011b42763ccba11c4a8c35936c8dd6c7068c"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94f85614f2cba13f62c3c6481716e4adeae48e1eaa7e8bac379b9d177d93947a"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:510b7fb0a86dc8f10a8bb43bd2f97beb63cffad1203071dc434dac26453955cd"}, - {file = "pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1eba2f7ce3e30ee2170410e2171867ea73dbd692433b81a93758ab2de6c64835"}, - {file = "pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b259fd8409ab84b4041b7b3f24dcc41e4696f180b775961ca8142b5b21d0e70"}, - {file = "pydantic_core-2.23.3-cp39-none-win32.whl", hash = "sha256:40d9bd259538dba2f40963286009bf7caf18b5112b19d2b55b09c14dde6db6a7"}, - {file = "pydantic_core-2.23.3-cp39-none-win_amd64.whl", hash = "sha256:5a8cd3074a98ee70173a8633ad3c10e00dcb991ecec57263aacb4095c5efb958"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f399e8657c67313476a121a6944311fab377085ca7f490648c9af97fc732732d"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6b5547d098c76e1694ba85f05b595720d7c60d342f24d5aad32c3049131fa5c4"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0dda0290a6f608504882d9f7650975b4651ff91c85673341789a476b1159f211"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b6e5da855e9c55a0c67f4db8a492bf13d8d3316a59999cfbaf98cc6e401961"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:09e926397f392059ce0afdcac920df29d9c833256354d0c55f1584b0b70cf07e"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:87cfa0ed6b8c5bd6ae8b66de941cece179281239d482f363814d2b986b79cedc"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e61328920154b6a44d98cabcb709f10e8b74276bc709c9a513a8c37a18786cc4"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce3317d155628301d649fe5e16a99528d5680af4ec7aa70b90b8dacd2d725c9b"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e89513f014c6be0d17b00a9a7c81b1c426f4eb9224b15433f3d98c1a071f8433"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4f62c1c953d7ee375df5eb2e44ad50ce2f5aff931723b398b8bc6f0ac159791a"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2718443bc671c7ac331de4eef9b673063b10af32a0bb385019ad61dcf2cc8f6c"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d90e08b2727c5d01af1b5ef4121d2f0c99fbee692c762f4d9d0409c9da6541"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b676583fc459c64146debea14ba3af54e540b61762dfc0613dc4e98c3f66eeb"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:50e4661f3337977740fdbfbae084ae5693e505ca2b3130a6d4eb0f2281dc43b8"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:68f4cf373f0de6abfe599a38307f4417c1c867ca381c03df27c873a9069cda25"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:59d52cf01854cb26c46958552a21acb10dd78a52aa34c86f284e66b209db8cab"}, - {file = "pydantic_core-2.23.3.tar.gz", hash = "sha256:3cb0f65d8b4121c1b015c60104a685feb929a29d7cf204387c7f2688c7974690"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - -[[package]] -name = "pydantic-settings" -version = "2.5.2" -description = "Settings management using Pydantic" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic_settings-2.5.2-py3-none-any.whl", hash = "sha256:2c912e55fd5794a59bf8c832b9de832dcfdf4778d79ff79b708744eed499a907"}, - {file = "pydantic_settings-2.5.2.tar.gz", hash = "sha256:f90b139682bee4d2065273d5185d71d37ea46cfe57e1b5ae184fc6a0b2484ca0"}, -] - -[package.dependencies] -pydantic = ">=2.7.0" -python-dotenv = ">=0.21.0" - -[package.extras] -azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] -toml = ["tomli (>=2.0.1)"] -yaml = ["pyyaml (>=6.0.1)"] - -[[package]] -name = "pygments" -version = "2.18.0" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pymdown-extensions" -version = "10.9" -description = "Extension pack for Python Markdown." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pymdown_extensions-10.9-py3-none-any.whl", hash = "sha256:d323f7e90d83c86113ee78f3fe62fc9dee5f56b54d912660703ea1816fed5626"}, - {file = "pymdown_extensions-10.9.tar.gz", hash = "sha256:6ff740bcd99ec4172a938970d42b96128bdc9d4b9bcad72494f29921dc69b753"}, -] - -[package.dependencies] -markdown = ">=3.6" -pyyaml = "*" - -[package.extras] -extra = ["pygments (>=2.12)"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-dotenv" -version = "1.0.1" -description = "Read key-value pairs from a .env file and set them as environment variables" -optional = false -python-versions = ">=3.8" -files = [ - {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, - {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, -] - -[package.extras] -cli = ["click (>=5.0)"] - -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - -[[package]] -name = "pyyaml-env-tag" -version = "0.1" -description = "A custom YAML tag for referencing environment variables in YAML files. " -optional = false -python-versions = ">=3.6" -files = [ - {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, - {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, -] - -[package.dependencies] -pyyaml = "*" - -[[package]] -name = "regex" -version = "2024.9.11" -description = "Alternative regular expression module, to replace re." -optional = false -python-versions = ">=3.8" -files = [ - {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1494fa8725c285a81d01dc8c06b55287a1ee5e0e382d8413adc0a9197aac6408"}, - {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e12c481ad92d129c78f13a2a3662317e46ee7ef96c94fd332e1c29131875b7d"}, - {file = "regex-2024.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16e13a7929791ac1216afde26f712802e3df7bf0360b32e4914dca3ab8baeea5"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46989629904bad940bbec2106528140a218b4a36bb3042d8406980be1941429c"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a906ed5e47a0ce5f04b2c981af1c9acf9e8696066900bf03b9d7879a6f679fc8"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a091b0550b3b0207784a7d6d0f1a00d1d1c8a11699c1a4d93db3fbefc3ad35"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ddcd9a179c0a6fa8add279a4444015acddcd7f232a49071ae57fa6e278f1f71"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b41e1adc61fa347662b09398e31ad446afadff932a24807d3ceb955ed865cc8"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ced479f601cd2f8ca1fd7b23925a7e0ad512a56d6e9476f79b8f381d9d37090a"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:635a1d96665f84b292e401c3d62775851aedc31d4f8784117b3c68c4fcd4118d"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c0256beda696edcf7d97ef16b2a33a8e5a875affd6fa6567b54f7c577b30a137"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3ce4f1185db3fbde8ed8aa223fc9620f276c58de8b0d4f8cc86fd1360829edb6"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:09d77559e80dcc9d24570da3745ab859a9cf91953062e4ab126ba9d5993688ca"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a22ccefd4db3f12b526eccb129390942fe874a3a9fdbdd24cf55773a1faab1a"}, - {file = "regex-2024.9.11-cp310-cp310-win32.whl", hash = "sha256:f745ec09bc1b0bd15cfc73df6fa4f726dcc26bb16c23a03f9e3367d357eeedd0"}, - {file = "regex-2024.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:01c2acb51f8a7d6494c8c5eafe3d8e06d76563d8a8a4643b37e9b2dd8a2ff623"}, - {file = "regex-2024.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df"}, - {file = "regex-2024.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268"}, - {file = "regex-2024.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4cc92bb6db56ab0c1cbd17294e14f5e9224f0cc6521167ef388332604e92679"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d05ac6fa06959c4172eccd99a222e1fbf17b5670c4d596cb1e5cde99600674c4"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040562757795eeea356394a7fb13076ad4f99d3c62ab0f8bdfb21f99a1f85664"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e5fb5f77c8745a60105403a774fe2c1759b71d3e7b4ca237a5e67ad066c7199"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54d9ff35d4515debf14bc27f1e3b38bfc453eff3220f5bce159642fa762fe5d4"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df5cbb1fbc74a8305b6065d4ade43b993be03dbe0f8b30032cced0d7740994bd"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7fb89ee5d106e4a7a51bce305ac4efb981536301895f7bdcf93ec92ae0d91c7f"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a738b937d512b30bf75995c0159c0ddf9eec0775c9d72ac0202076c72f24aa96"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e28f9faeb14b6f23ac55bfbbfd3643f5c7c18ede093977f1df249f73fd22c7b1"}, - {file = "regex-2024.9.11-cp311-cp311-win32.whl", hash = "sha256:18e707ce6c92d7282dfce370cd205098384b8ee21544e7cb29b8aab955b66fa9"}, - {file = "regex-2024.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:313ea15e5ff2a8cbbad96ccef6be638393041b0a7863183c2d31e0c6116688cf"}, - {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7"}, - {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231"}, - {file = "regex-2024.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a"}, - {file = "regex-2024.9.11-cp312-cp312-win32.whl", hash = "sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776"}, - {file = "regex-2024.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009"}, - {file = "regex-2024.9.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784"}, - {file = "regex-2024.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36"}, - {file = "regex-2024.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8"}, - {file = "regex-2024.9.11-cp313-cp313-win32.whl", hash = "sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8"}, - {file = "regex-2024.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f"}, - {file = "regex-2024.9.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:35f4a6f96aa6cb3f2f7247027b07b15a374f0d5b912c0001418d1d55024d5cb4"}, - {file = "regex-2024.9.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:55b96e7ce3a69a8449a66984c268062fbaa0d8ae437b285428e12797baefce7e"}, - {file = "regex-2024.9.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb130fccd1a37ed894824b8c046321540263013da72745d755f2d35114b81a60"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:323c1f04be6b2968944d730e5c2091c8c89767903ecaa135203eec4565ed2b2b"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be1c8ed48c4c4065ecb19d882a0ce1afe0745dfad8ce48c49586b90a55f02366"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5b029322e6e7b94fff16cd120ab35a253236a5f99a79fb04fda7ae71ca20ae8"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6fff13ef6b5f29221d6904aa816c34701462956aa72a77f1f151a8ec4f56aeb"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:587d4af3979376652010e400accc30404e6c16b7df574048ab1f581af82065e4"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:079400a8269544b955ffa9e31f186f01d96829110a3bf79dc338e9910f794fca"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f9268774428ec173654985ce55fc6caf4c6d11ade0f6f914d48ef4719eb05ebb"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:23f9985c8784e544d53fc2930fc1ac1a7319f5d5332d228437acc9f418f2f168"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2941333154baff9838e88aa71c1d84f4438189ecc6021a12c7573728b5838e"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e93f1c331ca8e86fe877a48ad64e77882c0c4da0097f2212873a69bbfea95d0c"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:846bc79ee753acf93aef4184c040d709940c9d001029ceb7b7a52747b80ed2dd"}, - {file = "regex-2024.9.11-cp38-cp38-win32.whl", hash = "sha256:c94bb0a9f1db10a1d16c00880bdebd5f9faf267273b8f5bd1878126e0fbde771"}, - {file = "regex-2024.9.11-cp38-cp38-win_amd64.whl", hash = "sha256:2b08fce89fbd45664d3df6ad93e554b6c16933ffa9d55cb7e01182baaf971508"}, - {file = "regex-2024.9.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:07f45f287469039ffc2c53caf6803cd506eb5f5f637f1d4acb37a738f71dd066"}, - {file = "regex-2024.9.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4838e24ee015101d9f901988001038f7f0d90dc0c3b115541a1365fb439add62"}, - {file = "regex-2024.9.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6edd623bae6a737f10ce853ea076f56f507fd7726bee96a41ee3d68d347e4d16"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c69ada171c2d0e97a4b5aa78fbb835e0ffbb6b13fc5da968c09811346564f0d3"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02087ea0a03b4af1ed6ebab2c54d7118127fee8d71b26398e8e4b05b78963199"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69dee6a020693d12a3cf892aba4808fe168d2a4cef368eb9bf74f5398bfd4ee8"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297f54910247508e6e5cae669f2bc308985c60540a4edd1c77203ef19bfa63ca"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecea58b43a67b1b79805f1a0255730edaf5191ecef84dbc4cc85eb30bc8b63b9"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eab4bb380f15e189d1313195b062a6aa908f5bd687a0ceccd47c8211e9cf0d4a"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0cbff728659ce4bbf4c30b2a1be040faafaa9eca6ecde40aaff86f7889f4ab39"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:54c4a097b8bc5bb0dfc83ae498061d53ad7b5762e00f4adaa23bee22b012e6ba"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:73d6d2f64f4d894c96626a75578b0bf7d9e56dcda8c3d037a2118fdfe9b1c664"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:e53b5fbab5d675aec9f0c501274c467c0f9a5d23696cfc94247e1fb56501ed89"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ffbcf9221e04502fc35e54d1ce9567541979c3fdfb93d2c554f0ca583a19b35"}, - {file = "regex-2024.9.11-cp39-cp39-win32.whl", hash = "sha256:e4c22e1ac1f1ec1e09f72e6c44d8f2244173db7eb9629cc3a346a8d7ccc31142"}, - {file = "regex-2024.9.11-cp39-cp39-win_amd64.whl", hash = "sha256:faa3c142464efec496967359ca99696c896c591c56c53506bac1ad465f66e919"}, - {file = "regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd"}, -] - -[[package]] -name = "requests" -version = "2.32.3" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "rich" -version = "13.8.1" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "rich-13.8.1-py3-none-any.whl", hash = "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06"}, - {file = "rich-13.8.1.tar.gz", hash = "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - -[[package]] -name = "sqlalchemy" -version = "2.0.34" -description = "Database Abstraction Library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "SQLAlchemy-2.0.34-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:95d0b2cf8791ab5fb9e3aa3d9a79a0d5d51f55b6357eecf532a120ba3b5524db"}, - {file = "SQLAlchemy-2.0.34-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:243f92596f4fd4c8bd30ab8e8dd5965afe226363d75cab2468f2c707f64cd83b"}, - {file = "SQLAlchemy-2.0.34-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ea54f7300553af0a2a7235e9b85f4204e1fc21848f917a3213b0e0818de9a24"}, - {file = "SQLAlchemy-2.0.34-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:173f5f122d2e1bff8fbd9f7811b7942bead1f5e9f371cdf9e670b327e6703ebd"}, - {file = "SQLAlchemy-2.0.34-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:196958cde924a00488e3e83ff917be3b73cd4ed8352bbc0f2989333176d1c54d"}, - {file = "SQLAlchemy-2.0.34-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bd90c221ed4e60ac9d476db967f436cfcecbd4ef744537c0f2d5291439848768"}, - {file = "SQLAlchemy-2.0.34-cp310-cp310-win32.whl", hash = "sha256:3166dfff2d16fe9be3241ee60ece6fcb01cf8e74dd7c5e0b64f8e19fab44911b"}, - {file = "SQLAlchemy-2.0.34-cp310-cp310-win_amd64.whl", hash = "sha256:6831a78bbd3c40f909b3e5233f87341f12d0b34a58f14115c9e94b4cdaf726d3"}, - {file = "SQLAlchemy-2.0.34-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7db3db284a0edaebe87f8f6642c2b2c27ed85c3e70064b84d1c9e4ec06d5d84"}, - {file = "SQLAlchemy-2.0.34-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:430093fce0efc7941d911d34f75a70084f12f6ca5c15d19595c18753edb7c33b"}, - {file = "SQLAlchemy-2.0.34-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79cb400c360c7c210097b147c16a9e4c14688a6402445ac848f296ade6283bbc"}, - {file = "SQLAlchemy-2.0.34-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb1b30f31a36c7f3fee848391ff77eebdd3af5750bf95fbf9b8b5323edfdb4ec"}, - {file = "SQLAlchemy-2.0.34-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fddde2368e777ea2a4891a3fb4341e910a056be0bb15303bf1b92f073b80c02"}, - {file = "SQLAlchemy-2.0.34-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80bd73ea335203b125cf1d8e50fef06be709619eb6ab9e7b891ea34b5baa2287"}, - {file = "SQLAlchemy-2.0.34-cp311-cp311-win32.whl", hash = "sha256:6daeb8382d0df526372abd9cb795c992e18eed25ef2c43afe518c73f8cccb721"}, - {file = "SQLAlchemy-2.0.34-cp311-cp311-win_amd64.whl", hash = "sha256:5bc08e75ed11693ecb648b7a0a4ed80da6d10845e44be0c98c03f2f880b68ff4"}, - {file = "SQLAlchemy-2.0.34-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:53e68b091492c8ed2bd0141e00ad3089bcc6bf0e6ec4142ad6505b4afe64163e"}, - {file = "SQLAlchemy-2.0.34-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bcd18441a49499bf5528deaa9dee1f5c01ca491fc2791b13604e8f972877f812"}, - {file = "SQLAlchemy-2.0.34-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:165bbe0b376541092bf49542bd9827b048357f4623486096fc9aaa6d4e7c59a2"}, - {file = "SQLAlchemy-2.0.34-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3330415cd387d2b88600e8e26b510d0370db9b7eaf984354a43e19c40df2e2b"}, - {file = "SQLAlchemy-2.0.34-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97b850f73f8abbffb66ccbab6e55a195a0eb655e5dc74624d15cff4bfb35bd74"}, - {file = "SQLAlchemy-2.0.34-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee4c6917857fd6121ed84f56d1dc78eb1d0e87f845ab5a568aba73e78adf83"}, - {file = "SQLAlchemy-2.0.34-cp312-cp312-win32.whl", hash = "sha256:fbb034f565ecbe6c530dff948239377ba859420d146d5f62f0271407ffb8c580"}, - {file = "SQLAlchemy-2.0.34-cp312-cp312-win_amd64.whl", hash = "sha256:707c8f44931a4facd4149b52b75b80544a8d824162602b8cd2fe788207307f9a"}, - {file = "SQLAlchemy-2.0.34-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:24af3dc43568f3780b7e1e57c49b41d98b2d940c1fd2e62d65d3928b6f95f021"}, - {file = "SQLAlchemy-2.0.34-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e60ed6ef0a35c6b76b7640fe452d0e47acc832ccbb8475de549a5cc5f90c2c06"}, - {file = "SQLAlchemy-2.0.34-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:413c85cd0177c23e32dee6898c67a5f49296640041d98fddb2c40888fe4daa2e"}, - {file = "SQLAlchemy-2.0.34-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:25691f4adfb9d5e796fd48bf1432272f95f4bbe5f89c475a788f31232ea6afba"}, - {file = "SQLAlchemy-2.0.34-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:526ce723265643dbc4c7efb54f56648cc30e7abe20f387d763364b3ce7506c82"}, - {file = "SQLAlchemy-2.0.34-cp37-cp37m-win32.whl", hash = "sha256:13be2cc683b76977a700948411a94c67ad8faf542fa7da2a4b167f2244781cf3"}, - {file = "SQLAlchemy-2.0.34-cp37-cp37m-win_amd64.whl", hash = "sha256:e54ef33ea80d464c3dcfe881eb00ad5921b60f8115ea1a30d781653edc2fd6a2"}, - {file = "SQLAlchemy-2.0.34-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:43f28005141165edd11fbbf1541c920bd29e167b8bbc1fb410d4fe2269c1667a"}, - {file = "SQLAlchemy-2.0.34-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b68094b165a9e930aedef90725a8fcfafe9ef95370cbb54abc0464062dbf808f"}, - {file = "SQLAlchemy-2.0.34-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a1e03db964e9d32f112bae36f0cc1dcd1988d096cfd75d6a588a3c3def9ab2b"}, - {file = "SQLAlchemy-2.0.34-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:203d46bddeaa7982f9c3cc693e5bc93db476ab5de9d4b4640d5c99ff219bee8c"}, - {file = "SQLAlchemy-2.0.34-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ae92bebca3b1e6bd203494e5ef919a60fb6dfe4d9a47ed2453211d3bd451b9f5"}, - {file = "SQLAlchemy-2.0.34-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9661268415f450c95f72f0ac1217cc6f10256f860eed85c2ae32e75b60278ad8"}, - {file = "SQLAlchemy-2.0.34-cp38-cp38-win32.whl", hash = "sha256:895184dfef8708e15f7516bd930bda7e50ead069280d2ce09ba11781b630a434"}, - {file = "SQLAlchemy-2.0.34-cp38-cp38-win_amd64.whl", hash = "sha256:6e7cde3a2221aa89247944cafb1b26616380e30c63e37ed19ff0bba5e968688d"}, - {file = "SQLAlchemy-2.0.34-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dbcdf987f3aceef9763b6d7b1fd3e4ee210ddd26cac421d78b3c206d07b2700b"}, - {file = "SQLAlchemy-2.0.34-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ce119fc4ce0d64124d37f66a6f2a584fddc3c5001755f8a49f1ca0a177ef9796"}, - {file = "SQLAlchemy-2.0.34-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a17d8fac6df9835d8e2b4c5523666e7051d0897a93756518a1fe101c7f47f2f0"}, - {file = "SQLAlchemy-2.0.34-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ebc11c54c6ecdd07bb4efbfa1554538982f5432dfb8456958b6d46b9f834bb7"}, - {file = "SQLAlchemy-2.0.34-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2e6965346fc1491a566e019a4a1d3dfc081ce7ac1a736536367ca305da6472a8"}, - {file = "SQLAlchemy-2.0.34-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:220574e78ad986aea8e81ac68821e47ea9202b7e44f251b7ed8c66d9ae3f4278"}, - {file = "SQLAlchemy-2.0.34-cp39-cp39-win32.whl", hash = "sha256:b75b00083e7fe6621ce13cfce9d4469c4774e55e8e9d38c305b37f13cf1e874c"}, - {file = "SQLAlchemy-2.0.34-cp39-cp39-win_amd64.whl", hash = "sha256:c29d03e0adf3cc1a8c3ec62d176824972ae29b67a66cbb18daff3062acc6faa8"}, - {file = "SQLAlchemy-2.0.34-py3-none-any.whl", hash = "sha256:7286c353ee6475613d8beff83167374006c6b3e3f0e6491bfe8ca610eb1dec0f"}, - {file = "sqlalchemy-2.0.34.tar.gz", hash = "sha256:10d8f36990dd929690666679b0f42235c159a7051534adb135728ee52828dd22"}, -] - -[package.dependencies] -greenlet = {version = "!=0.4.17", optional = true, markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") or extra == \"asyncio\""} -typing-extensions = ">=4.6.0" - -[package.extras] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=8)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3_binary"] - -[[package]] -name = "sqlmodel" -version = "0.0.20" -description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." -optional = false -python-versions = ">=3.7" -files = [ - {file = "sqlmodel-0.0.20-py3-none-any.whl", hash = "sha256:744756c49e24095808984754cc4d3a32c2d8361fef803c4914fadcb912239bc9"}, - {file = "sqlmodel-0.0.20.tar.gz", hash = "sha256:94dd1f63e4ceb0ab405e304e1ad3e8b8c8800b47c3ca5f68736807be8e5b9314"}, -] - -[package.dependencies] -pydantic = ">=1.10.13,<3.0.0" -SQLAlchemy = ">=2.0.14,<2.1.0" - -[[package]] -name = "starlette" -version = "0.36.3" -description = "The little ASGI library that shines." -optional = false -python-versions = ">=3.8" -files = [ - {file = "starlette-0.36.3-py3-none-any.whl", hash = "sha256:13d429aa93a61dc40bf503e8c801db1f1bca3dc706b10ef2434a36123568f044"}, - {file = "starlette-0.36.3.tar.gz", hash = "sha256:90a671733cfb35771d8cc605e0b679d23b992f8dcfad48cc60b38cb29aeb7080"}, -] - -[package.dependencies] -anyio = ">=3.4.0,<5" - -[package.extras] -full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - -[[package]] -name = "urllib3" -version = "2.2.3" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "uvicorn" -version = "0.27.1" -description = "The lightning-fast ASGI server." -optional = false -python-versions = ">=3.8" -files = [ - {file = "uvicorn-0.27.1-py3-none-any.whl", hash = "sha256:5c89da2f3895767472a35556e539fd59f7edbe9b1e9c0e1c99eebeadc61838e4"}, - {file = "uvicorn-0.27.1.tar.gz", hash = "sha256:3d9a267296243532db80c83a959a3400502165ade2c1338dea4e67915fd4745a"}, -] - -[package.dependencies] -click = ">=7.0" -colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} -h11 = ">=0.8" -httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} -python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} -pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} -uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} -watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} -websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} - -[package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] - -[[package]] -name = "uvloop" -version = "0.20.0" -description = "Fast implementation of asyncio event loop on top of libuv" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "uvloop-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9ebafa0b96c62881d5cafa02d9da2e44c23f9f0cd829f3a32a6aff771449c996"}, - {file = "uvloop-0.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:35968fc697b0527a06e134999eef859b4034b37aebca537daeb598b9d45a137b"}, - {file = "uvloop-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b16696f10e59d7580979b420eedf6650010a4a9c3bd8113f24a103dfdb770b10"}, - {file = "uvloop-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b04d96188d365151d1af41fa2d23257b674e7ead68cfd61c725a422764062ae"}, - {file = "uvloop-0.20.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:94707205efbe809dfa3a0d09c08bef1352f5d3d6612a506f10a319933757c006"}, - {file = "uvloop-0.20.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:89e8d33bb88d7263f74dc57d69f0063e06b5a5ce50bb9a6b32f5fcbe655f9e73"}, - {file = "uvloop-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e50289c101495e0d1bb0bfcb4a60adde56e32f4449a67216a1ab2750aa84f037"}, - {file = "uvloop-0.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e237f9c1e8a00e7d9ddaa288e535dc337a39bcbf679f290aee9d26df9e72bce9"}, - {file = "uvloop-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:746242cd703dc2b37f9d8b9f173749c15e9a918ddb021575a0205ec29a38d31e"}, - {file = "uvloop-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82edbfd3df39fb3d108fc079ebc461330f7c2e33dbd002d146bf7c445ba6e756"}, - {file = "uvloop-0.20.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:80dc1b139516be2077b3e57ce1cb65bfed09149e1d175e0478e7a987863b68f0"}, - {file = "uvloop-0.20.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4f44af67bf39af25db4c1ac27e82e9665717f9c26af2369c404be865c8818dcf"}, - {file = "uvloop-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4b75f2950ddb6feed85336412b9a0c310a2edbcf4cf931aa5cfe29034829676d"}, - {file = "uvloop-0.20.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:77fbc69c287596880ecec2d4c7a62346bef08b6209749bf6ce8c22bbaca0239e"}, - {file = "uvloop-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6462c95f48e2d8d4c993a2950cd3d31ab061864d1c226bbf0ee2f1a8f36674b9"}, - {file = "uvloop-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:649c33034979273fa71aa25d0fe120ad1777c551d8c4cd2c0c9851d88fcb13ab"}, - {file = "uvloop-0.20.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a609780e942d43a275a617c0839d85f95c334bad29c4c0918252085113285b5"}, - {file = "uvloop-0.20.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aea15c78e0d9ad6555ed201344ae36db5c63d428818b4b2a42842b3870127c00"}, - {file = "uvloop-0.20.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0e94b221295b5e69de57a1bd4aeb0b3a29f61be6e1b478bb8a69a73377db7ba"}, - {file = "uvloop-0.20.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fee6044b64c965c425b65a4e17719953b96e065c5b7e09b599ff332bb2744bdf"}, - {file = "uvloop-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:265a99a2ff41a0fd56c19c3838b29bf54d1d177964c300dad388b27e84fd7847"}, - {file = "uvloop-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b10c2956efcecb981bf9cfb8184d27d5d64b9033f917115a960b83f11bfa0d6b"}, - {file = "uvloop-0.20.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e7d61fe8e8d9335fac1bf8d5d82820b4808dd7a43020c149b63a1ada953d48a6"}, - {file = "uvloop-0.20.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2beee18efd33fa6fdb0976e18475a4042cd31c7433c866e8a09ab604c7c22ff2"}, - {file = "uvloop-0.20.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8c36fdf3e02cec92aed2d44f63565ad1522a499c654f07935c8f9d04db69e95"}, - {file = "uvloop-0.20.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0fac7be202596c7126146660725157d4813aa29a4cc990fe51346f75ff8fde7"}, - {file = "uvloop-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d0fba61846f294bce41eb44d60d58136090ea2b5b99efd21cbdf4e21927c56a"}, - {file = "uvloop-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95720bae002ac357202e0d866128eb1ac82545bcf0b549b9abe91b5178d9b541"}, - {file = "uvloop-0.20.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:36c530d8fa03bfa7085af54a48f2ca16ab74df3ec7108a46ba82fd8b411a2315"}, - {file = "uvloop-0.20.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e97152983442b499d7a71e44f29baa75b3b02e65d9c44ba53b10338e98dedb66"}, - {file = "uvloop-0.20.0.tar.gz", hash = "sha256:4603ca714a754fc8d9b197e325db25b2ea045385e8a3ad05d3463de725fdf469"}, -] - -[package.extras] -docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] - -[[package]] -name = "watchdog" -version = "3.0.0" -description = "Filesystem events monitoring" -optional = false -python-versions = ">=3.7" -files = [ - {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41"}, - {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397"}, - {file = "watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96"}, - {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae"}, - {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9"}, - {file = "watchdog-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7"}, - {file = "watchdog-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9fac43a7466eb73e64a9940ac9ed6369baa39b3bf221ae23493a9ec4d0022674"}, - {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8ae9cda41fa114e28faf86cb137d751a17ffd0316d1c34ccf2235e8a84365c7f"}, - {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f70b4aa53bd743729c7475d7ec41093a580528b100e9a8c5b5efe8899592fc"}, - {file = "watchdog-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4f94069eb16657d2c6faada4624c39464f65c05606af50bb7902e036e3219be3"}, - {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c5f84b5194c24dd573fa6472685b2a27cc5a17fe5f7b6fd40345378ca6812e3"}, - {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa7f6a12e831ddfe78cdd4f8996af9cf334fd6346531b16cec61c3b3c0d8da0"}, - {file = "watchdog-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:233b5817932685d39a7896b1090353fc8efc1ef99c9c054e46c8002561252fb8"}, - {file = "watchdog-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100"}, - {file = "watchdog-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346"}, - {file = "watchdog-3.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33"}, - {file = "watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f"}, - {file = "watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c"}, - {file = "watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759"}, - {file = "watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9"}, -] - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - -[[package]] -name = "watchfiles" -version = "0.24.0" -description = "Simple, modern and high performance file watching and code reload in python." -optional = false -python-versions = ">=3.8" -files = [ - {file = "watchfiles-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:083dc77dbdeef09fa44bb0f4d1df571d2e12d8a8f985dccde71ac3ac9ac067a0"}, - {file = "watchfiles-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e94e98c7cb94cfa6e071d401ea3342767f28eb5a06a58fafdc0d2a4974f4f35c"}, - {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82ae557a8c037c42a6ef26c494d0631cacca040934b101d001100ed93d43f361"}, - {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:acbfa31e315a8f14fe33e3542cbcafc55703b8f5dcbb7c1eecd30f141df50db3"}, - {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b74fdffce9dfcf2dc296dec8743e5b0332d15df19ae464f0e249aa871fc1c571"}, - {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:449f43f49c8ddca87c6b3980c9284cab6bd1f5c9d9a2b00012adaaccd5e7decd"}, - {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4abf4ad269856618f82dee296ac66b0cd1d71450fc3c98532d93798e73399b7a"}, - {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f895d785eb6164678ff4bb5cc60c5996b3ee6df3edb28dcdeba86a13ea0465e"}, - {file = "watchfiles-0.24.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7ae3e208b31be8ce7f4c2c0034f33406dd24fbce3467f77223d10cd86778471c"}, - {file = "watchfiles-0.24.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2efec17819b0046dde35d13fb8ac7a3ad877af41ae4640f4109d9154ed30a188"}, - {file = "watchfiles-0.24.0-cp310-none-win32.whl", hash = "sha256:6bdcfa3cd6fdbdd1a068a52820f46a815401cbc2cb187dd006cb076675e7b735"}, - {file = "watchfiles-0.24.0-cp310-none-win_amd64.whl", hash = "sha256:54ca90a9ae6597ae6dc00e7ed0a040ef723f84ec517d3e7ce13e63e4bc82fa04"}, - {file = "watchfiles-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:bdcd5538e27f188dd3c804b4a8d5f52a7fc7f87e7fd6b374b8e36a4ca03db428"}, - {file = "watchfiles-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2dadf8a8014fde6addfd3c379e6ed1a981c8f0a48292d662e27cabfe4239c83c"}, - {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6509ed3f467b79d95fc62a98229f79b1a60d1b93f101e1c61d10c95a46a84f43"}, - {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8360f7314a070c30e4c976b183d1d8d1585a4a50c5cb603f431cebcbb4f66327"}, - {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:316449aefacf40147a9efaf3bd7c9bdd35aaba9ac5d708bd1eb5763c9a02bef5"}, - {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73bde715f940bea845a95247ea3e5eb17769ba1010efdc938ffcb967c634fa61"}, - {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3770e260b18e7f4e576edca4c0a639f704088602e0bc921c5c2e721e3acb8d15"}, - {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa0fd7248cf533c259e59dc593a60973a73e881162b1a2f73360547132742823"}, - {file = "watchfiles-0.24.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d7a2e3b7f5703ffbd500dabdefcbc9eafeff4b9444bbdd5d83d79eedf8428fab"}, - {file = "watchfiles-0.24.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d831ee0a50946d24a53821819b2327d5751b0c938b12c0653ea5be7dea9c82ec"}, - {file = "watchfiles-0.24.0-cp311-none-win32.whl", hash = "sha256:49d617df841a63b4445790a254013aea2120357ccacbed00253f9c2b5dc24e2d"}, - {file = "watchfiles-0.24.0-cp311-none-win_amd64.whl", hash = "sha256:d3dcb774e3568477275cc76554b5a565024b8ba3a0322f77c246bc7111c5bb9c"}, - {file = "watchfiles-0.24.0-cp311-none-win_arm64.whl", hash = "sha256:9301c689051a4857d5b10777da23fafb8e8e921bcf3abe6448a058d27fb67633"}, - {file = "watchfiles-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7211b463695d1e995ca3feb38b69227e46dbd03947172585ecb0588f19b0d87a"}, - {file = "watchfiles-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b8693502d1967b00f2fb82fc1e744df128ba22f530e15b763c8d82baee15370"}, - {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdab9555053399318b953a1fe1f586e945bc8d635ce9d05e617fd9fe3a4687d6"}, - {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34e19e56d68b0dad5cff62273107cf5d9fbaf9d75c46277aa5d803b3ef8a9e9b"}, - {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41face41f036fee09eba33a5b53a73e9a43d5cb2c53dad8e61fa6c9f91b5a51e"}, - {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5148c2f1ea043db13ce9b0c28456e18ecc8f14f41325aa624314095b6aa2e9ea"}, - {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e4bd963a935aaf40b625c2499f3f4f6bbd0c3776f6d3bc7c853d04824ff1c9f"}, - {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c79d7719d027b7a42817c5d96461a99b6a49979c143839fc37aa5748c322f234"}, - {file = "watchfiles-0.24.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:32aa53a9a63b7f01ed32e316e354e81e9da0e6267435c7243bf8ae0f10b428ef"}, - {file = "watchfiles-0.24.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce72dba6a20e39a0c628258b5c308779b8697f7676c254a845715e2a1039b968"}, - {file = "watchfiles-0.24.0-cp312-none-win32.whl", hash = "sha256:d9018153cf57fc302a2a34cb7564870b859ed9a732d16b41a9b5cb2ebed2d444"}, - {file = "watchfiles-0.24.0-cp312-none-win_amd64.whl", hash = "sha256:551ec3ee2a3ac9cbcf48a4ec76e42c2ef938a7e905a35b42a1267fa4b1645896"}, - {file = "watchfiles-0.24.0-cp312-none-win_arm64.whl", hash = "sha256:b52a65e4ea43c6d149c5f8ddb0bef8d4a1e779b77591a458a893eb416624a418"}, - {file = "watchfiles-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2e3ab79a1771c530233cadfd277fcc762656d50836c77abb2e5e72b88e3a48"}, - {file = "watchfiles-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:327763da824817b38ad125dcd97595f942d720d32d879f6c4ddf843e3da3fe90"}, - {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd82010f8ab451dabe36054a1622870166a67cf3fce894f68895db6f74bbdc94"}, - {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d64ba08db72e5dfd5c33be1e1e687d5e4fcce09219e8aee893a4862034081d4e"}, - {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1cf1f6dd7825053f3d98f6d33f6464ebdd9ee95acd74ba2c34e183086900a827"}, - {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43e3e37c15a8b6fe00c1bce2473cfa8eb3484bbeecf3aefbf259227e487a03df"}, - {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88bcd4d0fe1d8ff43675360a72def210ebad3f3f72cabfeac08d825d2639b4ab"}, - {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:999928c6434372fde16c8f27143d3e97201160b48a614071261701615a2a156f"}, - {file = "watchfiles-0.24.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:30bbd525c3262fd9f4b1865cb8d88e21161366561cd7c9e1194819e0a33ea86b"}, - {file = "watchfiles-0.24.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:edf71b01dec9f766fb285b73930f95f730bb0943500ba0566ae234b5c1618c18"}, - {file = "watchfiles-0.24.0-cp313-none-win32.whl", hash = "sha256:f4c96283fca3ee09fb044f02156d9570d156698bc3734252175a38f0e8975f07"}, - {file = "watchfiles-0.24.0-cp313-none-win_amd64.whl", hash = "sha256:a974231b4fdd1bb7f62064a0565a6b107d27d21d9acb50c484d2cdba515b9366"}, - {file = "watchfiles-0.24.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ee82c98bed9d97cd2f53bdb035e619309a098ea53ce525833e26b93f673bc318"}, - {file = "watchfiles-0.24.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fd92bbaa2ecdb7864b7600dcdb6f2f1db6e0346ed425fbd01085be04c63f0b05"}, - {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f83df90191d67af5a831da3a33dd7628b02a95450e168785586ed51e6d28943c"}, - {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fca9433a45f18b7c779d2bae7beeec4f740d28b788b117a48368d95a3233ed83"}, - {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b995bfa6bf01a9e09b884077a6d37070464b529d8682d7691c2d3b540d357a0c"}, - {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed9aba6e01ff6f2e8285e5aa4154e2970068fe0fc0998c4380d0e6278222269b"}, - {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5171ef898299c657685306d8e1478a45e9303ddcd8ac5fed5bd52ad4ae0b69b"}, - {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4933a508d2f78099162da473841c652ad0de892719043d3f07cc83b33dfd9d91"}, - {file = "watchfiles-0.24.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95cf3b95ea665ab03f5a54765fa41abf0529dbaf372c3b83d91ad2cfa695779b"}, - {file = "watchfiles-0.24.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:01def80eb62bd5db99a798d5e1f5f940ca0a05986dcfae21d833af7a46f7ee22"}, - {file = "watchfiles-0.24.0-cp38-none-win32.whl", hash = "sha256:4d28cea3c976499475f5b7a2fec6b3a36208656963c1a856d328aeae056fc5c1"}, - {file = "watchfiles-0.24.0-cp38-none-win_amd64.whl", hash = "sha256:21ab23fdc1208086d99ad3f69c231ba265628014d4aed31d4e8746bd59e88cd1"}, - {file = "watchfiles-0.24.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b665caeeda58625c3946ad7308fbd88a086ee51ccb706307e5b1fa91556ac886"}, - {file = "watchfiles-0.24.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5c51749f3e4e269231510da426ce4a44beb98db2dce9097225c338f815b05d4f"}, - {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82b2509f08761f29a0fdad35f7e1638b8ab1adfa2666d41b794090361fb8b855"}, - {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a60e2bf9dc6afe7f743e7c9b149d1fdd6dbf35153c78fe3a14ae1a9aee3d98b"}, - {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7d9b87c4c55e3ea8881dfcbf6d61ea6775fffed1fedffaa60bd047d3c08c430"}, - {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:78470906a6be5199524641f538bd2c56bb809cd4bf29a566a75051610bc982c3"}, - {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07cdef0c84c03375f4e24642ef8d8178e533596b229d32d2bbd69e5128ede02a"}, - {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d337193bbf3e45171c8025e291530fb7548a93c45253897cd764a6a71c937ed9"}, - {file = "watchfiles-0.24.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ec39698c45b11d9694a1b635a70946a5bad066b593af863460a8e600f0dff1ca"}, - {file = "watchfiles-0.24.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e28d91ef48eab0afb939fa446d8ebe77e2f7593f5f463fd2bb2b14132f95b6e"}, - {file = "watchfiles-0.24.0-cp39-none-win32.whl", hash = "sha256:7138eff8baa883aeaa074359daabb8b6c1e73ffe69d5accdc907d62e50b1c0da"}, - {file = "watchfiles-0.24.0-cp39-none-win_amd64.whl", hash = "sha256:b3ef2c69c655db63deb96b3c3e587084612f9b1fa983df5e0c3379d41307467f"}, - {file = "watchfiles-0.24.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:632676574429bee8c26be8af52af20e0c718cc7f5f67f3fb658c71928ccd4f7f"}, - {file = "watchfiles-0.24.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a2a9891723a735d3e2540651184be6fd5b96880c08ffe1a98bae5017e65b544b"}, - {file = "watchfiles-0.24.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7fa2bc0efef3e209a8199fd111b8969fe9db9c711acc46636686331eda7dd4"}, - {file = "watchfiles-0.24.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01550ccf1d0aed6ea375ef259706af76ad009ef5b0203a3a4cce0f6024f9b68a"}, - {file = "watchfiles-0.24.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:96619302d4374de5e2345b2b622dc481257a99431277662c30f606f3e22f42be"}, - {file = "watchfiles-0.24.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:85d5f0c7771dcc7a26c7a27145059b6bb0ce06e4e751ed76cdf123d7039b60b5"}, - {file = "watchfiles-0.24.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951088d12d339690a92cef2ec5d3cfd957692834c72ffd570ea76a6790222777"}, - {file = "watchfiles-0.24.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49fb58bcaa343fedc6a9e91f90195b20ccb3135447dc9e4e2570c3a39565853e"}, - {file = "watchfiles-0.24.0.tar.gz", hash = "sha256:afb72325b74fa7a428c009c1b8be4b4d7c2afedafb2982827ef2156646df2fe1"}, -] - -[package.dependencies] -anyio = ">=3.0.0" - -[[package]] -name = "websockets" -version = "13.0.1" -description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "websockets-13.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1841c9082a3ba4a05ea824cf6d99570a6a2d8849ef0db16e9c826acb28089e8f"}, - {file = "websockets-13.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c5870b4a11b77e4caa3937142b650fbbc0914a3e07a0cf3131f35c0587489c1c"}, - {file = "websockets-13.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f1d3d1f2eb79fe7b0fb02e599b2bf76a7619c79300fc55f0b5e2d382881d4f7f"}, - {file = "websockets-13.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15c7d62ee071fa94a2fc52c2b472fed4af258d43f9030479d9c4a2de885fd543"}, - {file = "websockets-13.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6724b554b70d6195ba19650fef5759ef11346f946c07dbbe390e039bcaa7cc3d"}, - {file = "websockets-13.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a952fa2ae57a42ba7951e6b2605e08a24801a4931b5644dfc68939e041bc7f"}, - {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:17118647c0ea14796364299e942c330d72acc4b248e07e639d34b75067b3cdd8"}, - {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64a11aae1de4c178fa653b07d90f2fb1a2ed31919a5ea2361a38760192e1858b"}, - {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0617fd0b1d14309c7eab6ba5deae8a7179959861846cbc5cb528a7531c249448"}, - {file = "websockets-13.0.1-cp310-cp310-win32.whl", hash = "sha256:11f9976ecbc530248cf162e359a92f37b7b282de88d1d194f2167b5e7ad80ce3"}, - {file = "websockets-13.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:c3c493d0e5141ec055a7d6809a28ac2b88d5b878bb22df8c621ebe79a61123d0"}, - {file = "websockets-13.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:699ba9dd6a926f82a277063603fc8d586b89f4cb128efc353b749b641fcddda7"}, - {file = "websockets-13.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf2fae6d85e5dc384bf846f8243ddaa9197f3a1a70044f59399af001fd1f51d4"}, - {file = "websockets-13.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:52aed6ef21a0f1a2a5e310fb5c42d7555e9c5855476bbd7173c3aa3d8a0302f2"}, - {file = "websockets-13.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8eb2b9a318542153674c6e377eb8cb9ca0fc011c04475110d3477862f15d29f0"}, - {file = "websockets-13.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5df891c86fe68b2c38da55b7aea7095beca105933c697d719f3f45f4220a5e0e"}, - {file = "websockets-13.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac2d146ff30d9dd2fcf917e5d147db037a5c573f0446c564f16f1f94cf87462"}, - {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b8ac5b46fd798bbbf2ac6620e0437c36a202b08e1f827832c4bf050da081b501"}, - {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:46af561eba6f9b0848b2c9d2427086cabadf14e0abdd9fde9d72d447df268418"}, - {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b5a06d7f60bc2fc378a333978470dfc4e1415ee52f5f0fce4f7853eb10c1e9df"}, - {file = "websockets-13.0.1-cp311-cp311-win32.whl", hash = "sha256:556e70e4f69be1082e6ef26dcb70efcd08d1850f5d6c5f4f2bcb4e397e68f01f"}, - {file = "websockets-13.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:67494e95d6565bf395476e9d040037ff69c8b3fa356a886b21d8422ad86ae075"}, - {file = "websockets-13.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f9c9e258e3d5efe199ec23903f5da0eeaad58cf6fccb3547b74fd4750e5ac47a"}, - {file = "websockets-13.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6b41a1b3b561f1cba8321fb32987552a024a8f67f0d05f06fcf29f0090a1b956"}, - {file = "websockets-13.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f73e676a46b0fe9426612ce8caeca54c9073191a77c3e9d5c94697aef99296af"}, - {file = "websockets-13.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f613289f4a94142f914aafad6c6c87903de78eae1e140fa769a7385fb232fdf"}, - {file = "websockets-13.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f52504023b1480d458adf496dc1c9e9811df4ba4752f0bc1f89ae92f4f07d0c"}, - {file = "websockets-13.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:139add0f98206cb74109faf3611b7783ceafc928529c62b389917a037d4cfdf4"}, - {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:47236c13be337ef36546004ce8c5580f4b1150d9538b27bf8a5ad8edf23ccfab"}, - {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c44ca9ade59b2e376612df34e837013e2b273e6c92d7ed6636d0556b6f4db93d"}, - {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9bbc525f4be3e51b89b2a700f5746c2a6907d2e2ef4513a8daafc98198b92237"}, - {file = "websockets-13.0.1-cp312-cp312-win32.whl", hash = "sha256:3624fd8664f2577cf8de996db3250662e259bfbc870dd8ebdcf5d7c6ac0b5185"}, - {file = "websockets-13.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0513c727fb8adffa6d9bf4a4463b2bade0186cbd8c3604ae5540fae18a90cb99"}, - {file = "websockets-13.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1ee4cc030a4bdab482a37462dbf3ffb7e09334d01dd37d1063be1136a0d825fa"}, - {file = "websockets-13.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbb0b697cc0655719522406c059eae233abaa3243821cfdfab1215d02ac10231"}, - {file = "websockets-13.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:acbebec8cb3d4df6e2488fbf34702cbc37fc39ac7abf9449392cefb3305562e9"}, - {file = "websockets-13.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63848cdb6fcc0bf09d4a155464c46c64ffdb5807ede4fb251da2c2692559ce75"}, - {file = "websockets-13.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:872afa52a9f4c414d6955c365b6588bc4401272c629ff8321a55f44e3f62b553"}, - {file = "websockets-13.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e70fec7c54aad4d71eae8e8cab50525e899791fc389ec6f77b95312e4e9920"}, - {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e82db3756ccb66266504f5a3de05ac6b32f287faacff72462612120074103329"}, - {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4e85f46ce287f5c52438bb3703d86162263afccf034a5ef13dbe4318e98d86e7"}, - {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f3fea72e4e6edb983908f0db373ae0732b275628901d909c382aae3b592589f2"}, - {file = "websockets-13.0.1-cp313-cp313-win32.whl", hash = "sha256:254ecf35572fca01a9f789a1d0f543898e222f7b69ecd7d5381d8d8047627bdb"}, - {file = "websockets-13.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca48914cdd9f2ccd94deab5bcb5ac98025a5ddce98881e5cce762854a5de330b"}, - {file = "websockets-13.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b74593e9acf18ea5469c3edaa6b27fa7ecf97b30e9dabd5a94c4c940637ab96e"}, - {file = "websockets-13.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:132511bfd42e77d152c919147078460c88a795af16b50e42a0bd14f0ad71ddd2"}, - {file = "websockets-13.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:165bedf13556f985a2aa064309baa01462aa79bf6112fbd068ae38993a0e1f1b"}, - {file = "websockets-13.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e801ca2f448850685417d723ec70298feff3ce4ff687c6f20922c7474b4746ae"}, - {file = "websockets-13.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30d3a1f041360f029765d8704eae606781e673e8918e6b2c792e0775de51352f"}, - {file = "websockets-13.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67648f5e50231b5a7f6d83b32f9c525e319f0ddc841be0de64f24928cd75a603"}, - {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4f0426d51c8f0926a4879390f53c7f5a855e42d68df95fff6032c82c888b5f36"}, - {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ef48e4137e8799998a343706531e656fdec6797b80efd029117edacb74b0a10a"}, - {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:249aab278810bee585cd0d4de2f08cfd67eed4fc75bde623be163798ed4db2eb"}, - {file = "websockets-13.0.1-cp38-cp38-win32.whl", hash = "sha256:06c0a667e466fcb56a0886d924b5f29a7f0886199102f0a0e1c60a02a3751cb4"}, - {file = "websockets-13.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1f3cf6d6ec1142412d4535adabc6bd72a63f5f148c43fe559f06298bc21953c9"}, - {file = "websockets-13.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1fa082ea38d5de51dd409434edc27c0dcbd5fed2b09b9be982deb6f0508d25bc"}, - {file = "websockets-13.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4a365bcb7be554e6e1f9f3ed64016e67e2fa03d7b027a33e436aecf194febb63"}, - {file = "websockets-13.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:10a0dc7242215d794fb1918f69c6bb235f1f627aaf19e77f05336d147fce7c37"}, - {file = "websockets-13.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59197afd478545b1f73367620407b0083303569c5f2d043afe5363676f2697c9"}, - {file = "websockets-13.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d20516990d8ad557b5abeb48127b8b779b0b7e6771a265fa3e91767596d7d97"}, - {file = "websockets-13.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1a2e272d067030048e1fe41aa1ec8cfbbaabce733b3d634304fa2b19e5c897f"}, - {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ad327ac80ba7ee61da85383ca8822ff808ab5ada0e4a030d66703cc025b021c4"}, - {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:518f90e6dd089d34eaade01101fd8a990921c3ba18ebbe9b0165b46ebff947f0"}, - {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:68264802399aed6fe9652e89761031acc734fc4c653137a5911c2bfa995d6d6d"}, - {file = "websockets-13.0.1-cp39-cp39-win32.whl", hash = "sha256:a5dc0c42ded1557cc7c3f0240b24129aefbad88af4f09346164349391dea8e58"}, - {file = "websockets-13.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b448a0690ef43db5ef31b3a0d9aea79043882b4632cfc3eaab20105edecf6097"}, - {file = "websockets-13.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:faef9ec6354fe4f9a2c0bbb52fb1ff852effc897e2a4501e25eb3a47cb0a4f89"}, - {file = "websockets-13.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:03d3f9ba172e0a53e37fa4e636b86cc60c3ab2cfee4935e66ed1d7acaa4625ad"}, - {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d450f5a7a35662a9b91a64aefa852f0c0308ee256122f5218a42f1d13577d71e"}, - {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f55b36d17ac50aa8a171b771e15fbe1561217510c8768af3d546f56c7576cdc"}, - {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14b9c006cac63772b31abbcd3e3abb6228233eec966bf062e89e7fa7ae0b7333"}, - {file = "websockets-13.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b79915a1179a91f6c5f04ece1e592e2e8a6bd245a0e45d12fd56b2b59e559a32"}, - {file = "websockets-13.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f40de079779acbcdbb6ed4c65af9f018f8b77c5ec4e17a4b737c05c2db554491"}, - {file = "websockets-13.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:80e4ba642fc87fa532bac07e5ed7e19d56940b6af6a8c61d4429be48718a380f"}, - {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a02b0161c43cc9e0232711eff846569fad6ec836a7acab16b3cf97b2344c060"}, - {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6aa74a45d4cdc028561a7d6ab3272c8b3018e23723100b12e58be9dfa5a24491"}, - {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00fd961943b6c10ee6f0b1130753e50ac5dcd906130dcd77b0003c3ab797d026"}, - {file = "websockets-13.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d93572720d781331fb10d3da9ca1067817d84ad1e7c31466e9f5e59965618096"}, - {file = "websockets-13.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:71e6e5a3a3728886caee9ab8752e8113670936a193284be9d6ad2176a137f376"}, - {file = "websockets-13.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c4a6343e3b0714e80da0b0893543bf9a5b5fa71b846ae640e56e9abc6fbc4c83"}, - {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a678532018e435396e37422a95e3ab87f75028ac79570ad11f5bf23cd2a7d8c"}, - {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6716c087e4aa0b9260c4e579bb82e068f84faddb9bfba9906cb87726fa2e870"}, - {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e33505534f3f673270dd67f81e73550b11de5b538c56fe04435d63c02c3f26b5"}, - {file = "websockets-13.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acab3539a027a85d568c2573291e864333ec9d912675107d6efceb7e2be5d980"}, - {file = "websockets-13.0.1-py3-none-any.whl", hash = "sha256:b80f0c51681c517604152eb6a572f5a9378f877763231fddb883ba2f968e8817"}, - {file = "websockets-13.0.1.tar.gz", hash = "sha256:4d6ece65099411cfd9a48d13701d7438d9c34f479046b34c50ff60bb8834e43e"}, -] - -[metadata] -lock-version = "2.0" -python-versions = "^3.11" -content-hash = "276055f48c9ecc966fa3de2ffa07f45543449461710ac4ac0b418471e212ed03" diff --git a/docs_website/pyproject.toml b/docs_website/pyproject.toml deleted file mode 100644 index c50ae8a2..00000000 --- a/docs_website/pyproject.toml +++ /dev/null @@ -1,22 +0,0 @@ -[tool.poetry] -name = "website" -version = "0.1.0" -description = "" -authors = ["Pierce Freeman "] -readme = "README.md" -package-mode = false - -[tool.poetry.dependencies] -python = "^3.11" -mkdocstrings = { extras = ["crystal"], version = "^0.26.1" } -mkdocstrings-python = "^1.11.1" -mkdocs = "^1.5.3" - -mountaineer = { path = "..", develop = true } -mkdocs-material = "^9.5.15" -pymdown-extensions = "^10.7.1" -black = "^24.3.0" - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" diff --git a/mountaineer/__tests__/client_builder/test_builder.py b/mountaineer/__tests__/client_builder/test_builder.py index a08f237b..585a8669 100644 --- a/mountaineer/__tests__/client_builder/test_builder.py +++ b/mountaineer/__tests__/client_builder/test_builder.py @@ -172,12 +172,22 @@ def test_cache_is_outdated_existing_data( json_dumps( { "ExampleHomeController": { - "action": builder.openapi_action_specs[home_controller], - "render": asdict(builder.openapi_render_specs[home_controller]), + "action": builder.openapi_action_specs[ + home_controller.__class__.__name__ + ], + "render": asdict( + builder.openapi_render_specs[home_controller.__class__.__name__] + ), }, "ExampleDetailController": { - "action": builder.openapi_action_specs[detail_controller], - "render": asdict(builder.openapi_render_specs[detail_controller]), + "action": builder.openapi_action_specs[ + detail_controller.__class__.__name__ + ], + "render": asdict( + builder.openapi_render_specs[ + detail_controller.__class__.__name__ + ] + ), }, }, sort_keys=True, @@ -200,19 +210,29 @@ def test_cache_is_outdated_url_change( json_dumps( { "ExampleHomeController": { - "action": builder.openapi_action_specs[home_controller], + "action": builder.openapi_action_specs[ + home_controller.__class__.__name__ + ], "render": asdict( # Only modify the render attribute. Simulate a user changing the URL # of a component, which does require a FE rebuild. replace( - builder.openapi_render_specs[home_controller], + builder.openapi_render_specs[ + home_controller.__class__.__name__ + ], url="/new_url", ) ), }, "ExampleDetailController": { - "action": builder.openapi_action_specs[detail_controller], - "render": asdict(builder.openapi_render_specs[detail_controller]), + "action": builder.openapi_action_specs[ + detail_controller.__class__.__name__ + ], + "render": asdict( + builder.openapi_render_specs[ + detail_controller.__class__.__name__ + ] + ), }, }, sort_keys=True, diff --git a/mountaineer/__tests__/test_app_manager.py b/mountaineer/__tests__/test_app_manager.py index 3114f6bd..d6952c3e 100644 --- a/mountaineer/__tests__/test_app_manager.py +++ b/mountaineer/__tests__/test_app_manager.py @@ -1,6 +1,4 @@ -import importlib import sys -from inspect import getmembers, isclass from pathlib import Path from tempfile import TemporaryDirectory from time import sleep @@ -10,7 +8,7 @@ from fastapi.responses import Response from mountaineer.__tests__.fixtures import get_fixture_path -from mountaineer.app_manager import HotReloadManager +from mountaineer.app_manager import DevAppManager, package_path_to_module from mountaineer.webservice import UvicornThread AppPackageType = tuple[str, Path, Path] @@ -56,9 +54,9 @@ def app_package(tmp_app_package_dir: Path): @pytest.fixture -def manager(app_package: AppPackageType) -> HotReloadManager: +def manager(app_package: AppPackageType) -> DevAppManager: package_name, _, _ = app_package - return HotReloadManager.from_webcontroller( # type: ignore + return DevAppManager.from_webcontroller( # type: ignore f"{package_name}.test_controller:test_controller", host="localhost", port=8000, @@ -66,7 +64,7 @@ def manager(app_package: AppPackageType) -> HotReloadManager: ) -def test_from_webcontroller(manager: HotReloadManager, app_package: AppPackageType): +def test_from_webcontroller(manager: DevAppManager, app_package: AppPackageType): package_name, _, _ = app_package assert manager.package == package_name assert manager.module_name == f"{package_name}.test_controller" @@ -76,21 +74,19 @@ def test_from_webcontroller(manager: HotReloadManager, app_package: AppPackageTy assert manager.live_reload_port == 8001 -def test_update_module(manager: HotReloadManager, app_package: AppPackageType): +def test_update_module(manager: DevAppManager, app_package: AppPackageType): _, _, controller_file = app_package - # Modify the controller file - with controller_file.open("a") as f: - f.write("\ntest_controller.new_attribute = 'test'\n") + # Make sure we are able to pull an app controller from the mounted + # system module state + manager.app_controller = None # type: ignore manager.update_module() - # Check if the new attribute is present - assert hasattr(manager.app_controller, "new_attribute") - assert manager.app_controller.new_attribute == "test" # type: ignore + assert manager.app_controller is not None -def test_restart_server(manager: HotReloadManager): +def test_restart_server(manager: DevAppManager): manager.restart_server() assert manager.webservice_thread is not None @@ -102,48 +98,14 @@ def test_restart_server(manager: HotReloadManager): manager.webservice_thread.stop() -def test_objects_in_module(manager: HotReloadManager, app_package: AppPackageType): - package_name, _, _ = app_package - module = importlib.import_module(f"{package_name}.test_controller") - - objects = manager.objects_in_module(module) - - # Only counts the TestController class, not the test_controller object - assert len(objects) == 1 - - -def test_package_path_to_module(manager: HotReloadManager, app_package: AppPackageType): +def test_package_path_to_module(app_package: AppPackageType): package_name, temp_dir, _ = app_package file_path = temp_dir / package_name / "test_controller.py" - module_name = manager.package_path_to_module(file_path) + module_name = package_path_to_module(package_name, file_path) assert module_name == f"{package_name}.test_controller" -def test_module_to_package_path(manager: HotReloadManager, app_package: AppPackageType): - package_name, temp_dir, _ = app_package - module_name = manager.module_to_package_path(f"{package_name}.test_controller") - - assert module_name == temp_dir / package_name / "test_controller.py" - - -def test_get_submodules_with_objects( - manager: HotReloadManager, app_package: AppPackageType -): - package_name, _, _ = app_package - root_module = importlib.import_module(package_name) - objects = set( - manager.objects_in_module( - importlib.import_module(f"{package_name}.test_controller") - ) - ) - - submodules = list(manager.get_submodules_with_objects(root_module, objects)) - - assert len(submodules) == 1 - assert submodules[0].__name__ == f"{package_name}.test_controller" - - def test_is_port_open(manager): # Test with a likely closed port assert not manager.is_port_open("localhost", 12345) @@ -163,13 +125,13 @@ def test_is_port_open(manager): server_socket.close() -def test_mount_exceptions(manager: HotReloadManager): +def test_mount_exceptions(manager: DevAppManager): # Check if the exception handler is mounted assert Exception in manager.app_controller.app.exception_handlers @pytest.mark.asyncio -async def test_handle_dev_exception(manager: HotReloadManager): +async def test_handle_dev_exception(manager: DevAppManager): # Create a mock request request = Request({"type": "http", "method": "GET"}) @@ -183,58 +145,3 @@ async def test_handle_dev_exception(manager: HotReloadManager): assert isinstance(response, Response) assert isinstance(response.body, bytes) assert "ValueError: Test exception" in response.body.decode() - - -@pytest.mark.parametrize( - "superclass_names, expected_subclasses", - [ - # Our function only finds direct subclasses - (["SuperClass1"], ["SubClass1"]), - (["SuperClass2"], ["SubClass2"]), - (["SuperClass1", "SuperClass2"], ["SubClass1", "SubClass2"]), - (["SubClass1"], ["SubSubClass"]), - (["UnrelatedClass"], []), - (["SuperClass1", "UnrelatedClass"], ["SubClass1"]), - ], -) -def test_get_objects_with_superclasses( - superclass_names: list[str], - expected_subclasses: list[str], - manager: HotReloadManager, - app_package: AppPackageType, -): - package_name, package_root, _ = app_package - - # Create a simple class hierarchy - class_definitions = [ - ("simple_classes_1.py", "simple_classes_1"), - ("simple_classes_2.py", "simple_classes_2"), - ] - for original_path, module_name in class_definitions: - controller_path = package_root / package_name / f"{module_name}.py" - controller_path.write_text( - (get_fixture_path("mock_webapp") / original_path).read_text() - ) - - superclass_ids: set[int] = set() - - for _, module_name in class_definitions: - class_module = importlib.import_module(f"{package_name}.{module_name}") - - # Sniff out the IDs for different classes - module_classes = getmembers(class_module, isclass) - for name, cls in module_classes: - if name in superclass_names: - superclass_ids.add(id(cls)) - - assert len(superclass_ids) == len(superclass_names) - - subclasses = [] - for _, module_name in class_definitions: - class_module = importlib.import_module(f"{package_name}.{module_name}") - - subclasses += manager.get_modified_subclass_modules( - class_module, superclass_ids - ) - - assert {subclass for _, subclass in subclasses} == set(expected_subclasses) diff --git a/mountaineer/__tests__/test_hotreload.py b/mountaineer/__tests__/test_hotreload.py new file mode 100644 index 00000000..573c262a --- /dev/null +++ b/mountaineer/__tests__/test_hotreload.py @@ -0,0 +1,931 @@ +import importlib +import sys +import textwrap +from pathlib import Path + +import pytest + +from mountaineer.hotreload import HotReloader, resolve_relative_import + + +@pytest.fixture +def test_package_dir(tmp_path: Path, request): + """ + Create test package structure with unique name per test so we allow + client functions to modify their files without adverse affects on other tests. + + """ + test_name = request.node.name.replace("test_", "") + pkg_name = f"test_package_{test_name}".replace("[", "_").replace("]", "_") + + pkg_dir = tmp_path / pkg_name + pkg_dir.mkdir() + + (pkg_dir / "__init__.py").write_text("") + (pkg_dir / "base.py").write_text( + textwrap.dedent( + """ + class BaseClass: + def __init__(self): + self.value = 10 + def get_value(self): + return self.value + """ + ) + ) + + (pkg_dir / "child.py").write_text( + textwrap.dedent( + f""" + from {pkg_name}.base import BaseClass + + class ChildClass(BaseClass): + def __init__(self): + super().__init__() + self.child_value = 20 + def get_child_value(self): + return self.child_value + """ + ) + ) + + # Make it immediately importable + sys.path.insert(0, str(pkg_dir.parent)) + sys.path.insert(0, str(pkg_dir)) + + return pkg_dir, pkg_name + + +def test_initial_dependency_tracking(test_package_dir: tuple[Path, str]): + """ + Test initial dependency tracking on load. + + """ + pkg_dir, pkg_name = test_package_dir + + # Initialize the HotReloader with entrypoint, this should take + # care of loading the child+base files + hot_reloader = HotReloader(pkg_name, pkg_dir, entrypoint=f"{pkg_name}.child") + + child_deps = hot_reloader.get_module_dependencies(f"{pkg_name}.child") + base_deps = hot_reloader.get_module_dependencies(f"{pkg_name}.base") + + assert child_deps + assert base_deps + + assert f"{pkg_name}.base" in child_deps.imports + assert child_deps.superclasses == {"ChildClass": {"BaseClass"}} + assert base_deps.subclasses == {"BaseClass": {"ChildClass"}} + + +def test_inheritance_changes(test_package_dir: tuple[Path, str]): + """ + Test inheritance changes if the base model is changed. + + """ + pkg_dir, pkg_name = test_package_dir + + # Verify initial class logic + child_module = importlib.import_module(f"{pkg_name}.child") + initial_child = child_module.ChildClass() + assert initial_child.get_value() == 10 + + # Initialize HotReloader + hot_reloader = HotReloader(pkg_name, pkg_dir, entrypoint=f"{pkg_name}.child") + + # Modify base with new intermediate class + (pkg_dir / "base.py").write_text( + textwrap.dedent( + """ + class BaseClass: + def __init__(self): + self.value = 10 + def get_value(self): + return self.value + class IntermediateClass(BaseClass): + def get_intermediate_value(self): + return 15 + """ + ) + ) + + success, reloaded = hot_reloader.reload_module(f"{pkg_name}.base") + assert success + + # Verify both inheritance relationships + base_deps = hot_reloader.get_module_dependencies(f"{pkg_name}.base") + assert base_deps + assert base_deps.subclasses == {"BaseClass": {"IntermediateClass", "ChildClass"}} + + # Verify child still works + child_module = importlib.import_module(f"{pkg_name}.child") + new_child = child_module.ChildClass() + assert new_child.get_value() == 10 + + +def test_cyclic_dependencies(test_package_dir: tuple[Path, str]): + """ + Test cyclic dependencies. + + """ + pkg_dir, pkg_name = test_package_dir + + # Write module files + (pkg_dir / "module_b.py").write_text( + textwrap.dedent( + """ + class B: + def __init__(self): + self.value = 10 + """ + ) + ) + (pkg_dir / "module_a.py").write_text( + textwrap.dedent( + f""" + from {pkg_name}.module_b import B + class A: + def __init__(self): + self.b = B() + """ + ) + ) + + # Initialize HotReloader + hot_reloader = HotReloader(pkg_name, pkg_dir, entrypoint=f"{pkg_name}.module_a") + + success, reloaded = hot_reloader.reload_module(f"{pkg_name}.module_a") + assert success + + +def test_partial_reload_failure(test_package_dir: tuple[Path, str]): + """ + Test partial reload failure. + + """ + pkg_dir, pkg_name = test_package_dir + + # Initialize HotReloader + hot_reloader = HotReloader(pkg_name, pkg_dir, entrypoint=f"{pkg_name}.child") + + # Introduce syntax error in child.py + (pkg_dir / "child.py").write_text( + textwrap.dedent( + f""" + from {pkg_name}.base import BaseClass + + class ChildClass(BaseClass) # Syntax error + def get_child_value(self): + return 20 + """ + ) + ) + + success, reloaded = hot_reloader.reload_module(f"{pkg_name}.child") + assert not success + + # Verify base module is still functional + base_module = importlib.import_module(f"{pkg_name}.base") + obj = base_module.BaseClass() + assert obj.get_value() == 10 + + +def test_multiple_inheritance(test_package_dir: tuple[Path, str]): + """ + Test multiple inheritance. + + """ + pkg_dir, pkg_name = test_package_dir + + # Write mixin.py + (pkg_dir / "mixin.py").write_text( + textwrap.dedent( + """ + class LoggerMixin: + def log(self): + return "logged" + """ + ) + ) + + # Update child.py to use multiple inheritance + (pkg_dir / "child.py").write_text( + textwrap.dedent( + f""" + from {pkg_name}.base import BaseClass + from {pkg_name}.mixin import LoggerMixin + + class ChildClass(BaseClass, LoggerMixin): + def get_child_value(self): + return 20 + """ + ) + ) + + # Initialize HotReloader + hot_reloader = HotReloader(pkg_name, pkg_dir, entrypoint=f"{pkg_name}.child") + + success, reloaded = hot_reloader.reload_module(f"{pkg_name}.child") + assert success + + child = importlib.import_module(f"{pkg_name}.child") + obj = child.ChildClass() + assert obj.get_value() == 10 + assert obj.log() == "logged" + + +def test_enum_reload(test_package_dir: tuple[Path, str]): + """ + Test that enums are properly handled during reload. + + """ + pkg_dir, pkg_name = test_package_dir + + # Create initial enum file + (pkg_dir / "status.py").write_text( + textwrap.dedent( + """ + from enum import Enum + + class Status(Enum): + DRAFT = "draft" + PUBLISHED = "published" + """ + ) + ) + + # Create a file that uses the enum + (pkg_dir / "document.py").write_text( + textwrap.dedent( + f""" + from {pkg_name}.status import Status + + class Document: + def __init__(self): + self.status = Status.DRAFT + + def get_status(self): + return self.status + """ + ) + ) + + # Initialize HotReloader + hot_reloader = HotReloader(pkg_name, pkg_dir, entrypoint=f"{pkg_name}.document") + + # Modify enum file - add new status + (pkg_dir / "status.py").write_text( + textwrap.dedent( + """ + from enum import Enum + + class Status(Enum): + DRAFT = "draft" + PUBLISHED = "published" + ARCHIVED = "archived" + """ + ) + ) + + success, reloaded = hot_reloader.reload_module(f"{pkg_name}.status") + assert success + + # Verify new enum value is available + status_module = importlib.import_module(f"{pkg_name}.status") + assert hasattr(status_module.Status, "ARCHIVED") + + +def test_import_alias_dependency_graph(test_package_dir: tuple[Path, str]): + """ + Test that the dependency graph correctly tracks imports with aliases. + + """ + pkg_dir, pkg_name = test_package_dir + + # Create models.py with initial class + (pkg_dir / "models.py").write_text( + textwrap.dedent( + """ + class MyModel: + def get_value(self): + return 10 + """ + ) + ) + + # Create main.py that imports models using an alias + (pkg_dir / "main.py").write_text( + textwrap.dedent( + f""" + import {pkg_name}.models as mod + + def get_model_value(): + model = mod.MyModel() + return model.get_value() + """ + ) + ) + + # Import modules + main_module = importlib.import_module(f"{pkg_name}.main") + + # Initialize HotReloader + hot_reloader = HotReloader(pkg_name, pkg_dir, entrypoint=f"{pkg_name}.main") + + # Ensure the dependency graph is built correctly + main_deps = hot_reloader.get_module_dependencies(f"{pkg_name}.main") + assert main_deps + assert ( + f"{pkg_name}.models" in main_deps.imports + ), "models should be in main's imports" + + # Check that models knows it's imported by main + models_deps = hot_reloader.get_module_dependencies(f"{pkg_name}.models") + assert models_deps + assert ( + f"{pkg_name}.main" in models_deps.imported_by + ), "main should be in models' imported_by" + + # Verify that the code works + assert main_module.get_model_value() == 10 + + (pkg_dir / "models.py").write_text( + textwrap.dedent( + """ + class MyModel: + def get_value(self): + return 20 + """ + ) + ) + + success, reloaded = hot_reloader.reload_module(f"{pkg_name}.models") + assert success + assert f"{pkg_name}.main" in reloaded + + # Verify that the updated value is reflected + main_module = sys.modules[f"{pkg_name}.main"] + assert main_module.get_model_value() == 20 + + +def test_relative_import(test_package_dir: tuple[Path, str]): + """ + Test that the dependency graph correctly tracks imports with aliases. + + """ + pkg_dir, pkg_name = test_package_dir + + # Create the package structure + (pkg_dir / "models").mkdir() + (pkg_dir / "models/example.py").write_text( + textwrap.dedent( + """ + class MyModel: + def get_value(self): + return 10 + """ + ) + ) + (pkg_dir / "models/__init__.py").write_text( + textwrap.dedent( + """ + from .example import MyModel as MyModel + """ + ) + ) + (pkg_dir / "main.py").write_text( + textwrap.dedent( + f""" + from {pkg_name} import models + + def get_model_value(): + model = models.MyModel() + return model.get_value() + """ + ) + ) + + # Import and verify initial state + main_module = importlib.import_module(f"{pkg_name}.main") + hot_reloader = HotReloader(pkg_name, pkg_dir, entrypoint=f"{pkg_name}.main") + + # Verify initial dependency graph + deps = hot_reloader.get_module_dependencies(f"{pkg_name}.models") + assert deps + assert f"{pkg_name}.models.example" in deps.imports, "models should import example" + assert f"{pkg_name}.main" in deps.imported_by, "models should be imported by main" + + # Verify initial functionality + initial_value = main_module.get_model_value() + assert initial_value == 10, f"Expected 10, got {initial_value}" + + # Modify the model + (pkg_dir / "models/example.py").write_text( + textwrap.dedent( + """ + class MyModel: + def get_value(self): + return 200 + """ + ) + ) + + # Reload and verify + success, reloaded = hot_reloader.reload_module(f"{pkg_name}.models.example") + assert success, "Reload should succeed" + assert f"{pkg_name}.models.example" in reloaded, "example.py should be reloaded" + assert f"{pkg_name}.models" in reloaded, "models/__init__.py should be reloaded" + assert f"{pkg_name}.main" in reloaded, "main.py should be reloaded" + + # Verify the module was actually reloaded with new code + new_value = main_module.get_model_value() + assert new_value == 200, f"Expected 200, got {new_value}" + + +def test_ignores_irrelevant_files(test_package_dir: tuple[Path, str]): + """ + Ignores files that aren't directly in the DAG path of the original file + + """ + pkg_dir, pkg_name = test_package_dir + + # Create the package structure + (pkg_dir / "models").mkdir() + (pkg_dir / "models/example.py").write_text( + textwrap.dedent( + f""" + from {pkg_name}.other_item import OtherFile + class MyModel: + def get_value(self): + return 10 + """ + ) + ) + (pkg_dir / "models/__init__.py").write_text( + textwrap.dedent( + """ + from .example import MyModel as MyModel + """ + ) + ) + (pkg_dir / "other_item.py").write_text( + textwrap.dedent( + """ + class OtherFile: + pass + """ + ) + ) + (pkg_dir / "main.py").write_text( + textwrap.dedent( + f""" + from {pkg_name} import models + from {pkg_name}.other_item import OtherFile + + def get_model_value(): + model = models.MyModel() + return model.get_value() + """ + ) + ) + + # Import and verify initial state + main_module = importlib.import_module(f"{pkg_name}.main") + hot_reloader = HotReloader(pkg_name, pkg_dir, entrypoint=f"{pkg_name}.main") + + # Verify initial dependency graph + deps = hot_reloader.get_module_dependencies(f"{pkg_name}.models") + assert deps + assert f"{pkg_name}.models.example" in deps.imports, "models should import example" + assert f"{pkg_name}.main" in deps.imported_by, "models should be imported by main" + + # Verify initial functionality + initial_value = main_module.get_model_value() + assert initial_value == 10, f"Expected 10, got {initial_value}" + + # Modify the model + (pkg_dir / "models/example.py").write_text( + textwrap.dedent( + """ + class MyModel: + def get_value(self): + return 200 + """ + ) + ) + + # Reload and verify + success, reloaded = hot_reloader.reload_module(f"{pkg_name}.models.example") + assert success, "Reload should succeed" + assert f"{pkg_name}.models.example" in reloaded, "example.py should be reloaded" + assert f"{pkg_name}.models" in reloaded, "models/__init__.py should be reloaded" + assert f"{pkg_name}.main" in reloaded, "main.py should be reloaded" + assert ( + f"{pkg_name}.other_item" not in reloaded + ), "other_item.py should not be reloaded" + + # Verify the module was actually reloaded with new code + new_value = main_module.get_model_value() + assert new_value == 200, f"Expected 200, got {new_value}" + + +def test_package_structure_scanning(test_package_dir: tuple[Path, str]): + """ + Test package structure scanning with nested directories. + + """ + pkg_dir, pkg_name = test_package_dir + + # Create nested package structure + nested_dir = pkg_dir / "nested" + nested_dir.mkdir() + (nested_dir / "__init__.py").write_text("") + (nested_dir / "module.py").write_text( + textwrap.dedent( + """ + class NestedClass: + pass + """ + ) + ) + + sub_nested = nested_dir / "subnested" + sub_nested.mkdir() + (sub_nested / "__init__.py").write_text( + textwrap.dedent( + """ + class SubNestedInit: + pass + """ + ) + ) + + importlib.import_module(f"{pkg_name}.nested") + importlib.import_module(f"{pkg_name}.nested.module") + importlib.import_module(f"{pkg_name}.nested.subnested") + + # Initialize HotReloader + hot_reloader = HotReloader(pkg_name, pkg_dir, entrypoint=f"{pkg_name}.nested") + + # Verify modules are tracked + assert f"{pkg_name}.nested.module" in hot_reloader.dependency_graph + assert f"{pkg_name}.nested" in hot_reloader.dependency_graph + assert f"{pkg_name}.nested.subnested" in hot_reloader.dependency_graph + + +def test_inheritance_tree_building(test_package_dir: tuple[Path, str]): + """ + Test inheritance tree building with complex inheritance. + + """ + pkg_dir, pkg_name = test_package_dir + + # Create a hierarchy of classes + (pkg_dir / "base.py").write_text( + textwrap.dedent( + """ + class BaseClass: + pass + + class AnotherBase: + pass + """ + ) + ) + + (pkg_dir / "middle.py").write_text( + textwrap.dedent( + f""" + from {pkg_name}.base import BaseClass, AnotherBase + + class MiddleClass(BaseClass): + pass + + class MultipleInheritance(BaseClass, AnotherBase): + pass + """ + ) + ) + + (pkg_dir / "leaf.py").write_text( + textwrap.dedent( + f""" + from {pkg_name}.middle import MiddleClass + + class LeafClass(MiddleClass): + pass + """ + ) + ) + + # Initialize HotReloader + hot_reloader = HotReloader(pkg_name, pkg_dir, entrypoint=f"{pkg_name}.leaf") + + # Verify inheritance relationships + base_deps = hot_reloader.get_module_dependencies(f"{pkg_name}.base") + middle_deps = hot_reloader.get_module_dependencies(f"{pkg_name}.middle") + leaf_deps = hot_reloader.get_module_dependencies(f"{pkg_name}.leaf") + + assert base_deps + assert middle_deps + assert leaf_deps + + assert "MiddleClass" in base_deps.subclasses["BaseClass"] + assert "MultipleInheritance" in base_deps.subclasses["BaseClass"] + assert "MultipleInheritance" in base_deps.subclasses["AnotherBase"] + assert "LeafClass" in middle_deps.subclasses["MiddleClass"] + assert leaf_deps.superclasses["LeafClass"] == {"MiddleClass"} + + +def test_package_structure_excluded_dirs(test_package_dir: tuple[Path, str]): + """ + Test that certain directories are excluded from scanning. + + """ + pkg_dir, pkg_name = test_package_dir + + # Create directories that should be excluded + hidden_dir = pkg_dir / ".hidden" + hidden_dir.mkdir() + (hidden_dir / "module.py").write_text("class Hidden: pass") + + pycache_dir = pkg_dir / "__pycache__" + pycache_dir.mkdir(exist_ok=True) + (pycache_dir / "cached.py").write_text("class Cached: pass") + + # Import modules (only the ones we expect to include) + importlib.import_module(f"{pkg_name}.base") + + # Initialize HotReloader + hot_reloader = HotReloader(pkg_name, pkg_dir, entrypoint=f"{pkg_name}.base") + + # Verify excluded modules aren't tracked + for module in hot_reloader.dependency_graph: + assert not module.endswith("Hidden") + assert not module.endswith("Cached") + + +def test_inheritance_tree_module_updates(test_package_dir: tuple[Path, str]): + """Test inheritance tree updates when modules change.""" + pkg_dir, pkg_name = test_package_dir + + # Initial class structure + (pkg_dir / "dynamic.py").write_text( + textwrap.dedent( + f""" + from {pkg_name}.base import BaseClass + + class DynamicClass(BaseClass): + pass + """ + ) + ) + + # Initialize HotReloader + hot_reloader = HotReloader(pkg_name, pkg_dir, entrypoint=f"{pkg_name}.dynamic") + + base_deps = hot_reloader.get_module_dependencies(f"{pkg_name}.base") + assert base_deps + assert "DynamicClass" in base_deps.subclasses["BaseClass"] + + # Update inheritance + (pkg_dir / "dynamic.py").write_text( + textwrap.dedent( + """ + class DynamicClass: # No longer inherits from BaseClass + pass + """ + ) + ) + + success, reloaded = hot_reloader.reload_module(f"{pkg_name}.dynamic") + assert success + + # Verify inheritance is updated + base_deps = hot_reloader.get_module_dependencies(f"{pkg_name}.base") + assert base_deps + assert "DynamicClass" not in base_deps.subclasses.get("BaseClass", set()) + + +def test_new_file_reload(test_package_dir: tuple[Path, str]): + """ + Test adding and reloading a new file that imports other modules. + + """ + pkg_dir, pkg_name = test_package_dir + + # Import initial modules + hot_reloader = HotReloader(pkg_name, pkg_dir, entrypoint=f"{pkg_name}.base") + + # Create new file that imports base + (pkg_dir / "new_module.py").write_text( + textwrap.dedent( + f""" + from {pkg_name}.base import BaseClass + + class NewClass(BaseClass): + def get_special_value(self): + return self.get_value() * 2 + """ + ) + ) + + print("All files", list(pkg_dir.iterdir()), sys.path) # noqa: T201 + + # Calling this should also start tracking the new file + new_deps = hot_reloader.get_module_dependencies(f"{pkg_name}.new_module") + assert new_deps + + # Verify we have also updated the old file bidirectionally + base_deps = hot_reloader.get_module_dependencies(f"{pkg_name}.base") + assert base_deps + + assert f"{pkg_name}.base" in new_deps.imports + assert new_deps.superclasses == {"NewClass": {"BaseClass"}} + assert "NewClass" in base_deps.subclasses["BaseClass"] + + # Verify that the new module was reloaded + new_module = sys.modules[f"{pkg_name}.new_module"] + obj = new_module.NewClass() + assert obj.get_special_value() == 20 + + # Modify the base module + (pkg_dir / "base.py").write_text( + textwrap.dedent( + """ + class BaseClass: + def __init__(self): + self.value = 20 + def get_value(self): + return self.value + """ + ) + ) + + success, reloaded = hot_reloader.reload_module(f"{pkg_name}.base") + assert success + assert reloaded == [f"{pkg_name}.base", f"{pkg_name}.new_module"] + + # Verify that the new module was reloaded + new_module = sys.modules[f"{pkg_name}.new_module"] + obj = new_module.NewClass() + assert obj.get_special_value() == 40 + + +# +# absolute imports +# + + +def parse_relative_import(import_str: str) -> tuple[str, int]: + """ + Parse a relative import string into (import_path, level). + + Examples: + "module" -> ("module", 0) + ".module" -> ("module", 1) + "..module" -> ("module", 2) + "..." -> ("", 3) + + """ + level = 0 + for char in import_str: + if char == ".": + level += 1 + else: + break + return import_str[level:], level + + +@pytest.mark.parametrize( + "current_module, from_import_str, import_name, sys_modules, expected", + [ + # Absolute imports (no dots) + ( + "my_package.module", + "other_module", + "MyClass", + {"my_package.module.other_module", "my_package.other_module"}, + "my_package.module.other_module", + ), + ( + "my_package.module", + "my_package.submodule", + "MyClass", + {"my_package.submodule"}, + "my_package.submodule", + ), + # Single dot relative imports (current directory) + ( + "my_package.module", + ".submodule", + "MyClass", + {"my_package.module.submodule"}, + "my_package.module.submodule", + ), + ( + "my_package.__init__", + ".submodule", + "MyClass", + {"my_package.submodule"}, + "my_package.submodule", + ), + # Two dot relative imports (parent directory) + ( + "my_package.sub.module", + "..other_module", + "MyClass", + {"my_package.sub.other_module"}, + "my_package.sub.other_module", + ), + ( + "my_package.sub.module", + "..other_package.module", + "MyClass", + {"my_package.sub.other_package.module"}, + "my_package.sub.other_package.module", + ), + # Three or more dot relative imports + ( + "my_package.a.b.c.module", + "...utils", + "MyClass", + {"my_package.a.b.utils"}, + "my_package.a.b.utils", + ), + ( + "my_package.deep.nested.module", + "....top_level", + "MyClass", + {"my_package.top_level"}, + "my_package.top_level", + ), + # Empty imports (importing the package itself) + ( + "my_package.module", + ".", + "MyClass", + {"my_package.module"}, + "my_package.module", + ), + ( + "my_package.sub.module", + "..", + "MyClass", + {"my_package.sub"}, + "my_package.sub", + ), + ( + "my_package.a.b.module", + "...", + "MyClass", + {"my_package.a"}, + "my_package.a", + ), + # Edge and error cases + ( + "my_package.module", + ".....", # Too many dots + "MyClass", + set(), + None, + ), + ( + "my_package.module", + "nonexistent_module", + "MyClass", + set(), + None, + ), + ( + "my_package.module", + "my_package.nonexistent", + "MyClass", + set(), + None, + ), + ], +) +def test_resolve_relative_import( + current_module: str, + from_import_str: str, + import_name: str, + sys_modules: set[str], + expected: str | None, +): + from_import, level = parse_relative_import(from_import_str) + + result = resolve_relative_import( + root_package="my_package", + current_module=current_module, + from_import=from_import, + from_import_level=level, + import_name=import_name, + sys_modules=sys_modules, + ) + + assert result == expected, ( + f"Failed for import '{from_import_str}' in module '{current_module}'\n" + f"Expected: {expected}\n" + f"Got: {result}" + ) diff --git a/mountaineer/app_manager.py b/mountaineer/app_manager.py index 98b0d321..21b24d5b 100644 --- a/mountaineer/app_manager.py +++ b/mountaineer/app_manager.py @@ -1,9 +1,10 @@ import importlib import os import socket +import sys from importlib.metadata import distributions -from inspect import getmembers, isclass from pathlib import Path +from tempfile import mkdtemp from traceback import format_exception from types import ModuleType @@ -11,16 +12,16 @@ from fastapi.responses import Response from mountaineer.app import AppController +from mountaineer.client_builder.builder import ClientBuilder +from mountaineer.client_compiler.compile import ClientCompiler from mountaineer.controllers.exception_controller import ExceptionController from mountaineer.webservice import UvicornThread -class HotReloadManager: +class DevAppManager: """ - Manages the lifecycle of a single app controller, including its webservice thread - and utilities for hot-reloading. - - This is only intended for development use. + Manages the lifecycle of a single app controller. This is only intended + for development use. """ @@ -52,6 +53,17 @@ def __init__( # Initial mount self.mount_exceptions(app_controller) + global_build_cache = Path(mkdtemp()) + self.js_compiler = ClientBuilder( + app_controller, + live_reload_port=live_reload_port, + build_cache=global_build_cache, + ) + + self.app_compiler = ClientCompiler( + app=app_controller, + ) + @classmethod def from_webcontroller( cls, @@ -80,15 +92,20 @@ def from_webcontroller( ) def update_module(self): - # Now we re-mount the app entrypoint, which should initialize the changed - # controllers with their new values - self.module = importlib.reload(self.module) + # By the time we get to this point, our hot reloader should + # have already reloaded the module in global space + self.module = sys.modules[self.module.__name__] + # self.module = importlib.reload(self.module) initial_state = {name: getattr(self.module, name) for name in dir(self.module)} self.app_controller = initial_state[self.controller_name] # Re-mount the exceptions now that we have a new app controller self.mount_exceptions(self.app_controller) + # We also have to update our builders + self.js_compiler.update_controller(self.app_controller) + self.app_compiler.update_controller(self.app_controller) + def restart_server(self): if not self.port: raise ValueError("Port not set") @@ -107,136 +124,6 @@ def restart_server(self): ) self.webservice_thread.start() - def objects_in_module(self, module: ModuleType): - """ - Given a module like `myapp.controllers.my_controller` it will find all - the objects that are actually defined in that file (versus imported - into that file but with a root definition elsewhere). - - """ - return { - id(obj) - for name in dir(module) - for obj in [getattr(module, name)] - # Only include objects defined in this file versus imports into this file - # from external sources - if hasattr(obj, "__module__") and obj.__module__ == module.__name__ - } - - def package_path_to_module(self, file_path_raw: Path): - """ - We are notified about changes to files on disk, this function converts - the filename to Python's addressable module syntax. - - """ - # Get the package's root directory - package = importlib.import_module(self.package) - if not package.__file__: - raise ValueError( - f"The package {self.package} does not have a __file__ attribute" - ) - - package_root = os.path.dirname(package.__file__) - - # Ensure the file_path is absolute - file_path = os.path.abspath(str(file_path_raw)) - - # Check if the file is within the package - if not file_path.startswith(package_root): - raise ValueError( - f"The file {file_path} is not in the package {self.package}" - ) - - # Remove the package root and the file extension - relative_path = os.path.relpath(file_path, package_root) - module_path = os.path.splitext(relative_path)[0] - - # Convert path separators to dots and add the package name - module_name = f"{self.package}.{module_path.replace(os.sep, '.')}" - - return module_name - - def module_to_package_path(self, module_name): - """ - Converts a Python module name to its corresponding file path on disk. - Returns a Path object. - """ - # Get the package's root directory - package = importlib.import_module(self.package) - if not package.__file__: - raise ValueError( - f"The package {self.package} does not have a __file__ attribute" - ) - package_root = Path(package.__file__).parent - - # Ensure the module is within the package - if not module_name.startswith(self.package + "."): - raise ValueError( - f"The module {module_name} is not in the package {self.package}" - ) - - # Remove the package name from the module name - relative_module = module_name[len(self.package) + 1 :] - - # Convert dots to path separators and add .py extension - relative_path = relative_module.replace(".", os.sep) + ".py" - - # Construct the full path - full_path = package_root / relative_path - - # Ensure the file exists - if not full_path.is_file(): - raise FileNotFoundError(f"No file found for module {module_name}") - - return full_path - - def get_submodules_with_objects( - self, root_module: ModuleType, object_ids: set[int] - ): - already_seen_modules = set() - - def inner(module): - if id(module) in already_seen_modules: - return - already_seen_modules.add(id(module)) - - for attribute_name in dir(module): - attribute_value = getattr(module, attribute_name) - - if isinstance(attribute_value, type(module)): - # Only consider modules that are part of our project - if not attribute_value.__name__.startswith(self.package): - continue - yield from self.get_submodules_with_objects( - attribute_value, object_ids - ) - else: - if id(attribute_value) in object_ids: - yield module - - yield from inner(root_module) - - def get_modified_subclass_modules( - self, module: ModuleType, allowed_object_ids: set[int] - ): - """ - Assuming we are being passed the module where allowed_object_ids are defined, - returns the direct subclasses of those objects. - - """ - result: set[tuple[str, str]] = set() - - # Sniff out the IDs for different classes - module_classes = getmembers(module, isclass) - for name, cls in module_classes: - if id(cls) not in allowed_object_ids: - continue - - for subclass in cls.__subclasses__(): - result.add((subclass.__module__, subclass.__name__)) - - return list(result) - def is_port_open(self, host, port): """ Check if a port is open on the given host. @@ -288,3 +175,34 @@ def find_packages_with_prefix(prefix: str): for dist in distributions() if dist.metadata["Name"].startswith(prefix) ] + + +def package_path_to_module(package: str, file_path_raw: Path) -> str: + """ + Convert a file path to its corresponding Python module path. + + Args: + package: The root package name (e.g. 'amplify') + file_path_raw: The file path to convert + + Returns: + The full module path (e.g. 'amplify.controllers.auth') + """ + # Get the package's root directory + package_module = importlib.import_module(package) + if not package_module.__file__: + raise ValueError(f"The package {package} does not have a __file__ attribute") + + package_root = os.path.dirname(package_module.__file__) + file_path = os.path.abspath(str(file_path_raw)) + + # Check if the file is within the package + if not file_path.startswith(package_root): + raise ValueError(f"The file {file_path} is not in the package {package}") + + # Remove the package root and the file extension + relative_path = os.path.relpath(file_path, package_root) + module_path = os.path.splitext(relative_path)[0] + + # Convert path separators to dots and add the package name + return f"{package}.{module_path.replace(os.sep, '.')}" diff --git a/mountaineer/cli.py b/mountaineer/cli.py index e67da63a..69593fe2 100644 --- a/mountaineer/cli.py +++ b/mountaineer/cli.py @@ -1,28 +1,26 @@ import asyncio -import importlib -import sys from hashlib import md5 from multiprocessing import get_start_method, set_start_method -from os import environ from pathlib import Path from signal import SIGINT, signal from tempfile import mkdtemp -from time import sleep, time -from traceback import format_exc +from time import time from typing import Callable from inflection import underscore from rich.traceback import install as rich_traceback_install from mountaineer import mountaineer as mountaineer_rs # type: ignore -from mountaineer.app_manager import HotReloadManager, find_packages_with_prefix -from mountaineer.cache import LRUCache +from mountaineer.app_manager import ( + DevAppManager, + find_packages_with_prefix, + package_path_to_module, +) from mountaineer.client_builder.builder import ClientBuilder -from mountaineer.client_compiler.compile import ClientCompiler from mountaineer.console import CONSOLE from mountaineer.constants import KNOWN_JS_EXTENSIONS +from mountaineer.hotreload import HotReloader from mountaineer.logging import LOGGER -from mountaineer.ssr import render_ssr from mountaineer.static import get_static_path from mountaineer.watch import ( CallbackDefinition, @@ -59,7 +57,7 @@ def handle_watch( # different builds global_build_cache = Path(mkdtemp()) - app_manager = HotReloadManager.from_webcontroller(webcontroller) + app_manager = DevAppManager.from_webcontroller(webcontroller) js_compiler = ClientBuilder( app_manager.app_controller, live_reload_port=None, @@ -117,216 +115,88 @@ def handle_runserver( update_multiprocessing_settings() rich_traceback_install() - # The global cache will let us keep cache files warm across - # different builds - global_build_cache = Path(mkdtemp()) + start_time = time() - # Start the webservice - it should persist for the lifetime of the - # runserver, so a single websocket frontend can be notified across - # multiple builds - start = time() + # Initialize components watcher_webservice = WatcherWebservice(webservice_host=host) watcher_webservice.start() - app_manager = HotReloadManager.from_webcontroller( + app_manager = DevAppManager.from_webcontroller( webcontroller, host=host, port=port, live_reload_port=watcher_webservice.port ) - LOGGER.debug(f"Initial load of {webcontroller} complete.") - js_compiler = ClientBuilder( - app_manager.app_controller, - live_reload_port=watcher_webservice.port, - build_cache=global_build_cache, + # Initialize hot reloader with root package + hot_reloader = HotReloader( + root_package=package, + package_path=Path(package.replace(".", "/")), + entrypoint=webcontroller.rsplit(":")[0], ) - asyncio.run(js_compiler.build_all()) - app_compiler = ClientCompiler( - app=app_manager.app_controller, - view_root=app_manager.app_controller.view_root, - ) - asyncio.run(app_compiler.run_builder_plugins()) + # Initial build + asyncio.run(app_manager.js_compiler.build_all()) + asyncio.run(app_manager.app_compiler.run_builder_plugins()) - # Start the initial thread app_manager.restart_server() - CONSOLE.print(f"[bold green]🚀 App launched in {time() - start:.2f} seconds") - - # Now that we've started the server for the first time, any additional reloads - # must be hot-reloaded - environ["MOUNTAINEER_HOT_RELOADING"] = "1" + CONSOLE.print(f"[bold green]🚀 App launched in {time() - start_time:.2f} seconds") - def update_build(metadata: CallbackMetadata): + async def handle_file_changes(metadata: CallbackMetadata): start = time() - - # Consider the case where we have the app controller that imports a given module - # ``` - # import myapp.controller as controller - # ``` - # And within this controller we have an import from the file that we've changed as part - # of this modification - # ``` - # from myapp.module import MyController as MyController - # ``` - # By default, an importlib.refresh(app_controller_module) won't update this given sub-dependency, since - # Python holds tighly to objects that are brought into scope and are not only global system modules. - # - # We keep track of the unique objects that are contained within the updated files, so we can - # manually inspect the project for any objects that are still referencing the old module. - # and update them manually. - objects_to_reload: set[int] = set() - - updated_js: set[Path] = set() - updated_modules: set[tuple[str, Path]] = set() + updated_js = set() + updated_python = set() for event in metadata.events: - if ( - event.action != CallbackType.MODIFIED - and event.action != CallbackType.CREATED - ): - # Keep deleted files around for now - continue - - if event.path.suffix == ".py": - module_name = app_manager.package_path_to_module(event.path) - updated_modules.add((module_name, event.path)) - elif event.path.suffix in KNOWN_JS_EXTENSIONS: + if event.path.suffix in KNOWN_JS_EXTENSIONS: updated_js.add(event.path) + elif event.path.suffix == ".py": + updated_python.add(event.path) - # Update the modules in a queue fashion so we can update the dependent modules - # where the subclasses live - module_queue = list(updated_modules) - seen_modules: set[str] = set() - updated_python: set[Path] = set() - - while module_queue: - module_name, python_path = module_queue.pop(0) - if module_name in seen_modules: - continue - seen_modules.add(module_name) - - # Get the IDs before we reload the module, since they'll change after - # the re-import - # If module_name is not already in sys.modules, it hasn't been - # imported yet and we don't need to worry about refreshing dependent definitions - if module_name in sys.modules: - LOGGER.debug(f"Changed Python: {python_path}") - current_module = sys.modules[module_name] - owned_by_module = app_manager.objects_in_module(current_module) - - objects_to_reload |= owned_by_module - obj_subclasses = app_manager.get_modified_subclass_modules( - current_module, owned_by_module - ) - subclass_modules = {module for module, _ in obj_subclasses} - - # Since these are just the direct subclass modules, we add it to the queue - # to bring in all recursive subclasses - for additional_module in subclass_modules: - module_path = app_manager.module_to_package_path(additional_module) - module_queue.append((additional_module, module_path)) - else: - LOGGER.debug(f"Module {module_name} is new and not yet imported") - - # Now, once we've cached the ids of objects currently in memory we can clear - # the actual model definition from the module cache - try: - updated_module = importlib.import_module(module_name) - importlib.reload(updated_module) - - # Only follow the downstream dependencies if the module was successfully reloaded - updated_python.add(python_path) - except Exception as e: - stacktrace = format_exc() - CONSOLE.print( - f"[bold red]Error reloading {module_name}, stopping reload..." - ) - CONSOLE.print(f"[bold red]{e}\n{stacktrace}") - - # In the case of an exception in one module we still want to try to load - # the other ones that were affected, since we want to keep the differential - # state of the app up to date - continue - - # Pass all updated files to the app compiler to build stylesheets - # and other asset compilations - asyncio.run( - app_compiler.run_builder_plugins( - limit_paths=list(updated_js) + list(updated_python) - ) - ) - - # Logging in the following section assumes we're actually doing some - # work; if we're not, we should just exit early - if not updated_js and not updated_python: + if not (updated_js or updated_python): return + # Handle Python changes if updated_python: - LOGGER.debug(f"Changed Python: {updated_python}") - asyncio.run(js_compiler.build_use_server()) + module_names = [ + package_path_to_module(package, module_path) + for module_path in updated_python + ] + success, reloaded = hot_reloader.reload_modules(module_names) - if updated_js: - LOGGER.debug(f"Changed JS: {updated_js}") - # asyncio.run(js_compiler.build_fe_diff(list(updated_js))) + if reloaded: + app_manager.update_module() + await app_manager.js_compiler.build_use_server() + app_manager.restart_server() - for path in updated_js: - app_manager.app_controller.invalidate_view(path) + if not success: + CONSOLE.print(f"[bold red]Failed to reload {updated_python}") - if updated_python: - # Update the nested dependencies of our app that are brought into runtime - # and still hold on to an outdated version of the module. We reload these - # so they'll proceed to fetch and update themselves with the contents of the new module. - all_updated_components = list( - app_manager.get_submodules_with_objects( - app_manager.module, objects_to_reload - ) - ) - LOGGER.debug( - f"Found dependent modules: {all_updated_components} ({app_manager.module}: {objects_to_reload})" + # Handle JS changes + if updated_js: + await app_manager.app_compiler.run_builder_plugins( + limit_paths=list(updated_js) ) + for path in updated_js: + app_manager.app_controller.invalidate_view(path) - for updated_module in all_updated_components: - importlib.reload(updated_module) - - # Now we re-mount the app entrypoint, which should initialize the changed - # controllers with their new values - app_manager.update_module() - - # Clear the cache so changes to the python logic should re-calculate - # their ssr views - if hasattr("render_ssr", "_cache"): - lru_cache: LRUCache = getattr(render_ssr, "_cache") - LOGGER.debug(f"Clearing SSR cache of {len(lru_cache.cache)} items") - lru_cache.clear() - - app_manager.restart_server() - - if updated_js or updated_python: - # Wait up to 5s for our webserver to start, so when we push our refresh - # websocket the new page is ready immediately. - start_time = time() - max_wait_time = 5 - while time() - start_time < max_wait_time: - if app_manager.is_port_open(host, port): - CONSOLE.print(f"[blue]Webserver is ready on {host}:{port}!") - break - sleep(0.1) # Short sleep to prevent busy-waiting - - watcher_webservice.notification_queue.put(True) + # Wait for server to be ready + start_time = time() + while time() - start_time < 5: + if app_manager.is_port_open(host, port): + break + await asyncio.sleep(0.1) + watcher_webservice.notification_queue.put(True) CONSOLE.print(f"[bold green]🚀 App relaunched in {time() - start:.2f} seconds") - # Install a signal handler to catch SIGINT and try to - # shut down gracefully - def graceful_shutdown(signum, frame): - if watcher_webservice is not None: - watcher_webservice.stop() + def handle_shutdown(signum, frame): + watcher_webservice.stop() CONSOLE.print("[yellow]Services shutdown, now exiting...") exit(0) - signal(SIGINT, graceful_shutdown) + signal(SIGINT, handle_shutdown) watchdog = build_common_watchdog( package, - update_build, + lambda metadata: asyncio.run(handle_file_changes(metadata)), subscribe_to_mountaineer=subscribe_to_mountaineer, ) watchdog.start_watching() @@ -349,21 +219,14 @@ def handle_build( :param minify: Minify the JS bundle, strip debug symbols """ - app_manager = HotReloadManager.from_webcontroller(webcontroller) + app_manager = DevAppManager.from_webcontroller(webcontroller) + app_manager.js_compiler.live_reload_port = None - js_compiler = ClientBuilder( - app_manager.app_controller, - live_reload_port=None, - ) - client_compiler = ClientCompiler( - app_manager.app_controller.view_root, - app_manager.app_controller, - ) start = time() # Build the latest client support files (useServer) - asyncio.run(js_compiler.build_all()) - asyncio.run(client_compiler.run_builder_plugins()) + asyncio.run(app_manager.js_compiler.build_all()) + asyncio.run(app_manager.app_compiler.run_builder_plugins()) # Compile the final bundle # This requires us to get each entrypoint, which should just be the controllers diff --git a/mountaineer/client_builder/builder.py b/mountaineer/client_builder/builder.py index 5d86e812..6c0748ba 100644 --- a/mountaineer/client_builder/builder.py +++ b/mountaineer/client_builder/builder.py @@ -66,11 +66,20 @@ def __init__( self.openapi_action_converter = OpenAPIToTypescriptActionConverter() self.openapi_link_converter = OpenAPIToTypescriptLinkConverter() - self.app = app - self.view_root = ManagedViewPath.from_view_root(app.view_root) self.live_reload_port = live_reload_port self.build_cache = build_cache + self._openapi_action_specs: dict[str, dict[Any, Any]] | None = None + self._openapi_render_specs: dict[str, RenderSpec] | None = None + + self.update_controller(app) + + def update_controller(self, controller: AppController): + self.app = controller + self.view_root = ManagedViewPath.from_view_root(controller.view_root) + self._openapi_action_specs = None + self._openapi_render_specs = None + async def build_all(self): # Totally clear away the old build cache, so we start fresh # and don't have additional files hanging around @@ -161,7 +170,7 @@ def generate_model_definitions(self): ) def _generate_controller_schema(self, controller: ControllerBase): - action_spec_openapi = self.openapi_action_specs[controller] + action_spec_openapi = self.openapi_action_specs[controller.__class__.__name__] try: action_base = OpenAPIDefinition(**action_spec_openapi) @@ -171,7 +180,7 @@ def _generate_controller_schema(self, controller: ControllerBase): ) raise e - render_spec_openapi = self.openapi_render_specs[controller] + render_spec_openapi = self.openapi_render_specs[controller.__class__.__name__] render_base = ( OpenAPISchema(**render_spec_openapi.spec) if render_spec_openapi.spec @@ -510,9 +519,13 @@ def cache_is_outdated(self): cached_metadata = self.build_cache / "client_builder_openapi.json" cached_contents = { controller_definition.controller.__class__.__name__: { - "action": self.openapi_action_specs[controller_definition.controller], + "action": self.openapi_action_specs[ + controller_definition.controller.__class__.__name__ + ], "render": asdict( - self.openapi_render_specs[controller_definition.controller] + self.openapi_render_specs[ + controller_definition.controller.__class__.__name__ + ] ), } for controller_definition in self.app.controllers @@ -610,14 +623,14 @@ def openapi_action_specs(self): are defined differently. We internally cache this for all stages that require it. """ - if not hasattr(self, "_openapi_action_specs"): - self._openapi_action_specs: dict[ControllerBase, dict[Any, Any]] = {} + if self._openapi_action_specs is None: + self._openapi_action_specs = {} for controller_definition in self.app.controllers: controller = controller_definition.controller - self._openapi_action_specs[controller] = self.openapi_from_controller( - controller_definition - ) + self._openapi_action_specs[ + controller.__class__.__name__ + ] = self.openapi_from_controller(controller_definition) return self._openapi_action_specs @@ -631,8 +644,8 @@ def openapi_render_specs(self): defined controllers with no return model. """ - if not hasattr(self, "_openapi_render_specs"): - self._openapi_render_specs: dict[ControllerBase, RenderSpec] = {} + if self._openapi_render_specs is None: + self._openapi_render_specs = {} for controller_definition in self.app.controllers: controller = controller_definition.controller @@ -645,7 +658,7 @@ def openapi_render_specs(self): if render_model else None ) - self._openapi_render_specs[controller] = RenderSpec( + self._openapi_render_specs[controller.__class__.__name__] = RenderSpec( url=None if isinstance(controller, LayoutControllerBase) else controller.url, diff --git a/mountaineer/client_compiler/compile.py b/mountaineer/client_compiler/compile.py index 2939c19e..f423c9dd 100644 --- a/mountaineer/client_compiler/compile.py +++ b/mountaineer/client_compiler/compile.py @@ -21,14 +21,16 @@ class ClientCompiler: def __init__( self, - view_root: ManagedViewPath, app: AppController, ): - self.view_root = view_root - self.app = app - self.tmp_dir = Path(mkdtemp()) + self.update_controller(app) + + def update_controller(self, controller: AppController): + self.app = controller + self.view_root = controller.view_root + async def run_builder_plugins( self, limit_paths: list[Path] | None = None, diff --git a/mountaineer/hotreload.py b/mountaineer/hotreload.py new file mode 100644 index 00000000..c1d9bb1b --- /dev/null +++ b/mountaineer/hotreload.py @@ -0,0 +1,561 @@ +import ast +import importlib +import importlib.util +import inspect +import os +import sys +import time +from dataclasses import dataclass, field +from importlib.abc import SourceLoader +from pathlib import Path +from types import ModuleType + +from mountaineer.logging import setup_logger + +logger = setup_logger(__name__) + + +@dataclass +class DependencyNode: + module_name: str + file_path: Path + last_modified: float + imports: set[str] = field(default_factory=set) + imported_by: set[str] = field(default_factory=set) + subclasses: dict[str, set[str]] = field(default_factory=dict) + superclasses: dict[str, set[str]] = field(default_factory=dict) + + def __str__(self): + return ( + f"DependencyNode({self.module_name}):\n" + f" file: {self.file_path}\n" + f" imports: {self.imports}\n" + f" imported_by: {self.imported_by}\n" + ) + + +class HotReloader: + def __init__(self, root_package: str, package_path: Path, entrypoint: str): + logger.debug( + f"Initializing HotReloader with root_package={root_package}, path={package_path}, entrypoint={entrypoint}" + ) + self.root_package = root_package + self.package_path = package_path + self.entrypoint = entrypoint + self.allow_manual_import = False + + sys.path.insert(0, str(package_path.parent)) + sys.path.insert(0, str(package_path)) + + self.dependency_graph: dict[str, DependencyNode] = {} + self.module_cache: dict[str, ModuleType] = {} + + # Ensure the entrypoint is imported + if entrypoint not in sys.modules: + logger.info(f"Importing entrypoint: {entrypoint}") + importlib.import_module(entrypoint) + else: + logger.info(f"Entrypoint already imported: {entrypoint}") + + # Build the dependency graph by inspecting the already imported modules + self._build_dependency_graph() + self._log_dependency_state() + + # Once the initial graph is built, anything else that is tracked (like a new file) + # will have to be imported manually + self.allow_manual_import = True + + def _log_dependency_state(self): + """ + Log the current state of the dependency graph + + """ + logger.info("Current Dependency Graph State:") + for module_name, node in self.dependency_graph.items(): + logger.info(str(node)) + logger.info( + f"Module cache ID for {module_name}: {id(self.module_cache.get(module_name))}" + ) + if module_name in sys.modules: + logger.info( + f"sys.modules ID for {module_name}: {id(sys.modules[module_name])}" + ) + + def _build_dependency_graph(self) -> None: + """ + Build the dependency graph by inspecting the already imported modules in sys.modules. + + """ + logger.debug("Building dependency graph from already imported modules.") + static_modules = [module_name for module_name in sys.modules] + for module_name in static_modules: + if module_name.startswith(self.root_package): + self._import_and_track_module(module_name) + self._build_inheritance_tree() + + def _import_and_track_module(self, module_name: str) -> ModuleType | None: + if module_name not in sys.modules: + if self.allow_manual_import: + # Import the entrypoint if not already imported + try: + module = importlib.import_module(module_name) + sys.modules[module_name] = module + except Exception as e: + logger.error( + f"Failed to import new module {module_name}: {e}", exc_info=True + ) + return None + else: + # Do not import modules unless they are already imported + logger.debug( + f"Module {module_name} is not loaded and manual loading is not allowed. Skipping." + ) + return None + + try: + module = sys.modules[module_name] + + # Determine the module path + module_parts = module_name.split(".") + relative_parts = module_parts[1:] + relative_path = Path(*relative_parts) + + # First, check if it's a package (directory with __init__.py) + package_init = self.package_path / relative_path / "__init__.py" + if package_init.exists(): + module_path = package_init + else: + # Else, assume it's a module (.py file) + module_py = self.package_path / relative_path.with_suffix(".py") + if module_py.exists(): + module_path = module_py + else: + logger.error( + f"Found module {module_name} path not resolved, proposed {module_py}" + ) + return None + + # Create/update node + if module_name not in self.dependency_graph: + node = DependencyNode( + module_name=module_name, + file_path=module_path, + last_modified=module_path.stat().st_mtime, + ) + self.dependency_graph[module_name] = node + + else: + node = self.dependency_graph[module_name] + # Clear imports and inheritance relationships + node.imports.clear() + node.subclasses.clear() + node.superclasses.clear() + + self.module_cache[module_name] = module + + # Now track imports and inheritance + self._track_imports(module_path, node) + self._update_inheritance_relationships(module_name) + + return module + + except Exception as e: + logger.error(f"Failed to process module {module_name}: {e}", exc_info=True) + if module_name in self.dependency_graph: + del self.dependency_graph[module_name] + return None + + def _track_imports(self, module_path: Path, node: DependencyNode) -> None: + """ + Track all imports in a module file. + + """ + try: + logger.debug(f"Tracking imports for {module_path}") + with open(module_path) as f: + content = f.read() + tree = ast.parse(content, filename=str(module_path)) + + for ast_node in ast.walk(tree): + if isinstance(ast_node, ast.Import): + for name in ast_node.names: + logger.debug(f"Found import in {node.module_name}: {name.name}") + if name.name.startswith(self.root_package): + node.imports.add(name.name) + if name.name in self.dependency_graph: + self.dependency_graph[name.name].imported_by.add( + node.module_name + ) + + elif isinstance(ast_node, ast.ImportFrom): + logger.info("Found ImportFrom:") + logger.info(f" Module: {ast_node.module}") + logger.info(f" Level: {ast_node.level}") + logger.info( + f" Names: {[n.name + (' as ' + n.asname if n.asname else '') for n in ast_node.names]}" + ) + + for import_element in ast_node.names: + absolute_module = resolve_relative_import( + root_package=self.root_package, + current_module=node.module_name, + from_import=ast_node.module or "", + from_import_level=ast_node.level, + sys_modules=set(sys.modules.keys()), + import_name=import_element.name, + ) + logger.info(f"Resolved base module: {absolute_module}") + + if not absolute_module: + continue + + # Add to imports + node.imports.add(absolute_module) + + if absolute_module in self.dependency_graph: + logger.info( + f"Marking {absolute_module} as imported by {node.module_name}" + ) + self.dependency_graph[absolute_module].imported_by.add( + node.module_name + ) + + logger.info("=== Final import state ===") + logger.info(f"Imports: {node.imports}") + logger.info(f"Imported by: {node.imported_by}") + + except Exception as e: + logger.error( + f"Failed to parse imports for {module_path}: {e}", exc_info=True + ) + raise + + def _update_inheritance_relationships(self, module_name: str) -> None: + """ + Update inheritance relationships for a module after it's loaded. + + """ + if module_name not in self.module_cache: + return + + module = self.module_cache[module_name] + node = self.dependency_graph[module_name] + + for name, obj in inspect.getmembers(module, inspect.isclass): + if not obj.__module__.startswith(self.root_package): + continue + + for base in obj.__bases__: + if base.__module__.startswith(self.root_package): + base_module = base.__module__ + base_name = base.__name__ + + if base_module in self.dependency_graph: + base_node = self.dependency_graph[base_module] + if base_name not in base_node.subclasses: + base_node.subclasses[base_name] = set() + base_node.subclasses[base_name].add(name) + + if name not in node.superclasses: + node.superclasses[name] = {base_name} + else: + node.superclasses[name].add(base_name) + + def _get_affected_modules(self, changed_module: str) -> set[str]: + """ + Get all modules affected by a change. For now we assume that any file + that imports modified files is affected, which is recursively true + for the whole project. + + """ + affected = {changed_module} + to_process = {changed_module} + + while to_process: + current = to_process.pop() + if current not in self.dependency_graph: + continue + + node = self.dependency_graph[current] + + # Add modules that import this one (dependents) + for dependent in node.imported_by: + if dependent not in affected: + affected.add(dependent) + to_process.add(dependent) + + return affected + + def _sort_modules_by_dependencies(self, modules: set[str]) -> list[str]: + """ + Sort modules ensuring parent modules are reloaded before their children. + + """ + result = [] + visited = set() + + def visit(module_name: str): + if module_name in visited: + return + # Don't skip non-sys.modules modules! We need example.py + visited.add(module_name) + + # First visit imports since they're the deepest dependencies + if module_name in self.dependency_graph: + node = self.dependency_graph[module_name] + for imp in node.imports: + if imp in modules: # Make sure it's an affected module + visit(imp) + + result.append(module_name) + + # Still start with deepest paths first + modules_list = sorted(modules, key=lambda x: len(x.split(".")), reverse=True) + for module_name in modules_list: + visit(module_name) + + return result + + def reload_module(self, module_name: str) -> tuple[bool, list[str]]: + return self.reload_modules([module_name]) + + def reload_modules(self, module_names: list[str]) -> tuple[bool, list[str]]: + """ + Reload a module and all its dependencies. Note that this requires the underlying bite + length to have changed: https://bugs.python.org/issue31772 + + """ + logger.info(f"=== Starting reload of {module_names} ===") + self._log_dependency_state() + + # Try to import any modules that haven't been indexed yet + valid_modules: set[str] = set() + for module_name in module_names: + if module_name not in self.module_cache: + module = self._import_and_track_module(module_name) + if module: + valid_modules.add(module_name) + else: + valid_modules.add(module_name) + + reloaded_modules: list[str] = [] + invalid_modules = set(module_names) - valid_modules + + # Flag an error on any errors but continue reloading the ones that we're + # able to find + if invalid_modules: + logger.error(f"Modules {invalid_modules} are not loaded. Cannot reload.") + + if not valid_modules: + return False, reloaded_modules + + try: + affected = { + dependency + for module_name in valid_modules + for dependency in self._get_affected_modules(module_name) + } + logger.info(f"Affected modules: {affected}") + sorted_modules = self._sort_modules_by_dependencies(affected) + logger.info(f"Reload order: {sorted_modules}") + + for mod_name in sorted_modules: + try: + old_module = sys.modules[mod_name] + logger.info(f"Reloading {mod_name} (old id: {id(old_module)})") + + module = safe_reload(old_module) + logger.info(f"Reloaded {mod_name} (new id: {id(module)})") + + # Update cache and track reload + self.module_cache[mod_name] = module + reloaded_modules.append(mod_name) + + except Exception as e: + logger.error(f"Failed to reload {mod_name}: {e}", exc_info=True) + return False, reloaded_modules + + logger.info("=== Rebuilding inheritance tree ===") + self._build_inheritance_tree() + self._log_dependency_state() + + return True, reloaded_modules + + except Exception as e: + logger.error(f"Failed to reload {valid_modules}: {e}", exc_info=True) + return False, reloaded_modules + + def get_module_dependencies(self, module_name: str): + """ + Helper function for clients to access the current DAG node of a module. We return a sythetic + DAG with no dependencies. + + """ + logger.debug(f"Getting dependencies for {module_name}") + if module_name not in self.dependency_graph: + self._import_and_track_module(module_name) + + if module_name not in self.dependency_graph: + logger.debug(f"Module {module_name} not found in dependency graph") + return None + + return self.dependency_graph[module_name] + + def _build_inheritance_tree(self) -> None: + for module_name, module in self.module_cache.items(): + node = self.dependency_graph[module_name] + node.subclasses.clear() + node.superclasses.clear() + + for module_name, module in self.module_cache.items(): + node = self.dependency_graph[module_name] + for name, obj in inspect.getmembers(module, inspect.isclass): + if not obj.__module__.startswith(self.root_package): + continue + + for base in obj.__bases__: + if base.__module__.startswith(self.root_package): + base_module = base.__module__ + base_name = base.__name__ + + if base_module in self.dependency_graph: + base_node = self.dependency_graph[base_module] + if base_name not in base_node.subclasses: + base_node.subclasses[base_name] = set() + base_node.subclasses[base_name].add(name) + + if name not in node.superclasses: + node.superclasses[name] = {base_name} + else: + node.superclasses[name].add(base_name) + + +def resolve_relative_import( + *, + root_package: str, + current_module: str, + from_import: str, + from_import_level: int, + import_name: str, + sys_modules: set[str], +) -> str | None: + """ + Resolves the absolute module name for a potentially relative import. Let's consider some + different forms that an import can take: + + from myfile import MyClass + from mymodule import myfile + from .myfile import MyClass + from ..mymodule import myfile + from mypackage.mymodule import myfile + + The general structure is: + + from {dots:from_import_level}{from_import} import {import_name} + + """ + + # Handle invalid level + if from_import_level < 0: + logger.warning(f"Invalid negative level {from_import_level}") + return None + + # Handle absolute imports or local path imports (level = 0) + if from_import_level == 0: + # Prioritize local path imports, then absolute imports + proposed_components = [ + # Local path - import name as a module + [current_module, from_import, import_name], + # Local path - import name as a class/function + [current_module, from_import], + # Absolute import - import name as a module + [from_import, import_name], + # Absolute import - import name as a class/function + [from_import], + ] + proposed_paths = [ + ".".join([component for component in components if component.strip()]) + for components in proposed_components + ] + + for absolute_path in proposed_paths: + if absolute_path in sys_modules: + return absolute_path + + # Otherwise, we can't find the module + logger.warning( + f"No matching level-0 modules found in sys.modules, tried: {proposed_paths}" + ) + return None + + parts = current_module.split(".") + + # Handle invalid level that's too high and goes outside of the package + if from_import_level > len(parts): + logger.warning( + f"Invalid relative import: level {from_import_level} too high for module {current_module}" + ) + return None + + # __init__ files should just map to their parent module + if parts[-1] == "__init__": + parts = parts[:-1] + + # Get base path by removing the right number of components + # level=1: use current directory + # level=2: remove one directory + # level=3: remove two directories + # etc. + if from_import_level == 1: + # For single dot, use the current directory + base_path = ".".join(parts) + else: + # For multiple dots, remove (level-1) components + base_path = ".".join(parts[: -(from_import_level - 1)]) + + # Handle empty base path (we've gone up to root) + if not base_path: + base_path = root_package + + # Build final path - at this point the level should be 0 + return resolve_relative_import( + root_package=root_package, + current_module=base_path, + from_import=from_import, + from_import_level=0, + import_name=import_name, + sys_modules=sys_modules, + ) + + +def safe_reload(module: ModuleType) -> ModuleType: + """ + Safely reload a module, ensuring bytecode is regenerated when the source file + has been modified, even within the same second. Local fix for https://bugs.python.org/issue31772 + + Since this only runs in the hot-path for development code, the additional os stat overhead + isn't meaningful. + + :param module: The module to reload + + """ + # Get the module spec + spec = importlib.util.find_spec(module.__name__) + if not spec or not spec.origin: + return importlib.reload(module) + + # Remove any cached bytecode if source mtime matches current time + source_path = spec.origin + if source_path.endswith(".py"): + # Clear out the bytecode to force recompilation + current_time = int(time.time()) + try: + if spec.loader and isinstance(spec.loader, SourceLoader): + source_mtime = int(spec.loader.path_stats(source_path)["mtime"]) + if source_mtime == current_time: + bytecode_path = importlib.util.cache_from_source(source_path) + os.remove(bytecode_path) + except (AttributeError, OSError, KeyError): + pass + + return importlib.reload(module) diff --git a/pyproject.toml b/pyproject.toml index 1c0ccbd8..63f07173 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "mountaineer" version = "0.1.0" -description = "" +description = "Mountaineer is a batteries-included web framework for Python." authors = ["Pierce Freeman "] readme = "README.md"