Specials¶
Monkay provides several special features that enhance flexibility, handle lazy imports, manage extensions, and ensure thread-safety and asynchronous safety. These features can be critical when building dynamic applications that need fine-grained control over imports, settings, and extensions.
Overwriting the Used Package for Relative Imports¶
By default, Monkay sets the package
parameter to the __spec__.parent
of the module, which is typically the directory containing the module. This is helpful for resolving relative imports.
If you need to specify a different package for resolving relative imports, you can override this default behavior by providing the package
parameter when initializing Monkay.
Example:¶
from monkay import Monkay
monkay = Monkay(
globals(),
package="my_custom_package" # Override the default package for resolving relative imports
)
In this case, Monkay will use "my_custom_package"
to resolve relative imports instead of using the module's __spec__.parent
.
Adding Dynamically Lazy Imports¶
Monkay allows you to add lazy imports dynamically, either for regular imports or deprecated imports. There are two methods for this:
add_lazy_import(export_name, path_or_fn, *, no_hooks=False)
: Adds a new lazy import or fails if the import already exists.add_deprecated_lazy_import(export_name, DeprecatedImport, *, no_hooks=False)
: Adds a new deprecated lazy import or fails if it already exists.
Additionally, Monkay allows you to dynamically modify the __all__
variable, which is useful for managing the module's exports.
Hook Functions:¶
pre_add_lazy_import_hook(key, value, type_: Literal["lazy_import", "deprecated_lazy_import"])
: This hook is called before adding a lazy import or a deprecated lazy import.post_add_lazy_import_hook(key)
: This hook is called after a lazy import is added and is the proper place to update the__all__
variable.
The hooks only apply when manually adding a lazy import (not during the initial setup of Monkay).
Example: Automatically Update __all__
¶
from monkay import Monkay
# we use a set for __all__
__all__ = {"bar"}
monkay = Monkay(
# required for auto-hooking
globals(),
lazy_imports={
"bar": "tests.targets.fn_module:bar",
},
settings_path="settings_path:Settings",
post_add_lazy_import_hook=__all__.add # Automatically update __all__ when a lazy import is added
)
if monkay.settings.with_deprecated:
monkay.add_deprecated_lazy_import(
"deprecated",
{
"path": "tests.targets.fn_module:deprecated",
"reason": "old.",
"new_attribute": "super_new",
}
)
# __all__ has now also deprecated when with_deprecated is true
In this example, we dynamically add the bar
import, and when the deprecated import is added, the __all__
variable is automatically updated to include deprecated
.
Example: Prefix Lazy Imports¶
You can modify the name of lazy imports dynamically by using the pre_add_lazy_import_hook
. This hook allows you to change the name of the imported module before it's added.
from monkay import Monkay
# we use a set for __all__
__all__ = {"bar"}
def prefix_fn(name: str, value: Any, type_: str) -> tuple[str, Any]:
return f"{type_}_prefix_{name}", value # Prefix the lazy import name
monkay = Monkay(
# required for auto-hooking
globals(),
lazy_imports={
"bar": "tests.targets.fn_module:bar",
},
pre_add_lazy_import_hook=prefix_fn, # Apply prefix to the import name
post_add_lazy_import_hook=__all__.add # Update __all__ after adding the import
)
monkay.add_deprecated_lazy_import(
"deprecated",
{
"path": "tests.targets.fn_module:deprecated",
"reason": "old.",
"new_attribute": "super_new",
}
)
# Now __all__ and lazy_imports will include "lazy_import_prefix_bar" and "deprecated_prefix_deprecated"
This will prefix the lazy imports with lazy_import_prefix_
and deprecated_prefix_
, allowing for more organized import naming.
Manual Extension Setup¶
You can add extensions to Monkay manually via the add_extension
method. The add_extension
method allows you to specify additional behavior for handling extensions within your application.
Parameters:¶
use_overwrite
(default:True
): Specifies whether to use the temporary overwrite provided bywith_extensions
. Setting this toFalse
prevents overwriting.on_conflict
(default:"error"
): Defines the behavior when an extension name conflicts with an existing one. Options include:"error"
: Raise an error."keep"
: Keep the old extension."replace"
: Replace with the new extension.
Example:¶
monkay.add_extension(
MyExtension(),
use_overwrite=False,
on_conflict="replace"
)
This will add the MyExtension()
to Monkay, replacing any existing extension with the same name.
Temporary Disable Overwrite¶
You can disable overwriting for a specific scope by using the with_...
functions with None
. This is useful for preventing temporary changes to global state, such as when applying extensions or settings.
Example:¶
with monkay.with_settings(None) as new_settings:
# Here new_settings will be None and the old settings will be restored
assert new_settings is None
assert monkay.settings is old_settings
This temporarily disables the settings overwrite and restores the original settings.
Echoed Values¶
The with_
and set_
methods in Monkay return the passed variable as a context manager value. This allows you to modify the value temporarily within a specific scope.
Example:¶
with monkay.with_settings(Settings()) as new_settings:
# Perform actions with the settings overwrite
with monkay.with_settings(None) as new_settings2:
# Echoed value is None, meaning the overwrite is disabled
assert new_settings2 is None
# The settings revert to the original state
assert monkay.settings is old_settings
evaluate_preloads
¶
The evaluate_preloads
function allows you to load preloads across your application. It ensures that all preloads are evaluated, returning True
if all succeeded.
Parameters:¶
preloads
: A list of import paths to evaluate. See the Preloads section for special syntax.ignore_import_errors
: WhenTrue
(default), not all import paths need to be available. WhenFalse
, all imports must succeed.package
(Optional): Specify a different package for the preload imports. By default, Monkay uses the package of the Monkay instance.
Example:¶
monkay.evaluate_preloads(
preloads=["module1", "module2"],
ignore_import_errors=True
)
This will attempt to load the preloads module1
and module2
, ignoring any import errors that occur.
Typings¶
Monkay is fully typed, supporting Generic types for both the instance and settings. This allows you to define stricter types for instances and settings within your application.
Example:¶
from dataclasses import dataclass
from pydantic_settings import BaseSettings
from monkay import Monkay, ExtensionProtocol
class Instance: ...
@dataclass
class Extension(ExtensionProtocol["Instance", "Settings"]):
name: str = "hello"
def apply(self, monkay_instance: Monkay) -> None:
"""Do something here"""
class Settings(BaseSettings):
extensions: list[ExtensionProtocol["Instance", "Settings"]] = [Extension()]
monkay = Monkay[Instance, Settings](
globals(),
settings_path=Settings,
with_extensions=True,
settings_extensions_name="extensions"
)
In this example, we define the types for Instance
and Settings
and apply extensions with stricter typing.
Cages¶
Cages are a special feature of Monkay that handle global mutable structures in an async-safe manner. Cages ensure thread-safety and allow you to work with structures that are not natively async-safe (e.g., userland threads, asyncio).
Because Cages are essential to handling mutable structures in an async-safe way, they have their own documentation. Please refer to the Cages documentation for more details.