Skip to content

Commit

Permalink
Static __all__, enhanced find_missing and hooks (#4)
Browse files Browse the repository at this point in the history
Changes:

- expand find_missing
- add sorted_exports
- bump version
- add hooks
- handle sets properly
  • Loading branch information
devkral authored Nov 13, 2024
1 parent 5c2eef8 commit 73a915f
Show file tree
Hide file tree
Showing 16 changed files with 649 additions and 108 deletions.
2 changes: 1 addition & 1 deletion docs/helpers.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

Monkay comes with some helpers

- `load(path, *, allow_splits=":.", package=None)`: Load a path like Monkay. `allow_splits` allows to configure if attributes are seperated via . or :.
- `load(path, *, allow_splits=":.", package=None)`: Load a path like Monkay. `allow_splits` allows to configure if attributes are separated via . or :.
When both are specified, both split ways are possible (Default).
- `load_any(module_path, potential_attrs, *, non_first_deprecated=False, package=None)`: Checks for a module if any attribute name matches. Return attribute value or raises ImportError when non matches.
When `non_first_deprecated` is `True`, a DeprecationMessage is issued for the non-first attribute which matches. This can be handy for deprecating module interfaces.
Expand Down
12 changes: 12 additions & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Release notes

## Version 0.0.5

### Added

- `sorted_exports` for sorted `__all__` exports.
- Hooks for add_lazy_import, add_deprecated_lazy_import.

### Changed

- `find_missing` test method has some different error names.
- `find_missing` doesn't require the all_var anymore.

## Version 0.0.4

### Added
Expand Down
90 changes: 90 additions & 0 deletions docs/specials.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,93 @@

Provide the `package` parameter to Monkay. By default it is set to the `__spec__.parent` of the module.
For a toplevel module it is the same name like the module.

## Adding dynamically lazy imports

For adding lazy imports there are two methods:

- `add_lazy_import(export_name, path_or_fn, *, no_hooks=False)`: Adding new lazy import or fail if already exist.
- `add_deprecated_lazy_import(export_name, DeprecatedImport, *, no_hoosk=False)`: Adding new deprecated lazy import or fail if already exist.

By default the `__all__` variable is not modified but sometimes this is desirable.

For this cases hooks exist:

- `pre_add_lazy_import_hook(key, value, type_: Literal["lazy_import" | "deprecated_lazy_import"])`: Wrap around key and value and takes as third parameter the type.
- `post_add_lazy_import_hook(key)`: The way to go to update the `__all__` variable dynamically.

The hooks are only executed when manually adding a lazy import and not during the setup of Monkay.

### Example: Automatically update `__all__`

``` python

from monkay import Monkay

# we use a set
__all__ = {"bar"}

monkay = Monkay(
# required for autohooking
globals(),
lazy_imports={
"bar": "tests.targets.fn_module:bar",
},
settings_path="settings_path:Settings",
post_add_lazy_import_hook=__all__.add
)

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
```

### Example: prefix lazy imports

``` python

from monkay import Monkay

# we use a set
__all__ = {"bar"}

def prefix_fn(name: str, value: Any, type_: str) -> tuple[str, Any]:
return f"{type_}_prefix_{name}", value

monkay = Monkay(
# required for autohooking
globals(),
lazy_imports={
"bar": "tests.targets.fn_module:bar",
},
pre_add_lazy_import_hook=prefix_fn,
post_add_lazy_import_hook=__all__.add
)
monkay.add_deprecated_lazy_import(
"deprecated",
{
"path": "tests.targets.fn_module:deprecated",
"reason": "old",
"new_attribute": "super_new",
}
)
# __all__, lazy_imports has now also deprecated under a type prefix name
# but we can skip the hooks with no_hooks=True

monkay.add_deprecated_lazy_import(
"deprecated",
{
"path": "tests.targets.fn_module:deprecated",
"reason": "old",
"new_attribute": "super_new",
},
no_hooks=True
)
```
24 changes: 15 additions & 9 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ For tests, but not limited to, Monkay provides three methods returning a context
- `with_instance(instance, * apply_extensions=False,use_extensions_overwrite=True)`


## Check lazy imports
## Check imports and exports

Monkay provides the debug method `find_missing(*, all_var=None, search_pathes=None, ignore_deprecated_import_errors=False, require_search_path_all_var=True)`.
Monkay provides the debug method `find_missing(*, all_var=True, search_pathes=None, ignore_deprecated_import_errors=False, require_search_path_all_var=True)`.
It is quite expensive so it should be only called for debugging, testing and in error cases.
It returns a dictionary containing items which had issues, e.g. imports failed or not in `__all__` variable.

When providing `search_pathes` (module pathes as string), all exports are checked if they are in the value set of Monkey.

When providing `__all__` as `all_var`, it is checked for all imports.
When providing `__all__` or `True` as `all_var`, the variable or in case of `True` the module `__all__` is checked for missing exports/imports.

Returned is a dictionary in the format:

Expand All @@ -27,11 +27,13 @@ Returned is a dictionary in the format:

Errors:

- `all_var`: key is not in the provided `__all__` variable
- `import`: key had an ImportError
- `search_path_extra`: key (here a path) is not included in lazy imports.
- `search_path_import`: import of key (here the search path) failed
- `search_path_all_var`: module imported as search path had no `__all__`. This error can be disabled with `require_search_path_all_var=False`
- `not_in_all_var`: Key is not in the provided `__all__` variable.
- `missing_attr`: Key (path) which was defined in an `__all__` variable does not exist or raises an AttributeError.
- `missing_all_var`: Key (search path or main module) had no `__all__`. For search pathes this error can be disabled via `require_search_path_all_var=False`.
- `import`: Key (module or function) raised an ImportError.
- `shadowed`: Key is defined as lazy_import but defined in the main module so the lazy import is not used.
- `search_path_extra`: Key (path) is not included in lazy imports.
- `search_path_import`: Import of key (here the search path) failed.

### Ignore import errors when lazy import is deprecated

Expand All @@ -46,7 +48,11 @@ Using Monkay for tests is confortable and easy:
import edgy

def test_edgy_lazy_imports():
assert not edgy.monkay.find_missing(all_var=edgy.__all__, search_pathes=["edgy.core.files", "edgy.core.db.fields", "edgy.core.connection"])
missing = edgy.monkay.find_missing(all_var=edgy.__all__, search_pathes=["edgy.core.files", "edgy.core.db.fields", "edgy.core.connection"])
# remove false positives
if missing["AutoNowMixin"] == {"AutoNowMixin"}:
del missing["AutoNowMixin"]
assert not missing

```

Expand Down
87 changes: 86 additions & 1 deletion docs/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ When providing your own `__all__` variable **after** providing Monkay or you wan

and update the `__all__` value via `Monkay.update_all_var` if wanted.



#### Lazy imports

When using lazy imports the globals get an `__getattr__` injected. A potential old `__getattr__` is used as fallback when provided **before**
Expand Down Expand Up @@ -79,7 +81,7 @@ There is also a method:
Which can be used to clear the caches.


##### Monkey
##### Monkey


#### Using settings
Expand Down Expand Up @@ -255,3 +257,86 @@ There is a second more complicated way to reorder:
via the parameter `extension_order_key_fn`. It takes a key function which is expected to return a lexicographic key capable for ordering.

You can however intermix both.


## Tricks

### Type-checker friendly lazy imports

Additionally to the lazy imports you can define in the `TYPE_CHECKING` scope the imports of the types.

They are not loaded except when type checking.

``` python

from typing import TYPE_CHECKING

from monkay import Monkay

if TYPE_CHECKING:
from tests.targets.fn_module import bar

monkay = Monkay(
# required for autohooking
globals(),
lazy_imports={
"bar": "tests.targets.fn_module:bar",
},
)
```


### Static `__all__`

For autocompletions it is helpful to have a static `__all__` variable because many tools parse the sourcecode.
Handling the `__all__` manually is for small imports easy but for bigger projects problematic.

Let's extend the former example:


``` python

import os
from typing import TYPE_CHECKING

from monkay import Monkay

if TYPE_CHECKING:
from tests.targets.fn_module import bar

__all__ =["bar", "monkay", "stringify_all", "check"]

monkay = Monkay(
# required for autohooking
globals(),
lazy_imports={
"bar": "tests.targets.fn_module:bar",
},
skip_all_update=not os.environ.get("DEBUG")
# when printing all, lazy imports automatically add to __all__
post_add_lazy_import_hook=__all__.append if __name__ == "__main__" else None
)


def print_stringify_all(separate_by_category: bool=True) -> None:
print("__all__ = [\n{}\n]".format(
"\n,".join(
f'"{t[1]}"'
for t in monkay.sorted_exports(separate_by_category=separate_by_category)
)
))

def check() -> None:
if monkay.find_missing(search_pathes=["tests.targets.fn_module"]):
raise Exception()

if __name__ == "__main__":
# refresh __all__ to contain all lazy imports
__all__ = monkay.update_all_var(__all__)
print_stringify_all()
elif os.environ.get("DEBUG"):
check()
```

This way in a debug environment the imports are automatically checked.
And via `python -m mod` the new `__all__` can be exported.
2 changes: 1 addition & 1 deletion monkay/__about__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2024-present alex <devkral@web.de>
#
# SPDX-License-Identifier: BSD-3-Clauses
__version__ = "0.0.4"
__version__ = "0.0.5"
11 changes: 10 additions & 1 deletion monkay/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@
#
# SPDX-License-Identifier: BSD-3-Clauses

from .base import DeprecatedImport, ExtensionProtocol, Monkay, load, load_any
from .base import (
PRE_ADD_LAZY_IMPORT_HOOK,
DeprecatedImport,
ExtensionProtocol,
Monkay,
load,
load_any,
)

__all__ = [
"Monkay",
"SortedExportsEntry",
"DeprecatedImport",
"PRE_ADD_LAZY_IMPORT_HOOK",
"ExtensionProtocol",
"load",
"load_any",
Expand Down
Loading

0 comments on commit 73a915f

Please sign in to comment.