Skip to content

Commit 101cc85

Browse files
authored
emergency release 0.3.0 (#20)
emergency release see changelog
1 parent ae070b4 commit 101cc85

File tree

10 files changed

+222
-112
lines changed

10 files changed

+222
-112
lines changed

docs/release-notes.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
# Release notes
22

3+
## Version 0.3.0
4+
5+
### Breaking
6+
7+
This is an emergency release. It removes the feature that implicitly evaluates settings during `__init__`. This
8+
is very error prone and can lead to two different versions of the same library in case the sys.path is manipulated.
9+
Also failed imports are not neccessarily side-effect free.
10+
11+
### Added
12+
13+
- `evaluate_settings` has now two extra keyword parameters: `onetime` and `ignore_preload_import_errors`.
14+
15+
### Changes
16+
17+
- `evaluate_settings` behaves like `evaluate_settings_once`. We will need this too often now and having two similar named versions is error-prone.
18+
- `evaluate_settings_once` is now deprecated.
19+
- Setting the `evaluate_settings` parameter in `__init__` is now an error.
20+
- For the parameter `ignore_import_errors` of `evaluate_settings` the default value is changed to `False`.
21+
322
## Version 0.2.2
423

524
### Added

docs/settings.md

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,16 @@ child.monkay.settings = lambda: monkay.settings
3838

3939
## Lazy settings setup
4040

41-
Like when using a settings forward it is possible to activate the settings later by assigning a string, a class or an settings instance
42-
to the settings attribute.
43-
For this provide an empty string to the settings_path variable.
44-
It ensures the initialization takes place.
41+
Settings are only evaluated when calling `evaluate_settings`. This means you can do a lazy setup like this:
4542

4643
``` python
4744
import os
4845
from monkay import Monkay
4946

5047
monkay = Monkay(
5148
globals(),
52-
# required for initializing settings
49+
# required for initializing settings feature
5350
settings_path=""
54-
evaluate_settings=False
5551
)
5652

5753
# somewhere later
@@ -65,38 +61,48 @@ else:
6561
monkay.settings = DebugSettings()
6662

6763
# now the settings are applied
68-
monkay.evaluate_settings_once()
64+
monkay.evaluate_settings()
6965
```
7066

71-
### `evaluate_settings_once` method
67+
## Multi stage settings setup
7268

73-
`evaluate_settings_once` has following keyword only parameter:
69+
By passing `ignore_import_errors=True` we can check multiple pathes if the config could load. We get a False as return value in case of not.
7470

75-
- `on_conflict`: Matches the values of add_extension but defaults to `error`.
76-
- `ignore_import_errors`: Suppress import related errors. Handles unset settings lenient. Defaults to `True`.
77-
78-
When run successfully the context-aware flag `settings_evaluated` is set. If the flag is set,
79-
the method becomes a noop until the flag is lifted by assigning new settings.
71+
``` python
72+
import os
73+
from monkay import Monkay
8074

81-
The return_value is `True` for a successful evaluation and `False` in the other case.
75+
monkay = Monkay(
76+
globals(),
77+
# required for initializing settings feature
78+
settings_path=""
79+
)
8280

83-
!!! Note
84-
`ignore_import_errors` suppresses also UnsetError which is raised when the settings are unset.
81+
def find_settings():
82+
for path in ["a.settings", "b.settings.develop"]:
83+
if monkay.evaluate_settings(ignore_import_errors=True):
84+
break
85+
```
8586

8687
### `evaluate_settings` method
8788

8889
There is also`evaluate_settings` which evaluates always, not checking for if the settings were
8990
evaluated already and not optionally ignoring import errors.
91+
The return_value is `True` for a successful evaluation and `False` in the other case.
9092
It has has following keyword only parameter:
9193

92-
- `on_conflict`: Matches the values of add_extension but defaults to `keep`.
93-
94-
It is internally used by `evaluate_settings_once` and will also set the `settings_evaluated` flag.
94+
- `on_conflict`: Matches the values of add_extension but defaults to `error`.
95+
- `onetime`: Evaluates the settings only one the first call. All other calls become noops. Defaults to `True`.
96+
- `ignore_import_errors`: Suppress import related errors concerning settings. Handles unset settings lenient. Defaults to `False`.
97+
- `ignore_preload_import_errors`: Suppress import related errors concerning preloads in settings. Defaults to `True`.
9598

9699
!!! Note
97100
`evaluate_settings` doesn't touch the settings when no `settings_preloads_name` and/or `settings_extensions_name` is set
98101
but will still set the `settings_evaluated` flag to `True`.
99102

103+
!!! Note
104+
`ignore_import_errors` suppresses also UnsetError which is raised when the settings are unset.
105+
100106
### `settings_evaluated` flag
101107

102108
Internally it is a property which sets the right flag. Either on the ContextVar or on the instance.

docs/specials.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,21 @@ with monkay.with_settings(Settings()) as new_settings:
128128
assert monkay.settings is old_settings
129129
```
130130

131+
## `evaluate_preloads`
132+
133+
`evaluate_preloads` is a way to load preloads everywhere in the application. It returns True if all preloads succeeded.
134+
This is useful together with `ignore_import_errors=True`.
135+
136+
### Parameters
137+
138+
- `preloads` (also positional): import strings to import. See [Preloads](./tutorial.md#preloads) for the special syntax.
139+
- `ignore_import_errors`: Ignore import errors of preloads. When `True` (default) not all import
140+
strings must be available. When `False` all must be available.
141+
- `package` (Optional). Provide a different package as parent package. By default (when empty) the package of the Monkay instance is used.
142+
143+
Note: `monkay.base` contains a slightly different `evaluate_preloads` which uses when no package is provided the `None` package. It doesn't require
144+
an Monkay instance either.
145+
131146
## Typings
132147

133148
Monkay is fully typed and its main class Monkay is a Generic supporting 2 type parameters:
@@ -139,7 +154,6 @@ This is protocol is runtime checkable and has also support for both paramers.
139154

140155
Here a combined example:
141156

142-
143157
```python
144158
from dataclasses import dataclass
145159

docs/tutorial.md

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ pip install monkay
1010

1111
### Usage
1212

13-
Probably in the main `__init__.py` you define something like this:
13+
Probably you define something like this:
1414

15-
``` python
15+
``` python title="foo/__init__.py"
1616
from monkay import Monkay
1717

1818
monkay = Monkay(
@@ -40,7 +40,18 @@ monkay = Monkay(
4040
)
4141
```
4242

43-
When providing your own `__all__` variable **after** providing Monkay or you want more controll, you can provide
43+
``` python title="foo/main.py"
44+
from foo import monkay
45+
def get_application():
46+
# sys.path updates
47+
important_preloads =[...]
48+
monkay.evaluate_preloads(important_preloads, ignore_import_errors=False)
49+
extra_preloads =[...]
50+
monkay.evaluate_preloads(extra_preloads)
51+
monkay.evaluate_settings()
52+
```
53+
54+
When providing your own `__all__` variable **after** providing Monkay or you want more control, you can provide
4455

4556
`skip_all_update=True`
4657

@@ -49,8 +60,7 @@ and update the `__all__` value via `Monkay.update_all_var` if wanted.
4960
!!! Warning
5061
There is a catch when using `settings_preloads_name` and/or `settings_preloads_name`.
5162
It is easy to run in circular dependency errors.
52-
For fixing the initialization the errors are by default catched and ignored.
53-
But this means, you have to apply them later via `evaluate_settings_once` later.
63+
But this means, you have to apply them later via `evaluate_settings` later.
5464
For more informations see [Settings preloads and/or extensions](#settings-extensions-andor-preloads)
5565

5666

@@ -128,6 +138,17 @@ class Settings(BaseSettings):
128138
extensions: list[Any] = []
129139
```
130140

141+
``` python title="main.py"
142+
143+
def get_application():
144+
# initialize the loaders/sys.path
145+
# add additional preloads
146+
monkay.evaluate_preloads(...)
147+
monkay.evaluate_settings()
148+
149+
app = get_application()
150+
```
151+
131152
And voila settings are now available from monkay.settings as well as settings. This works only when all settings arguments are
132153
set via environment or defaults.
133154
Note the `uncached_imports`. Because temporary overwrites should be possible, we should not cache this import. The settings
@@ -197,12 +218,11 @@ get_value_from_settings(monkay.settings, "foo")
197218
```
198219
#### Settings extensions and/or preloads
199220

200-
When using `settings_preloads_name` and/or `settings_extensions_name` it is easy to run in circular dependency issues
201-
especcially with `pydantic_settings`.
202-
For mitigating this such import errors are catched and silenced in init.
221+
When using `settings_preloads_name` and/or `settings_extensions_name` we need to call in the setup of the application
222+
`evaluate_settings()`. Otherwise we may would end up with circular depdendencies, missing imports and wrong library versions.
203223

204224
This means however the preloads are not loaded as well the extensions initialized.
205-
For initializing it later, we need `evaluate_settings_once`.
225+
For initializing it later, we need `evaluate_settings`.
206226

207227
Wherever the settings are expected we can add it.
208228

@@ -214,7 +234,7 @@ from functools import lru_cache
214234
@lru_cache
215235
def get_edgy():
216236
import edgy
217-
edgy.monkay.evaluate_settings_once()
237+
edgy.monkay.evaluate_settings(ignore_import_errors=False)
218238
return edgy
219239

220240
class SettingsForward:
@@ -228,7 +248,7 @@ or
228248
```python title="foo/main.py"
229249
def get_application():
230250
import foo
231-
foo.monkay.evaluate_settings_once(ignore_import_errors=False)
251+
foo.monkay.evaluate_settings(ignore_import_errors=False)
232252
app = App()
233253

234254
foo.monkay.set_instance(app)
@@ -237,15 +257,11 @@ def get_application():
237257
app = get_application()
238258
```
239259

240-
For performance reasons you may want to skip to try to import the settings in init:
241-
242-
`evaluate_settings=False` will disable the evaluation.
243-
244260
You may want to not silence the import error like in monkay `<0.2.0`, then pass
245261

246262
`ignore_settings_import_errors=False` to the init.
247263

248-
More information can be found in [Settings `evaluate_settings_once`](./settings.md#evaluate_settings_once-method)
264+
More information can be found in [Settings `evaluate_settings`](./settings.md#evaluate_settings-method)
249265

250266
#### Pathes
251267

@@ -278,14 +294,18 @@ class Settings(BaseSettings):
278294
preloads: list[str] = ["preloader:preloader"]
279295
```
280296

297+
!!! Warning
298+
Settings preloads are only executed after executing `evaluate_settings()`. Preloads given in the `__init__` are evaluated instantly.
299+
You can however call `evaluate_preloads` directly.
300+
301+
281302
#### Using the instance feature
282303

283304
The instance feature is activated by providing a boolean (or a string for an explicit name) to the `with_instance`
284305
parameter.
285306

286307
For entrypoints you can set now the instance via `set_instance`. A good entrypoint is the init and using the settings:
287308

288-
289309
``` python title="__init__.py"
290310
import os
291311
from monkay import Monkay, load
@@ -299,6 +319,7 @@ monkay = Monkay(
299319
settings_extensions_name="extensions",
300320
)
301321

322+
monkay.evaluate_settings()
302323
monkay.set_instance(load(settings.APP_PATH))
303324
```
304325

@@ -340,7 +361,6 @@ class Settings(BaseSettings):
340361
preloads: list[str] = ["preloader:preloader"]
341362
extensions: list[Any] = [Extension]
342363
APP_PATH: str = "settings.App"
343-
344364
```
345365

346366
##### Reordering extension order dynamically
@@ -358,7 +378,6 @@ via the parameter `extension_order_key_fn`. It takes a key function which is exp
358378

359379
You can however intermix both.
360380

361-
362381
## Tricks
363382

364383
### Type-checker friendly lazy imports
@@ -385,15 +404,13 @@ monkay = Monkay(
385404
)
386405
```
387406

388-
389407
### Static `__all__`
390408

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

394412
Let's extend the former example:
395413

396-
397414
``` python
398415

399416
import os

monkay/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# SPDX-FileCopyrightText: 2024-present alex <devkral@web.de>
22
#
33
# SPDX-License-Identifier: BSD-3-Clauses
4-
__version__ = "0.2.2"
4+
__version__ = "0.3.0"

monkay/base.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
import warnings
4-
from collections.abc import Collection
4+
from collections.abc import Collection, Iterable
55
from importlib import import_module
66
from typing import Any
77

@@ -70,3 +70,21 @@ def get_value_from_settings(settings: Any, name: str) -> Any:
7070
return getattr(settings, name)
7171
except AttributeError:
7272
return settings[name]
73+
74+
75+
def evaluate_preloads(
76+
preloads: Iterable[str], *, ignore_import_errors: bool = True, package: str | None = None
77+
) -> bool:
78+
no_errors: bool = True
79+
for preload in preloads:
80+
splitted = preload.rsplit(":", 1)
81+
try:
82+
module = import_module(splitted[0], package)
83+
except (ImportError, AttributeError) as exc:
84+
if not ignore_import_errors:
85+
raise exc
86+
no_errors = False
87+
continue
88+
if len(splitted) == 2:
89+
getattr(module, splitted[1])()
90+
return no_errors

0 commit comments

Comments
 (0)