-
-
Notifications
You must be signed in to change notification settings - Fork 1
feat: MkDocs #312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
feat: MkDocs #312
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,4 +16,4 @@ runs: | |
|
|
||
| - name: Install Dependencies | ||
| shell: bash | ||
| run: uv sync | ||
| run: uv sync --no-group bench | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| name: GitHub Pages | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - prod | ||
|
|
||
| jobs: | ||
| delivery: | ||
| name: Delivery | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write | ||
|
|
||
| steps: | ||
| - name: Run checkout | ||
| uses: actions/checkout@v6 | ||
|
|
||
| - name: Configure Git | ||
| run: | | ||
| git config --global user.name "github-pages[bot]" | ||
| git config --global user.email "github-pages[bot]@users.noreply.github.com" | ||
|
|
||
| - name: Set up environment | ||
| uses: ./.github/actions/environment | ||
|
|
||
| - name: Deploy MkDocs | ||
| shell: bash | ||
| run: uv run mkdocs gh-deploy --force |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,3 +19,6 @@ pyright: | |
|
|
||
| pytest: | ||
| uv run pytest | ||
|
|
||
| mkdocs: | ||
| uv run mkdocs serve | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| python-injection.remimd.dev |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| # Auto-imports | ||
|
|
||
| When using decorators to register dependencies, some implementations may never be explicitly imported in your project. This creates a problem: if a module is never imported, its decorators never execute, and the dependencies are never registered. | ||
|
|
||
| For example, if you register an implementation with `@injectable(on=AbstractDependency)` but never import that implementation module, the dependency won't be available for injection even though it's decorated. | ||
|
|
||
| To solve this, `python-injection` provides two solutions for automatically importing modules in a package. | ||
|
|
||
| !!! tip | ||
| Call auto-import functions early in your application startup, **before loading your profile** and before any dependency resolution occurs. | ||
|
|
||
| ## load_packages | ||
|
|
||
| The `load_packages` function imports all modules from the specified packages. Packages can be passed as module objects or as strings: | ||
| ```python | ||
| from injection.loaders import load_packages | ||
| from src import adapters, services | ||
|
|
||
| # Import all modules in adapters and services packages | ||
| load_packages(adapters, services) | ||
|
|
||
| # Or using string notation | ||
| load_packages("src.adapters", "src.services") | ||
| ``` | ||
|
|
||
| This is the simplest approach and works well when you want to import everything from specific packages. | ||
|
|
||
| ## PythonModuleLoader | ||
|
|
||
| For more control over which modules get imported, use `PythonModuleLoader` with a custom predicate function. Like `load_packages`, it accepts both module objects and strings: | ||
| ```python | ||
| from injection.loaders import PythonModuleLoader | ||
| from src import adapters, services | ||
|
|
||
| def predicate(module_name: str) -> bool: | ||
| # Only import modules containing "impl" in their name | ||
| return "impl" in module_name | ||
|
|
||
| PythonModuleLoader(predicate).load(adapters, services) | ||
| ``` | ||
|
|
||
| The predicate function receives the full module name (e.g., `"src.adapters.dependency_impl"`) and returns `True` if the module should be imported. | ||
|
|
||
| ### Factory methods | ||
|
|
||
| `PythonModuleLoader` provides three convenient factory methods for common filtering patterns: | ||
|
|
||
| **Filter by prefix:** | ||
| ```python | ||
| # Import only modules starting with "impl_" | ||
| PythonModuleLoader.startswith("impl_").load(adapters, services) | ||
| ``` | ||
|
|
||
| **Filter by suffix:** | ||
| ```python | ||
| # Import only modules ending with "_impl" | ||
| PythonModuleLoader.endswith("_impl").load(adapters, services) | ||
| ``` | ||
|
|
||
| **Filter by keyword marker:** | ||
| ```python | ||
| # Import only modules containing a specific comment | ||
| PythonModuleLoader.from_keywords("# auto-import").load(adapters, services) | ||
| ``` | ||
|
|
||
| This last approach is particularly useful for explicitly marking which modules should be auto-imported: | ||
| ```python | ||
| # auto-import | ||
|
|
||
| @injectable(on=AbstractDependency) | ||
| class Dependency(AbstractDependency): | ||
| ... | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| # Set up main functions | ||
|
|
||
| As we've seen throughout this guide, there can be quite a bit of setup around a main function: loading modules, defining scopes, injecting dependencies, loading profiles, etc. This becomes repetitive when you have multiple entry points in your project (CLI commands, etc.). | ||
|
|
||
| To solve this, `python-injection` provides **entrypoints**, a way to create custom decorators that encapsulate all your setup logic using a builder pattern. | ||
|
|
||
| ## Creating an entrypoint | ||
|
|
||
| Use the `@entrypointmaker` decorator to define your setup logic once: | ||
| ```python | ||
| from injection import adefine_scope | ||
| from injection.entrypoint import AsyncEntrypoint, Entrypoint, entrypointmaker | ||
| from injection.loaders import PythonModuleLoader | ||
|
|
||
| @entrypointmaker | ||
| def entrypoint[**P, T](self: AsyncEntrypoint[P, T]) -> Entrypoint[P, T]: | ||
| import src | ||
|
|
||
| module_loader = PythonModuleLoader.endswith("_impl") | ||
| return ( | ||
| self.inject() | ||
| .decorate(adefine_scope("lifespan", kind="shared")) | ||
| .async_to_sync() | ||
| .load_modules(module_loader, src) | ||
| ) | ||
| ``` | ||
|
|
||
| Now you can use your custom `@entrypoint` decorator on any function: | ||
| ```python | ||
| @entrypoint | ||
| async def main(dependency: Dependency): | ||
| # All setup is automatically applied | ||
| ... | ||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
| ``` | ||
|
|
||
| !!! info "Builder execution order" | ||
| The builder instructions execute in **reverse order**. Each method call re-decorates the main function, so the last instruction in the chain is call first. In the example above, modules are loaded first, then the function is converted to sync, then the scope is defined, and finally dependencies are injected. | ||
|
|
||
| ### Automatic execution | ||
|
|
||
| The entrypoint decorator accepts an optional `autocall` parameter. When set to `True`, the decorated function is automatically called: | ||
| ```python | ||
| @entrypoint(autocall=True) | ||
| async def main(dependency: Dependency): | ||
| # This function runs automatically when the module is executed | ||
| ... | ||
| ``` | ||
|
|
||
| This is particularly convenient for scripts and CLI commands where you want the entry point to execute immediately. | ||
|
|
||
| ## Integrating with ProfileLoader | ||
|
|
||
| If you're using a [`ProfileLoader`](profiles.md#profileloader) in your project, pass it to `@entrypointmaker` using the `profile_loader` parameter: | ||
| ```python | ||
| from injection.entrypoint import Entrypoint, entrypointmaker | ||
| from injection.loaders import ProfileLoader, PythonModuleLoader | ||
|
|
||
| profile_loader = ProfileLoader(...) | ||
|
|
||
| @entrypointmaker(profile_loader=profile_loader) | ||
| def entrypoint[**P, T](self: Entrypoint[P, T]) -> Entrypoint[P, T]: | ||
| import src | ||
|
|
||
| module_loader = PythonModuleLoader.endswith("_impl") | ||
| return ( | ||
| self.inject() | ||
| .load_profile(Profile.DEV) # Load a specific profile | ||
| .load_modules(module_loader, src) | ||
| ) | ||
| ``` | ||
|
|
||
| The `load_profile` method accepts a profile name and loads it before the main function executes. | ||
|
|
||
| ## Resolving dependencies in the setup | ||
|
|
||
| You can resolve dependencies from the setup function parameters. These dependencies must be registered in the default module (not in a profile-specific module) and should preferably be transient or constant. This is particularly useful for resolving configuration to determine which profile to load: | ||
| ```python | ||
| from dataclasses import dataclass | ||
| from injection import constant | ||
| from injection.entrypoint import Entrypoint, entrypointmaker | ||
| from injection.loaders import PythonModuleLoader | ||
| from os import getenv | ||
|
|
||
| @dataclass | ||
| class Config: | ||
| profile: Profile | ||
|
|
||
| @constant | ||
| def _config_factory() -> Config: | ||
| profile = Profile(getenv("PROFILE", "development")) | ||
| return Config(profile) | ||
|
|
||
| @entrypointmaker(profile_loader=profile_loader) | ||
| def entrypoint[**P, T](self: Entrypoint[P, T], config: Config) -> Entrypoint[P, T]: | ||
| import src | ||
|
|
||
| profile = config.profile # Use config to determine profile | ||
| suffixes = self.profile_loader.required_module_names(profile) | ||
| module_loader = PythonModuleLoader.endswith(*suffixes) | ||
| return ( | ||
| self.inject() | ||
| .load_profile(profile) | ||
| .load_modules(module_loader, src) | ||
| ) | ||
| ``` | ||
|
|
||
| In this example, `config` is resolved from the default module and used to dynamically load the appropriate profile. | ||
|
|
||
| !!! warning | ||
| Dependencies resolved in the entrypoint setup function must be registered in the default module (not in a profile-specific module) and should be transient or constant to avoid state issues. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.