yera 0.1.0__py3-none-any.whl → 0.2.0__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.
- infra_mvp/base_client.py +29 -0
- infra_mvp/base_server.py +68 -0
- infra_mvp/monitoring/__init__.py +15 -0
- infra_mvp/monitoring/metrics.py +185 -0
- infra_mvp/stream/README.md +56 -0
- infra_mvp/stream/__init__.py +14 -0
- infra_mvp/stream/__main__.py +101 -0
- infra_mvp/stream/agents/demos/financial/chart_additions_plan.md +170 -0
- infra_mvp/stream/agents/demos/financial/portfolio_assistant_stream.json +1571 -0
- infra_mvp/stream/agents/reference/blocks/action.json +170 -0
- infra_mvp/stream/agents/reference/blocks/button.json +66 -0
- infra_mvp/stream/agents/reference/blocks/date.json +65 -0
- infra_mvp/stream/agents/reference/blocks/input_prompt.json +94 -0
- infra_mvp/stream/agents/reference/blocks/layout.json +288 -0
- infra_mvp/stream/agents/reference/blocks/markdown.json +344 -0
- infra_mvp/stream/agents/reference/blocks/slider.json +67 -0
- infra_mvp/stream/agents/reference/blocks/spinner.json +110 -0
- infra_mvp/stream/agents/reference/blocks/table.json +56 -0
- infra_mvp/stream/agents/reference/chat_dynamics/branching_test_stream.json +145 -0
- infra_mvp/stream/app.py +49 -0
- infra_mvp/stream/container.py +112 -0
- infra_mvp/stream/schemas/__init__.py +16 -0
- infra_mvp/stream/schemas/agent.py +24 -0
- infra_mvp/stream/schemas/interaction.py +28 -0
- infra_mvp/stream/schemas/session.py +30 -0
- infra_mvp/stream/server.py +321 -0
- infra_mvp/stream/services/__init__.py +12 -0
- infra_mvp/stream/services/agent_service.py +40 -0
- infra_mvp/stream/services/event_converter.py +83 -0
- infra_mvp/stream/services/session_service.py +247 -0
- yera/__init__.py +50 -1
- yera/agents/__init__.py +2 -0
- yera/agents/context.py +41 -0
- yera/agents/dataclasses.py +69 -0
- yera/agents/decorator.py +207 -0
- yera/agents/discovery.py +124 -0
- yera/agents/typing/__init__.py +0 -0
- yera/agents/typing/coerce.py +408 -0
- yera/agents/typing/utils.py +19 -0
- yera/agents/typing/validate.py +206 -0
- yera/cli.py +377 -0
- yera/config/__init__.py +1 -0
- yera/config/config_utils.py +164 -0
- yera/config/function_config.py +55 -0
- yera/config/logging.py +18 -0
- yera/config/tool_config.py +8 -0
- yera/config2/__init__.py +8 -0
- yera/config2/dataclasses.py +534 -0
- yera/config2/keyring.py +270 -0
- yera/config2/paths.py +28 -0
- yera/config2/read.py +113 -0
- yera/config2/setup.py +109 -0
- yera/config2/setup_handlers/__init__.py +1 -0
- yera/config2/setup_handlers/anthropic.py +126 -0
- yera/config2/setup_handlers/azure.py +236 -0
- yera/config2/setup_handlers/base.py +125 -0
- yera/config2/setup_handlers/llama_cpp.py +205 -0
- yera/config2/setup_handlers/ollama.py +157 -0
- yera/config2/setup_handlers/openai.py +137 -0
- yera/config2/write.py +87 -0
- yera/dsl/__init__.py +0 -0
- yera/dsl/functions.py +94 -0
- yera/dsl/struct.py +20 -0
- yera/dsl/workspace.py +79 -0
- yera/events/__init__.py +57 -0
- yera/events/blocks/__init__.py +68 -0
- yera/events/blocks/action.py +57 -0
- yera/events/blocks/bar_chart.py +92 -0
- yera/events/blocks/base/__init__.py +20 -0
- yera/events/blocks/base/base.py +166 -0
- yera/events/blocks/base/chart.py +288 -0
- yera/events/blocks/base/layout.py +111 -0
- yera/events/blocks/buttons.py +37 -0
- yera/events/blocks/columns.py +26 -0
- yera/events/blocks/container.py +24 -0
- yera/events/blocks/date_picker.py +50 -0
- yera/events/blocks/exit.py +39 -0
- yera/events/blocks/form.py +24 -0
- yera/events/blocks/input_echo.py +22 -0
- yera/events/blocks/input_request.py +31 -0
- yera/events/blocks/line_chart.py +97 -0
- yera/events/blocks/markdown.py +67 -0
- yera/events/blocks/slider.py +54 -0
- yera/events/blocks/spinner.py +55 -0
- yera/events/blocks/system_prompt.py +22 -0
- yera/events/blocks/table.py +291 -0
- yera/events/models/__init__.py +39 -0
- yera/events/models/block_data.py +112 -0
- yera/events/models/in_event.py +7 -0
- yera/events/models/out_event.py +75 -0
- yera/events/runtime.py +187 -0
- yera/events/stream.py +91 -0
- yera/models/__init__.py +0 -0
- yera/models/data_classes.py +20 -0
- yera/models/llm_atlas_proxy.py +44 -0
- yera/models/llm_context.py +99 -0
- yera/models/llm_interfaces/__init__.py +0 -0
- yera/models/llm_interfaces/anthropic.py +153 -0
- yera/models/llm_interfaces/aws_bedrock.py +14 -0
- yera/models/llm_interfaces/azure_openai.py +143 -0
- yera/models/llm_interfaces/base.py +26 -0
- yera/models/llm_interfaces/interface_registry.py +74 -0
- yera/models/llm_interfaces/llama_cpp.py +136 -0
- yera/models/llm_interfaces/mock.py +29 -0
- yera/models/llm_interfaces/ollama_interface.py +118 -0
- yera/models/llm_interfaces/open_ai.py +150 -0
- yera/models/llm_workspace.py +19 -0
- yera/models/model_atlas.py +139 -0
- yera/models/model_definition.py +38 -0
- yera/models/model_factory.py +33 -0
- yera/opaque/__init__.py +9 -0
- yera/opaque/base.py +20 -0
- yera/opaque/decorator.py +8 -0
- yera/opaque/markdown.py +57 -0
- yera/opaque/opaque_function.py +25 -0
- yera/tools/__init__.py +29 -0
- yera/tools/atlas_tool.py +20 -0
- yera/tools/base.py +24 -0
- yera/tools/decorated_tool.py +18 -0
- yera/tools/decorator.py +35 -0
- yera/tools/tool_atlas.py +51 -0
- yera/tools/tool_utils.py +361 -0
- yera/ui/dist/404.html +1 -0
- yera/ui/dist/__next.__PAGE__.txt +10 -0
- yera/ui/dist/__next._full.txt +23 -0
- yera/ui/dist/__next._head.txt +6 -0
- yera/ui/dist/__next._index.txt +5 -0
- yera/ui/dist/__next._tree.txt +7 -0
- yera/ui/dist/_next/static/chunks/4c4688e1ff21ad98.js +1 -0
- yera/ui/dist/_next/static/chunks/652cd53c27924d50.js +4 -0
- yera/ui/dist/_next/static/chunks/786d2107b51e8499.css +1 -0
- yera/ui/dist/_next/static/chunks/7de9141b1af425c3.js +1 -0
- yera/ui/dist/_next/static/chunks/87ef65064d3524c1.js +2 -0
- yera/ui/dist/_next/static/chunks/a6dad97d9634a72d.js +1 -0
- yera/ui/dist/_next/static/chunks/a6dad97d9634a72d.js.map +1 -0
- yera/ui/dist/_next/static/chunks/c4c79d5d0b280aeb.js +1 -0
- yera/ui/dist/_next/static/chunks/dc2d2a247505d66f.css +5 -0
- yera/ui/dist/_next/static/chunks/f773f714b55ec620.js +37 -0
- yera/ui/dist/_next/static/chunks/turbopack-98b3031e1b1dbc33.js +4 -0
- yera/ui/dist/_next/static/lnhYLzJ1-a5EfNbW1uFF6/_buildManifest.js +11 -0
- yera/ui/dist/_next/static/lnhYLzJ1-a5EfNbW1uFF6/_clientMiddlewareManifest.json +1 -0
- yera/ui/dist/_next/static/lnhYLzJ1-a5EfNbW1uFF6/_ssgManifest.js +1 -0
- yera/ui/dist/_next/static/media/14e23f9b59180572-s.9c448f3c.woff2 +0 -0
- yera/ui/dist/_next/static/media/2a65768255d6b625-s.p.d19752fb.woff2 +0 -0
- yera/ui/dist/_next/static/media/2b2eb4836d2dad95-s.f36de3af.woff2 +0 -0
- yera/ui/dist/_next/static/media/31183d9fd602dc89-s.c4ff9b73.woff2 +0 -0
- yera/ui/dist/_next/static/media/3fcb63a1ac6a562e-s.2f77a576.woff2 +0 -0
- yera/ui/dist/_next/static/media/45ec8de98929b0f6-s.81056204.woff2 +0 -0
- yera/ui/dist/_next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
- yera/ui/dist/_next/static/media/65c558afe41e89d6-s.e2c8389a.woff2 +0 -0
- yera/ui/dist/_next/static/media/67add6cc0f54b8cf-s.8ce53448.woff2 +0 -0
- yera/ui/dist/_next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
- yera/ui/dist/_next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
- yera/ui/dist/_next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
- yera/ui/dist/_next/static/media/a8ff2d5d0ccb0d12-s.fc5b72a7.woff2 +0 -0
- yera/ui/dist/_next/static/media/aae5f0be330e13db-s.p.853e26d6.woff2 +0 -0
- yera/ui/dist/_next/static/media/b11a6ccf4a3edec7-s.2113d282.woff2 +0 -0
- yera/ui/dist/_next/static/media/b49b0d9b851e4899-s.4f3fa681.woff2 +0 -0
- yera/ui/dist/_next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
- yera/ui/dist/_next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
- yera/ui/dist/_next/static/media/favicon.0b3bf435.ico +0 -0
- yera/ui/dist/_not-found/__next._full.txt +14 -0
- yera/ui/dist/_not-found/__next._head.txt +6 -0
- yera/ui/dist/_not-found/__next._index.txt +5 -0
- yera/ui/dist/_not-found/__next._not-found.__PAGE__.txt +5 -0
- yera/ui/dist/_not-found/__next._not-found.txt +4 -0
- yera/ui/dist/_not-found/__next._tree.txt +2 -0
- yera/ui/dist/_not-found.html +1 -0
- yera/ui/dist/_not-found.txt +14 -0
- yera/ui/dist/agent-icon.svg +3 -0
- yera/ui/dist/favicon.ico +0 -0
- yera/ui/dist/file.svg +1 -0
- yera/ui/dist/globe.svg +1 -0
- yera/ui/dist/index.html +1 -0
- yera/ui/dist/index.txt +23 -0
- yera/ui/dist/logo/full_logo.png +0 -0
- yera/ui/dist/logo/rune_logo.png +0 -0
- yera/ui/dist/logo/rune_logo_borderless.png +0 -0
- yera/ui/dist/logo/text_logo.png +0 -0
- yera/ui/dist/next.svg +1 -0
- yera/ui/dist/send.png +0 -0
- yera/ui/dist/send_single.png +0 -0
- yera/ui/dist/vercel.svg +1 -0
- yera/ui/dist/window.svg +1 -0
- yera/utils/__init__.py +1 -0
- yera/utils/path_utils.py +38 -0
- yera-0.2.0.dist-info/METADATA +65 -0
- yera-0.2.0.dist-info/RECORD +190 -0
- {yera-0.1.0.dist-info → yera-0.2.0.dist-info}/WHEEL +1 -1
- yera-0.2.0.dist-info/entry_points.txt +2 -0
- yera-0.1.0.dist-info/METADATA +0 -11
- yera-0.1.0.dist-info/RECORD +0 -4
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import textwrap
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, TypeVar, get_type_hints
|
|
6
|
+
|
|
7
|
+
import tomllib
|
|
8
|
+
from pydantic import BaseModel, ValidationError
|
|
9
|
+
|
|
10
|
+
from yera.config.function_config import FunctionConfig, ParameterInfo, ParameterKind
|
|
11
|
+
|
|
12
|
+
TModel = TypeVar("TModel", bound=BaseModel)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def read_config_file(cfg_path: Path | str | None = None) -> str:
|
|
16
|
+
if cfg_path is None:
|
|
17
|
+
home = Path.home() / ".yera"
|
|
18
|
+
home.mkdir(parents=True, exist_ok=True)
|
|
19
|
+
cfg_path = home / "global_cfg.toml"
|
|
20
|
+
|
|
21
|
+
with open(cfg_path) as f:
|
|
22
|
+
return f.read()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def parse_toml_string(toml_str):
|
|
26
|
+
cfg = tomllib.loads(toml_str)
|
|
27
|
+
validate_config(cfg)
|
|
28
|
+
return cfg
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def validate_config(cfg_obj):
|
|
32
|
+
if not isinstance(cfg_obj, dict):
|
|
33
|
+
raise TypeError("cfg_obj must be a dictionary")
|
|
34
|
+
|
|
35
|
+
if "models" not in cfg_obj or "agents" not in cfg_obj:
|
|
36
|
+
raise KeyError("Configuration must contain 'models' and 'agents' keys")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def validate_schema(d: dict, cls: type[BaseModel]) -> bool:
|
|
40
|
+
try:
|
|
41
|
+
cls.model_validate(d)
|
|
42
|
+
return True
|
|
43
|
+
except ValidationError:
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def build_config_map(cfg_obj, cls: type[TModel]) -> dict[str, TModel]:
|
|
48
|
+
def walk(val, results, path):
|
|
49
|
+
if not isinstance(val, dict):
|
|
50
|
+
raise TypeError(
|
|
51
|
+
f"Something's gone wrong. The input is not a dict: {type(val)} {val}"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if validate_schema(val, cls):
|
|
55
|
+
results[path] = cls.model_validate(val)
|
|
56
|
+
return results
|
|
57
|
+
for k, v in val.items():
|
|
58
|
+
results = walk(v, results, f"{path}.{k}" if path else k)
|
|
59
|
+
|
|
60
|
+
return results
|
|
61
|
+
|
|
62
|
+
return walk(cfg_obj, {}, "")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _strip_decorators(source: str) -> str:
|
|
66
|
+
lines = source.splitlines()
|
|
67
|
+
# Remove leading decorators until we hit 'def'
|
|
68
|
+
body_start = 0
|
|
69
|
+
for i, line in enumerate(lines):
|
|
70
|
+
if line.lstrip().startswith("def "):
|
|
71
|
+
body_start = i
|
|
72
|
+
break
|
|
73
|
+
return "\n".join(lines[body_start:])
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _extract_function_body(source: str) -> str:
|
|
77
|
+
# source begins with 'def name(...):' line
|
|
78
|
+
lines = source.splitlines()
|
|
79
|
+
if not lines:
|
|
80
|
+
return ""
|
|
81
|
+
# Drop the signature line
|
|
82
|
+
body_lines = lines[1:]
|
|
83
|
+
# Dedent body
|
|
84
|
+
return textwrap.dedent("\n".join(body_lines)).rstrip()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _type_annotation_to_str(annotation: Any) -> str:
|
|
88
|
+
if annotation is inspect.Signature.empty:
|
|
89
|
+
return "Any"
|
|
90
|
+
try:
|
|
91
|
+
if hasattr(annotation, "__origin__") and annotation.__origin__ is not None:
|
|
92
|
+
# This is a generic type like list[str], dict[str, int], etc.
|
|
93
|
+
return str(annotation)
|
|
94
|
+
if hasattr(annotation, "__name__"):
|
|
95
|
+
# This is a simple type like str, int, etc.
|
|
96
|
+
return annotation.__name__
|
|
97
|
+
# Fallback to string representation
|
|
98
|
+
return str(annotation)
|
|
99
|
+
except Exception:
|
|
100
|
+
return "Any"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _map_parameter_kind_to_enum(kind: inspect._ParameterKind) -> ParameterKind:
|
|
104
|
+
"""Convert inspect.Parameter.kind to our enum."""
|
|
105
|
+
mapping = {
|
|
106
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD: ParameterKind.POSITIONAL_OR_KEYWORD,
|
|
107
|
+
inspect.Parameter.VAR_POSITIONAL: ParameterKind.VAR_POSITIONAL,
|
|
108
|
+
inspect.Parameter.KEYWORD_ONLY: ParameterKind.KEYWORD_ONLY,
|
|
109
|
+
inspect.Parameter.VAR_KEYWORD: ParameterKind.VAR_KEYWORD,
|
|
110
|
+
}
|
|
111
|
+
return mapping[kind]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def extract_function_config(func: Callable) -> FunctionConfig:
|
|
115
|
+
def extract_content():
|
|
116
|
+
source = inspect.getsource(func)
|
|
117
|
+
source = _strip_decorators(source)
|
|
118
|
+
content = _extract_function_body(source)
|
|
119
|
+
return content
|
|
120
|
+
|
|
121
|
+
sig = inspect.signature(func)
|
|
122
|
+
type_hints = get_type_hints(func)
|
|
123
|
+
|
|
124
|
+
def extract_parameters():
|
|
125
|
+
parameters = []
|
|
126
|
+
for name, param in sig.parameters.items():
|
|
127
|
+
if name == "self":
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
annotation = type_hints.get(name, inspect.Signature.empty)
|
|
131
|
+
type_annotation = _type_annotation_to_str(annotation)
|
|
132
|
+
kind = _map_parameter_kind_to_enum(param.kind)
|
|
133
|
+
default = (
|
|
134
|
+
param.default if param.default is not inspect.Signature.empty else None
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
parameters.append(
|
|
138
|
+
ParameterInfo(
|
|
139
|
+
name=name,
|
|
140
|
+
type_annotation=type_annotation,
|
|
141
|
+
kind=kind,
|
|
142
|
+
default=default,
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
return parameters
|
|
146
|
+
|
|
147
|
+
def extract_output_signature():
|
|
148
|
+
output_annotation = type_hints.get("return", inspect.Signature.empty)
|
|
149
|
+
output_signature = _type_annotation_to_str(output_annotation)
|
|
150
|
+
return output_signature
|
|
151
|
+
|
|
152
|
+
def extract_module_path():
|
|
153
|
+
module_path = getattr(func, "__module__", None)
|
|
154
|
+
if module_path == "__main__":
|
|
155
|
+
module_path = None # Don't store __main__ as it's not useful
|
|
156
|
+
return module_path
|
|
157
|
+
|
|
158
|
+
return FunctionConfig(
|
|
159
|
+
content=extract_content(),
|
|
160
|
+
name=func.__name__,
|
|
161
|
+
parameters=extract_parameters(),
|
|
162
|
+
output_signature=extract_output_signature(),
|
|
163
|
+
module_path=extract_module_path(),
|
|
164
|
+
)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, field_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ParameterKind(str, Enum):
|
|
9
|
+
POSITIONAL_OR_KEYWORD = "POSITIONAL_OR_KEYWORD"
|
|
10
|
+
VAR_POSITIONAL = "VAR_POSITIONAL" # *args
|
|
11
|
+
KEYWORD_ONLY = "KEYWORD_ONLY" # keyword-only parameters
|
|
12
|
+
VAR_KEYWORD = "VAR_KEYWORD" # **kwargs
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ParameterInfo(BaseModel):
|
|
16
|
+
name: str
|
|
17
|
+
type_annotation: str
|
|
18
|
+
kind: ParameterKind
|
|
19
|
+
default: Any | None = None
|
|
20
|
+
|
|
21
|
+
@field_validator("default")
|
|
22
|
+
@classmethod
|
|
23
|
+
def validate_default_for_kind(cls, v, info):
|
|
24
|
+
"""Validate parameter defaults and warn about mutable defaults."""
|
|
25
|
+
if "kind" in info.data:
|
|
26
|
+
kind = info.data["kind"]
|
|
27
|
+
name = info.data.get("name", "unknown")
|
|
28
|
+
|
|
29
|
+
# Validate that VAR_POSITIONAL and VAR_KEYWORD cannot have non-None defaults
|
|
30
|
+
if (
|
|
31
|
+
kind in (ParameterKind.VAR_POSITIONAL, ParameterKind.VAR_KEYWORD)
|
|
32
|
+
and v is not None
|
|
33
|
+
):
|
|
34
|
+
raise ValueError(
|
|
35
|
+
f"Parameter kind '{kind}' cannot have a default value (got {v})"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Warn about mutable defaults (Python best practice violation)
|
|
39
|
+
if v is not None and isinstance(v, list | dict | set):
|
|
40
|
+
warnings.warn(
|
|
41
|
+
f"Parameter '{name}' has a mutable default value ({type(v).__name__}). "
|
|
42
|
+
f"This can lead to unexpected behavior as the same object is reused across calls. "
|
|
43
|
+
f"Consider using 'None' as default and creating a new object inside the function.",
|
|
44
|
+
UserWarning,
|
|
45
|
+
stacklevel=3,
|
|
46
|
+
)
|
|
47
|
+
return v
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class FunctionConfig(BaseModel):
|
|
51
|
+
content: str
|
|
52
|
+
name: str
|
|
53
|
+
parameters: list[ParameterInfo] # Replaces input_signature and parameter_defaults
|
|
54
|
+
output_signature: str
|
|
55
|
+
module_path: str | None = None
|
yera/config/logging.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from logging.config import dictConfig
|
|
3
|
+
|
|
4
|
+
from uvicorn.config import LOGGING_CONFIG
|
|
5
|
+
|
|
6
|
+
# Make a copy to avoid mutating the original
|
|
7
|
+
LOG_CONFIG = deepcopy(LOGGING_CONFIG)
|
|
8
|
+
|
|
9
|
+
# Add root logger configuration for your application logs
|
|
10
|
+
LOG_CONFIG["root"] = {
|
|
11
|
+
"level": "INFO",
|
|
12
|
+
"handlers": ["default"],
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def setup_logging():
|
|
17
|
+
"""Configure logging for the application."""
|
|
18
|
+
dictConfig(LOG_CONFIG)
|