wishful 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wishful/__init__.py +99 -0
- wishful/__main__.py +72 -0
- wishful/cache/__init__.py +23 -0
- wishful/cache/manager.py +77 -0
- wishful/config.py +175 -0
- wishful/core/__init__.py +6 -0
- wishful/core/discovery.py +264 -0
- wishful/core/finder.py +77 -0
- wishful/core/loader.py +285 -0
- wishful/dynamic/__init__.py +8 -0
- wishful/dynamic/__init__.pyi +7 -0
- wishful/llm/__init__.py +5 -0
- wishful/llm/client.py +98 -0
- wishful/llm/prompts.py +74 -0
- wishful/logging.py +88 -0
- wishful/py.typed +0 -0
- wishful/safety/__init__.py +5 -0
- wishful/safety/validator.py +132 -0
- wishful/static/__init__.py +8 -0
- wishful/static/__init__.pyi +7 -0
- wishful/types/__init__.py +19 -0
- wishful/types/registry.py +333 -0
- wishful/ui.py +26 -0
- wishful-0.2.1.dist-info/METADATA +401 -0
- wishful-0.2.1.dist-info/RECORD +26 -0
- wishful-0.2.1.dist-info/WHEEL +4 -0
wishful/__init__.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""wishful - Just-in-Time code generation via import hooks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import sys
|
|
7
|
+
from typing import List
|
|
8
|
+
|
|
9
|
+
from wishful.cache import manager as cache
|
|
10
|
+
from wishful.config import configure, reset_defaults, settings
|
|
11
|
+
from wishful.core.discovery import set_context_radius as _set_context_radius
|
|
12
|
+
from wishful.core.finder import install as install_finder
|
|
13
|
+
from wishful.llm.client import GenerationError
|
|
14
|
+
from wishful.safety.validator import SecurityError
|
|
15
|
+
from wishful.types import type as type_decorator
|
|
16
|
+
|
|
17
|
+
# Install on import so `import magic.xyz` is intercepted immediately.
|
|
18
|
+
install_finder()
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"configure",
|
|
22
|
+
"clear_cache",
|
|
23
|
+
"inspect_cache",
|
|
24
|
+
"regenerate",
|
|
25
|
+
"reimport",
|
|
26
|
+
"set_context_radius",
|
|
27
|
+
"settings",
|
|
28
|
+
"reset_defaults",
|
|
29
|
+
"SecurityError",
|
|
30
|
+
"GenerationError",
|
|
31
|
+
"type",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
# Alias for cleaner API
|
|
35
|
+
type = type_decorator
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def clear_cache() -> None:
|
|
39
|
+
"""Delete all generated files from the cache directory."""
|
|
40
|
+
|
|
41
|
+
cache.clear_cache()
|
|
42
|
+
# Remove generated namespaces so they regenerate on next import.
|
|
43
|
+
for name in list(sys.modules):
|
|
44
|
+
if name.startswith("wishful.static") or name.startswith("wishful.dynamic"):
|
|
45
|
+
sys.modules.pop(name, None)
|
|
46
|
+
# Keep root wishful module to retain settings/logging; re-importer can handle children.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def inspect_cache() -> List[str]:
|
|
50
|
+
"""Return a list of cached module file paths as strings."""
|
|
51
|
+
|
|
52
|
+
return [str(p) for p in cache.inspect_cache()]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def regenerate(module_name: str) -> None:
|
|
56
|
+
"""Force regeneration of a module on next import.
|
|
57
|
+
|
|
58
|
+
Accepts module names with or without the wishful.static prefix.
|
|
59
|
+
Example: regenerate('users') or regenerate('wishful.static.users')
|
|
60
|
+
"""
|
|
61
|
+
# Ensure it has the wishful prefix
|
|
62
|
+
if not module_name.startswith("wishful"):
|
|
63
|
+
# Default to static namespace for backward compatibility
|
|
64
|
+
module_name = f"wishful.static.{module_name}"
|
|
65
|
+
|
|
66
|
+
cache.delete_cached(module_name)
|
|
67
|
+
sys.modules.pop(module_name, None)
|
|
68
|
+
importlib.invalidate_caches()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def set_context_radius(radius: int) -> None:
|
|
72
|
+
"""Adjust how many surrounding lines are sent as context to the LLM."""
|
|
73
|
+
_set_context_radius(radius)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def reimport(module_path: str):
|
|
77
|
+
"""Force a fresh import by clearing the module from cache.
|
|
78
|
+
|
|
79
|
+
This is especially useful for wishful.dynamic.* imports in loops,
|
|
80
|
+
where you want the LLM to regenerate with fresh context on each iteration.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
module_path: The full module path (e.g., 'wishful.dynamic.story')
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
The freshly imported module
|
|
87
|
+
|
|
88
|
+
Example:
|
|
89
|
+
>>> story = wishful.reimport('wishful.dynamic.story')
|
|
90
|
+
>>> next_line = story.cosmic_horror_next_sentence(current_text)
|
|
91
|
+
"""
|
|
92
|
+
# Clear from Python's module cache
|
|
93
|
+
sys.modules.pop(module_path, None)
|
|
94
|
+
|
|
95
|
+
# Import fresh (this triggers wishful's import hook if it's a wishful.* module)
|
|
96
|
+
return importlib.import_module(module_path)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
__version__ = "0.1.0"
|
wishful/__main__.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Command-line interface for wishful."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import wishful
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _print_usage() -> None:
|
|
9
|
+
print("wishful - Just-in-time Python module generation")
|
|
10
|
+
print("\nUsage:")
|
|
11
|
+
print(" python -m wishful inspect Show cached modules")
|
|
12
|
+
print(" python -m wishful clear Clear all cache")
|
|
13
|
+
print(" python -m wishful regen <module> Regenerate a module")
|
|
14
|
+
print("\nExamples:")
|
|
15
|
+
print(" python -m wishful inspect")
|
|
16
|
+
print(" python -m wishful regen wishful.text")
|
|
17
|
+
print(" python -m wishful clear")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _cmd_inspect() -> None:
|
|
21
|
+
cached = wishful.inspect_cache()
|
|
22
|
+
if not cached:
|
|
23
|
+
print("No cached modules found in", wishful.settings.cache_dir)
|
|
24
|
+
return
|
|
25
|
+
print(f"Cached modules in {wishful.settings.cache_dir}:")
|
|
26
|
+
for path in cached:
|
|
27
|
+
print(f" {path}")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _cmd_clear() -> None:
|
|
31
|
+
wishful.clear_cache()
|
|
32
|
+
print(f"Cleared all cached modules from {wishful.settings.cache_dir}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _cmd_regen(args: list[str]) -> None:
|
|
36
|
+
if not args:
|
|
37
|
+
raise ValueError("'regen' requires a module name")
|
|
38
|
+
module_name = args[0]
|
|
39
|
+
wishful.regenerate(module_name)
|
|
40
|
+
print(f"Regenerated {module_name} (will be re-created on next import)")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def main() -> None:
|
|
44
|
+
"""Main CLI entry point."""
|
|
45
|
+
args = sys.argv[1:]
|
|
46
|
+
if not args:
|
|
47
|
+
_print_usage()
|
|
48
|
+
sys.exit(0)
|
|
49
|
+
|
|
50
|
+
command, *rest = args
|
|
51
|
+
handlers = {
|
|
52
|
+
"inspect": _cmd_inspect,
|
|
53
|
+
"clear": _cmd_clear,
|
|
54
|
+
"regen": lambda: _cmd_regen(rest),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
handler = handlers.get(command)
|
|
58
|
+
if handler is None:
|
|
59
|
+
print(f"Unknown command: {command}")
|
|
60
|
+
print("Use 'python -m wishful' for help")
|
|
61
|
+
sys.exit(1)
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
handler()
|
|
65
|
+
except ValueError as exc:
|
|
66
|
+
print(f"Error: {exc}")
|
|
67
|
+
print("Usage: python -m wishful regen <module>")
|
|
68
|
+
sys.exit(1)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
main()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Cache utilities for wishful."""
|
|
2
|
+
|
|
3
|
+
from .manager import (
|
|
4
|
+
clear_cache,
|
|
5
|
+
delete_cached,
|
|
6
|
+
ensure_cache_dir,
|
|
7
|
+
has_cached,
|
|
8
|
+
inspect_cache,
|
|
9
|
+
module_path,
|
|
10
|
+
read_cached,
|
|
11
|
+
write_cached,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"read_cached",
|
|
16
|
+
"write_cached",
|
|
17
|
+
"clear_cache",
|
|
18
|
+
"inspect_cache",
|
|
19
|
+
"module_path",
|
|
20
|
+
"ensure_cache_dir",
|
|
21
|
+
"delete_cached",
|
|
22
|
+
"has_cached",
|
|
23
|
+
]
|
wishful/cache/manager.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
|
|
7
|
+
from wishful.config import settings
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def module_path(fullname: str) -> Path:
|
|
11
|
+
# Strip leading namespace "wishful" (and static/dynamic) and map dots to directories.
|
|
12
|
+
parts = fullname.split(".")
|
|
13
|
+
if parts[0] == "wishful":
|
|
14
|
+
parts = parts[1:]
|
|
15
|
+
# Also strip 'static' or 'dynamic' if present
|
|
16
|
+
if parts and parts[0] in ("static", "dynamic"):
|
|
17
|
+
parts = parts[1:]
|
|
18
|
+
relative = Path(*parts) if parts else Path("__init__")
|
|
19
|
+
return settings.cache_dir / relative.with_suffix(".py")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def dynamic_snapshot_path(fullname: str) -> Path:
|
|
23
|
+
"""Path for storing dynamic-generation snapshots without affecting cache."""
|
|
24
|
+
parts = fullname.split(".")
|
|
25
|
+
if parts[0] == "wishful":
|
|
26
|
+
parts = parts[1:]
|
|
27
|
+
if parts and parts[0] in ("static", "dynamic"):
|
|
28
|
+
parts = parts[1:]
|
|
29
|
+
relative = Path(*parts) if parts else Path("__init__")
|
|
30
|
+
return settings.cache_dir / "_dynamic" / relative.with_suffix(".py")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def ensure_cache_dir() -> Path:
|
|
34
|
+
settings.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
return settings.cache_dir
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def read_cached(fullname: str) -> Optional[str]:
|
|
39
|
+
path = module_path(fullname)
|
|
40
|
+
if path.exists():
|
|
41
|
+
return path.read_text()
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def write_cached(fullname: str, source: str) -> Path:
|
|
46
|
+
path = module_path(fullname)
|
|
47
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
path.write_text(source)
|
|
49
|
+
return path
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def write_dynamic_snapshot(fullname: str, source: str) -> Path:
|
|
53
|
+
path = dynamic_snapshot_path(fullname)
|
|
54
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
path.write_text(source)
|
|
56
|
+
return path
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def delete_cached(fullname: str) -> None:
|
|
60
|
+
path = module_path(fullname)
|
|
61
|
+
if path.exists():
|
|
62
|
+
path.unlink()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def clear_cache() -> None:
|
|
66
|
+
if settings.cache_dir.exists():
|
|
67
|
+
shutil.rmtree(settings.cache_dir)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def inspect_cache() -> List[Path]:
|
|
71
|
+
if not settings.cache_dir.exists():
|
|
72
|
+
return []
|
|
73
|
+
return sorted(settings.cache_dir.rglob("*.py"))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def has_cached(fullname: str) -> bool:
|
|
77
|
+
return module_path(fullname).exists()
|
wishful/config.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import builtins
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from textwrap import dedent
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from dotenv import load_dotenv
|
|
11
|
+
|
|
12
|
+
# Load environment variables from a local .env if present so users don't need to
|
|
13
|
+
# export them manually when running examples.
|
|
14
|
+
load_dotenv()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_DEFAULT_MODEL = os.getenv("DEFAULT_MODEL", os.getenv("WISHFUL_MODEL", "azure/gpt-4.1"))
|
|
18
|
+
_DEFAULT_SYSTEM_PROMPT = os.getenv(
|
|
19
|
+
"WISHFUL_SYSTEM_PROMPT",
|
|
20
|
+
dedent(
|
|
21
|
+
"""
|
|
22
|
+
You are a Python code generator. Output ONLY executable Python code.
|
|
23
|
+
- Do not wrap code in markdown fences.
|
|
24
|
+
- You may use any Python libraries available in the environment.
|
|
25
|
+
- Prefer simple, readable implementations.
|
|
26
|
+
- Avoid network calls, filesystem writes, subprocess, or shell execution.
|
|
27
|
+
- Include docstrings and type hints where helpful.
|
|
28
|
+
"""
|
|
29
|
+
).strip(),
|
|
30
|
+
)
|
|
31
|
+
_DEFAULT_LOG_LEVEL = os.getenv("WISHFUL_LOG_LEVEL", "WARNING").upper()
|
|
32
|
+
_DEFAULT_LOG_TO_FILE = os.getenv("WISHFUL_LOG_TO_FILE", "1") != "0"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class Settings:
|
|
37
|
+
"""Runtime configuration for wishful.
|
|
38
|
+
|
|
39
|
+
Values are mutable at runtime via :func:`configure` to make tests and user
|
|
40
|
+
code ergonomics-friendly. Defaults are sourced from environment variables.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
model: str = _DEFAULT_MODEL
|
|
44
|
+
cache_dir: Path = field(default_factory=lambda: Path(os.getenv("WISHFUL_CACHE_DIR", ".wishful")))
|
|
45
|
+
review: bool = os.getenv("WISHFUL_REVIEW", "0") == "1"
|
|
46
|
+
debug: bool = os.getenv("WISHFUL_DEBUG", "0") == "1"
|
|
47
|
+
allow_unsafe: bool = os.getenv("WISHFUL_UNSAFE", "0") == "1"
|
|
48
|
+
spinner: bool = os.getenv("WISHFUL_SPINNER", "1") != "0"
|
|
49
|
+
max_tokens: int = int(os.getenv("WISHFUL_MAX_TOKENS", "4096"))
|
|
50
|
+
temperature: float = float(os.getenv("WISHFUL_TEMPERATURE", "1"))
|
|
51
|
+
system_prompt: str = _DEFAULT_SYSTEM_PROMPT
|
|
52
|
+
log_level: str = _DEFAULT_LOG_LEVEL
|
|
53
|
+
log_to_file: bool = _DEFAULT_LOG_TO_FILE
|
|
54
|
+
|
|
55
|
+
def copy(self) -> "Settings":
|
|
56
|
+
return Settings(
|
|
57
|
+
model=self.model,
|
|
58
|
+
cache_dir=self.cache_dir,
|
|
59
|
+
review=self.review,
|
|
60
|
+
debug=self.debug,
|
|
61
|
+
allow_unsafe=self.allow_unsafe,
|
|
62
|
+
spinner=self.spinner,
|
|
63
|
+
max_tokens=self.max_tokens,
|
|
64
|
+
temperature=self.temperature,
|
|
65
|
+
system_prompt=self.system_prompt,
|
|
66
|
+
log_level=self.log_level,
|
|
67
|
+
log_to_file=self.log_to_file,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# Persist the settings object across module reloads (tests deliberately purge
|
|
72
|
+
# wishful.* modules). Stash it on `builtins` so all imports share the same
|
|
73
|
+
# instance even after sys.modules churn.
|
|
74
|
+
if getattr(builtins, "_wishful_settings", None) is None:
|
|
75
|
+
builtins._wishful_settings = Settings()
|
|
76
|
+
settings = builtins._wishful_settings # type: ignore[attr-defined]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Internal helper to load logging module robustly (handles altered sys.modules)
|
|
80
|
+
def _load_logging_module():
|
|
81
|
+
try:
|
|
82
|
+
from wishful import logging as logging_mod # type: ignore
|
|
83
|
+
return logging_mod
|
|
84
|
+
except Exception:
|
|
85
|
+
pass
|
|
86
|
+
try:
|
|
87
|
+
import importlib.util
|
|
88
|
+
import sys
|
|
89
|
+
path = Path(__file__).parent / "logging.py"
|
|
90
|
+
spec = importlib.util.spec_from_file_location("wishful.logging", path)
|
|
91
|
+
if spec and spec.loader:
|
|
92
|
+
logging_mod = importlib.util.module_from_spec(spec)
|
|
93
|
+
sys.modules["wishful.logging"] = logging_mod
|
|
94
|
+
spec.loader.exec_module(logging_mod) # type: ignore[arg-type]
|
|
95
|
+
return logging_mod
|
|
96
|
+
except Exception:
|
|
97
|
+
return None
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def configure(
|
|
102
|
+
*,
|
|
103
|
+
model: Optional[str] = None,
|
|
104
|
+
cache_dir: Optional[str | Path] = None,
|
|
105
|
+
review: Optional[bool] = None,
|
|
106
|
+
debug: Optional[bool] = None,
|
|
107
|
+
allow_unsafe: Optional[bool] = None,
|
|
108
|
+
spinner: Optional[bool] = None,
|
|
109
|
+
temperature: Optional[float] = None,
|
|
110
|
+
max_tokens: Optional[int] = None,
|
|
111
|
+
system_prompt: Optional[str] = None,
|
|
112
|
+
log_level: Optional[str] = None,
|
|
113
|
+
log_to_file: Optional[bool] = None,
|
|
114
|
+
) -> None:
|
|
115
|
+
"""Update global settings in-place.
|
|
116
|
+
|
|
117
|
+
All parameters are optional; only provided values overwrite current
|
|
118
|
+
settings. Accepts both strings and :class:`pathlib.Path` for `cache_dir`.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
updates = {
|
|
122
|
+
"model": model,
|
|
123
|
+
"cache_dir": Path(cache_dir) if cache_dir is not None else None,
|
|
124
|
+
"review": review,
|
|
125
|
+
"debug": debug,
|
|
126
|
+
"allow_unsafe": allow_unsafe,
|
|
127
|
+
"spinner": spinner,
|
|
128
|
+
"temperature": temperature,
|
|
129
|
+
"max_tokens": max_tokens,
|
|
130
|
+
"system_prompt": system_prompt,
|
|
131
|
+
"log_level": log_level.upper() if isinstance(log_level, str) else log_level,
|
|
132
|
+
"log_to_file": log_to_file,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# If debug explicitly enabled, default to DEBUG level and file logging unless
|
|
136
|
+
# caller provided overrides.
|
|
137
|
+
if debug is True:
|
|
138
|
+
if updates["log_level"] is None:
|
|
139
|
+
updates["log_level"] = "DEBUG"
|
|
140
|
+
if updates["log_to_file"] is None:
|
|
141
|
+
updates["log_to_file"] = True
|
|
142
|
+
# Spinners and heavy debug output don't mix nicely
|
|
143
|
+
if updates["spinner"] is None:
|
|
144
|
+
updates["spinner"] = False
|
|
145
|
+
|
|
146
|
+
for attr, value in updates.items():
|
|
147
|
+
if value is not None:
|
|
148
|
+
setattr(settings, attr, value)
|
|
149
|
+
|
|
150
|
+
# Reconfigure logging after updates (lazy import to avoid cycles during init)
|
|
151
|
+
logging_mod = _load_logging_module()
|
|
152
|
+
if logging_mod:
|
|
153
|
+
logging_mod.configure_logging(force=True)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def reset_defaults() -> None:
|
|
157
|
+
"""Reset settings to environment-driven defaults (useful for tests)."""
|
|
158
|
+
# Create new defaults and copy to existing settings object
|
|
159
|
+
# This ensures all existing references to settings get updated
|
|
160
|
+
defaults = Settings()
|
|
161
|
+
settings.model = defaults.model
|
|
162
|
+
settings.cache_dir = defaults.cache_dir
|
|
163
|
+
settings.review = defaults.review
|
|
164
|
+
settings.debug = defaults.debug
|
|
165
|
+
settings.allow_unsafe = defaults.allow_unsafe
|
|
166
|
+
settings.spinner = defaults.spinner
|
|
167
|
+
settings.max_tokens = defaults.max_tokens
|
|
168
|
+
settings.temperature = defaults.temperature
|
|
169
|
+
settings.system_prompt = defaults.system_prompt
|
|
170
|
+
settings.log_level = defaults.log_level
|
|
171
|
+
settings.log_to_file = defaults.log_to_file
|
|
172
|
+
|
|
173
|
+
logging_mod = _load_logging_module()
|
|
174
|
+
if logging_mod:
|
|
175
|
+
logging_mod.configure_logging(force=True)
|