nl2sql-engine 0.1.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.
Files changed (192) hide show
  1. nl2sql/__init__.py +38 -0
  2. nl2sql/adapters/__init__.py +0 -0
  3. nl2sql/adapters/duckdb/__init__.py +0 -0
  4. nl2sql/adapters/duckdb/adapter.py +71 -0
  5. nl2sql/adapters/mssql/__init__.py +0 -0
  6. nl2sql/adapters/mssql/adapter.py +122 -0
  7. nl2sql/adapters/mysql/__init__.py +0 -0
  8. nl2sql/adapters/mysql/adapter.py +123 -0
  9. nl2sql/adapters/postgres/__init__.py +0 -0
  10. nl2sql/adapters/postgres/adapter.py +115 -0
  11. nl2sql/adapters/sqlalchemy_base/__init__.py +17 -0
  12. nl2sql/adapters/sqlalchemy_base/adapter.py +476 -0
  13. nl2sql/adapters/sqlalchemy_base/models.py +36 -0
  14. nl2sql/adapters/sqlite/__init__.py +0 -0
  15. nl2sql/adapters/sqlite/adapter.py +88 -0
  16. nl2sql/aggregation/__init__.py +3 -0
  17. nl2sql/aggregation/aggregator.py +98 -0
  18. nl2sql/aggregation/engines/__init__.py +3 -0
  19. nl2sql/aggregation/engines/polars_duckdb.py +125 -0
  20. nl2sql/api/__init__.py +0 -0
  21. nl2sql/api/auth_api.py +60 -0
  22. nl2sql/api/benchmark_api.py +114 -0
  23. nl2sql/api/datasource_api.py +132 -0
  24. nl2sql/api/indexing_api.py +59 -0
  25. nl2sql/api/llm_api.py +82 -0
  26. nl2sql/api/policy_api.py +135 -0
  27. nl2sql/api/query_api.py +138 -0
  28. nl2sql/api/result_api.py +24 -0
  29. nl2sql/api/settings_api.py +65 -0
  30. nl2sql/auth/__init__.py +8 -0
  31. nl2sql/auth/models.py +36 -0
  32. nl2sql/auth/rbac.py +25 -0
  33. nl2sql/cli/__init__.py +0 -0
  34. nl2sql/cli/checks.py +53 -0
  35. nl2sql/cli/commands/__init__.py +0 -0
  36. nl2sql/cli/commands/benchmark.py +34 -0
  37. nl2sql/cli/commands/doctor.py +49 -0
  38. nl2sql/cli/commands/indexing.py +126 -0
  39. nl2sql/cli/commands/info.py +25 -0
  40. nl2sql/cli/commands/install.py +27 -0
  41. nl2sql/cli/commands/policy.py +57 -0
  42. nl2sql/cli/commands/run.py +166 -0
  43. nl2sql/cli/commands/setup.py +415 -0
  44. nl2sql/cli/commands/visualize.py +34 -0
  45. nl2sql/cli/common/decorators.py +34 -0
  46. nl2sql/cli/config.py +24 -0
  47. nl2sql/cli/console.py +52 -0
  48. nl2sql/cli/demo/__init__.py +1 -0
  49. nl2sql/cli/demo/data.py +87 -0
  50. nl2sql/cli/demo/defaults.py +122 -0
  51. nl2sql/cli/demo/factory.py +289 -0
  52. nl2sql/cli/demo/manager.py +230 -0
  53. nl2sql/cli/demo/schemas.py +336 -0
  54. nl2sql/cli/demo/writers/__init__.py +0 -0
  55. nl2sql/cli/demo/writers/docker.py +182 -0
  56. nl2sql/cli/demo/writers/sqlite.py +88 -0
  57. nl2sql/cli/generators/datasources/__init__.py +3 -0
  58. nl2sql/cli/generators/datasources/generator.py +24 -0
  59. nl2sql/cli/generators/datasources/templates.py +7 -0
  60. nl2sql/cli/generators/env/__init__.py +3 -0
  61. nl2sql/cli/generators/env/generator.py +46 -0
  62. nl2sql/cli/generators/env/templates.py +25 -0
  63. nl2sql/cli/generators/llm/__init__.py +3 -0
  64. nl2sql/cli/generators/llm/generator.py +24 -0
  65. nl2sql/cli/generators/llm/templates.py +4 -0
  66. nl2sql/cli/generators/policies/__init__.py +3 -0
  67. nl2sql/cli/generators/policies/generator.py +20 -0
  68. nl2sql/cli/generators/policies/templates.py +2 -0
  69. nl2sql/cli/main.py +195 -0
  70. nl2sql/cli/reporting.py +878 -0
  71. nl2sql/cli/types.py +13 -0
  72. nl2sql/common/__init__.py +1 -0
  73. nl2sql/common/cancellation.py +25 -0
  74. nl2sql/common/context.py +5 -0
  75. nl2sql/common/errors.py +109 -0
  76. nl2sql/common/event_logger.py +88 -0
  77. nl2sql/common/exceptions.py +3 -0
  78. nl2sql/common/logger.py +119 -0
  79. nl2sql/common/metrics.py +50 -0
  80. nl2sql/common/resilience.py +59 -0
  81. nl2sql/common/settings.py +195 -0
  82. nl2sql/configs/__init__.py +6 -0
  83. nl2sql/configs/datasources.py +10 -0
  84. nl2sql/configs/llm.py +36 -0
  85. nl2sql/configs/manager.py +176 -0
  86. nl2sql/configs/policies.py +14 -0
  87. nl2sql/configs/sample_questions.py +11 -0
  88. nl2sql/configs/secrets.py +11 -0
  89. nl2sql/context.py +106 -0
  90. nl2sql/datasources/__init__.py +21 -0
  91. nl2sql/datasources/discovery.py +28 -0
  92. nl2sql/datasources/models.py +21 -0
  93. nl2sql/datasources/protocols.py +3 -0
  94. nl2sql/datasources/registry.py +172 -0
  95. nl2sql/evaluation/__init__.py +6 -0
  96. nl2sql/evaluation/benchmark_runner.py +320 -0
  97. nl2sql/evaluation/evaluator.py +134 -0
  98. nl2sql/evaluation/types.py +22 -0
  99. nl2sql/execution/__init__.py +4 -0
  100. nl2sql/execution/artifacts/__init__.py +3 -0
  101. nl2sql/execution/artifacts/parquet.py +41 -0
  102. nl2sql/execution/artifacts/store.py +165 -0
  103. nl2sql/execution/contracts.py +57 -0
  104. nl2sql/execution/execution_store.py +25 -0
  105. nl2sql/execution/executor/__init__.py +3 -0
  106. nl2sql/execution/executor/sql_executor.py +116 -0
  107. nl2sql/indexing/__init__.py +7 -0
  108. nl2sql/indexing/chunk_builder.py +227 -0
  109. nl2sql/indexing/embeddings.py +180 -0
  110. nl2sql/indexing/enrichment_service.py +316 -0
  111. nl2sql/indexing/models.py +209 -0
  112. nl2sql/indexing/orchestrator.py +90 -0
  113. nl2sql/indexing/vector_store.py +422 -0
  114. nl2sql/llm/__init__.py +8 -0
  115. nl2sql/llm/models.py +10 -0
  116. nl2sql/llm/registry.py +214 -0
  117. nl2sql/pipeline/__init__.py +1 -0
  118. nl2sql/pipeline/graph.py +73 -0
  119. nl2sql/pipeline/graph_utils.py +141 -0
  120. nl2sql/pipeline/nodes/__init__.py +25 -0
  121. nl2sql/pipeline/nodes/aggregator/__init__.py +4 -0
  122. nl2sql/pipeline/nodes/aggregator/node.py +55 -0
  123. nl2sql/pipeline/nodes/aggregator/prompts.py +20 -0
  124. nl2sql/pipeline/nodes/aggregator/schemas.py +28 -0
  125. nl2sql/pipeline/nodes/answer_synthesizer/__init__.py +4 -0
  126. nl2sql/pipeline/nodes/answer_synthesizer/node.py +98 -0
  127. nl2sql/pipeline/nodes/answer_synthesizer/prompts.py +19 -0
  128. nl2sql/pipeline/nodes/answer_synthesizer/schemas.py +24 -0
  129. nl2sql/pipeline/nodes/ast_planner/__init__.py +4 -0
  130. nl2sql/pipeline/nodes/ast_planner/node.py +104 -0
  131. nl2sql/pipeline/nodes/ast_planner/prompts.py +138 -0
  132. nl2sql/pipeline/nodes/ast_planner/schemas.py +236 -0
  133. nl2sql/pipeline/nodes/datasource_resolver/__init__.py +4 -0
  134. nl2sql/pipeline/nodes/datasource_resolver/node.py +253 -0
  135. nl2sql/pipeline/nodes/datasource_resolver/schemas.py +21 -0
  136. nl2sql/pipeline/nodes/decomposer/__init__.py +3 -0
  137. nl2sql/pipeline/nodes/decomposer/node.py +219 -0
  138. nl2sql/pipeline/nodes/decomposer/prompts.py +96 -0
  139. nl2sql/pipeline/nodes/decomposer/schemas.py +143 -0
  140. nl2sql/pipeline/nodes/executor/__init__.py +3 -0
  141. nl2sql/pipeline/nodes/executor/node.py +107 -0
  142. nl2sql/pipeline/nodes/generator/__init__.py +4 -0
  143. nl2sql/pipeline/nodes/generator/node.py +267 -0
  144. nl2sql/pipeline/nodes/generator/schemas.py +13 -0
  145. nl2sql/pipeline/nodes/global_planner/__init__.py +4 -0
  146. nl2sql/pipeline/nodes/global_planner/node.py +186 -0
  147. nl2sql/pipeline/nodes/global_planner/schemas.py +101 -0
  148. nl2sql/pipeline/nodes/refiner/__init__.py +4 -0
  149. nl2sql/pipeline/nodes/refiner/node.py +132 -0
  150. nl2sql/pipeline/nodes/refiner/prompts.py +28 -0
  151. nl2sql/pipeline/nodes/refiner/schemas.py +13 -0
  152. nl2sql/pipeline/nodes/schema_retriever/__init__.py +3 -0
  153. nl2sql/pipeline/nodes/schema_retriever/node.py +252 -0
  154. nl2sql/pipeline/nodes/schema_retriever/schema.py +27 -0
  155. nl2sql/pipeline/nodes/validator/__init__.py +7 -0
  156. nl2sql/pipeline/nodes/validator/node.py +839 -0
  157. nl2sql/pipeline/nodes/validator/schemas.py +12 -0
  158. nl2sql/pipeline/pipeline_runner.py +72 -0
  159. nl2sql/pipeline/routes.py +72 -0
  160. nl2sql/pipeline/runtime.py +153 -0
  161. nl2sql/pipeline/state.py +92 -0
  162. nl2sql/pipeline/subgraphs/__init__.py +5 -0
  163. nl2sql/pipeline/subgraphs/schemas.py +23 -0
  164. nl2sql/pipeline/subgraphs/sql_agent.py +167 -0
  165. nl2sql/public_api.py +199 -0
  166. nl2sql/schema/__init__.py +37 -0
  167. nl2sql/schema/in_memory_store.py +173 -0
  168. nl2sql/schema/protocol.py +88 -0
  169. nl2sql/schema/sqlite_store.py +233 -0
  170. nl2sql/schema/store.py +29 -0
  171. nl2sql/secrets/__init__.py +14 -0
  172. nl2sql/secrets/factory.py +85 -0
  173. nl2sql/secrets/interfaces.py +16 -0
  174. nl2sql/secrets/manager.py +139 -0
  175. nl2sql/secrets/models.py +56 -0
  176. nl2sql/secrets/providers/aws.py +30 -0
  177. nl2sql/secrets/providers/azure.py +49 -0
  178. nl2sql/secrets/providers/env.py +8 -0
  179. nl2sql/secrets/providers/hashi.py +46 -0
  180. nl2sql/services/__init__.py +0 -0
  181. nl2sql/services/callbacks/__init__.py +0 -0
  182. nl2sql/services/callbacks/monitor.py +84 -0
  183. nl2sql/services/callbacks/node_context.py +7 -0
  184. nl2sql/services/callbacks/node_handlers.py +187 -0
  185. nl2sql/services/callbacks/node_metrics.py +14 -0
  186. nl2sql/services/callbacks/presenter.py +12 -0
  187. nl2sql/services/callbacks/token_handler.py +56 -0
  188. nl2sql_engine-0.1.0.dist-info/METADATA +295 -0
  189. nl2sql_engine-0.1.0.dist-info/RECORD +192 -0
  190. nl2sql_engine-0.1.0.dist-info/WHEEL +5 -0
  191. nl2sql_engine-0.1.0.dist-info/entry_points.txt +9 -0
  192. nl2sql_engine-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,25 @@
1
+
2
+ ENV_FILE_TEMPLATE = """# NL2SQL Configuration for '{env}'
3
+
4
+ # --- Configuration Paths ---
5
+ DATASOURCE_CONFIG=configs/datasources{suffix}.yaml
6
+ POLICIES_CONFIG=configs/policies{suffix}.json
7
+ SECRETS_CONFIG=configs/secrets{suffix}.yaml
8
+ LLM_CONFIG=configs/llm{suffix}.yaml
9
+ VECTOR_STORE=data/vector_store_{env}
10
+ ROUTING_EXAMPLES=configs/sample_questions{suffix}.yaml
11
+ """
12
+
13
+ # Settings appended for specific environments only.
14
+ ENV_SPECIFIC_SETTINGS = {
15
+ "demo": (
16
+ "\n# --- Embeddings ---\n"
17
+ "# Local ONNX embeddings keep `nl2sql index` key-free. The first index run\n"
18
+ "# downloads a ~79 MB model. Running a query still needs an LLM key.\n"
19
+ "EMBEDDING_PROVIDER=local\n"
20
+ ),
21
+ }
22
+
23
+ ENV_SECRETS_HEADER = """
24
+ # --- Secrets ---
25
+ """
@@ -0,0 +1,3 @@
1
+ from .generator import LLMGenerator
2
+
3
+ __all__ = ["LLMGenerator"]
@@ -0,0 +1,24 @@
1
+ import yaml
2
+ from nl2sql.configs import LLMFileConfig
3
+
4
+ class LLMGenerator:
5
+ """Generates the content for llm.yaml."""
6
+
7
+ HEADER = "# NL2SQL LLM Configuration\n\n"
8
+
9
+ @staticmethod
10
+ def generate(config: LLMFileConfig) -> str:
11
+ """
12
+ Generates YAML content for LLM configuration.
13
+
14
+ Args:
15
+ config: LLMFileConfig object (Envelope).
16
+
17
+ Returns:
18
+ Formatted YAML string.
19
+ """
20
+ dumped_config = config.model_dump(mode="json", exclude_none=True)
21
+
22
+ yaml_block = yaml.safe_dump(dumped_config, sort_keys=False)
23
+
24
+ return LLMGenerator.HEADER + yaml_block
@@ -0,0 +1,4 @@
1
+ LLM_TEMPLATE = """# NL2SQL LLM Configuration
2
+
3
+ {llm_yaml_block}
4
+ """
@@ -0,0 +1,3 @@
1
+ from .generator import PolicyGenerator
2
+
3
+ __all__ = ["PolicyGenerator"]
@@ -0,0 +1,20 @@
1
+ import json
2
+ from nl2sql.configs import PolicyFileConfig
3
+
4
+ class PolicyGenerator:
5
+ """Generates the content for policies.json."""
6
+
7
+ @staticmethod
8
+ def generate(config: PolicyFileConfig) -> str:
9
+ """
10
+ Generates JSON content for Policy configuration.
11
+
12
+ Args:
13
+ config: PolicyFileConfig object (Envelope).
14
+
15
+ Returns:
16
+ Formatted JSON string.
17
+ """
18
+ json_dump = config.model_dump(mode="json")
19
+
20
+ return json.dumps(json_dump, indent=2)
@@ -0,0 +1,2 @@
1
+ POLICY_TEMPLATE = """{json_block}
2
+ """
nl2sql/cli/main.py ADDED
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env python3
2
+ """Unified CLI for the NL2SQL Ecosystem."""
3
+ import typer
4
+ import os
5
+ import sys
6
+ import pathlib
7
+ import json
8
+ from typing import Optional, List
9
+ from typing_extensions import Annotated
10
+
11
+ # Core Library Imports
12
+ from nl2sql.common.logger import configure_logging
13
+ from nl2sql.common.settings import reload_settings, settings
14
+ from nl2sql.context import NL2SQLContext
15
+ from nl2sql import BenchmarkConfig
16
+
17
+ # Local CLI Imports
18
+ from nl2sql.cli.commands.indexing import run_indexing
19
+ from nl2sql.cli.commands.benchmark import run_benchmark as exec_benchmark
20
+ from nl2sql.cli.commands.run import run_pipeline
21
+ from nl2sql.cli.commands.info import list_available_adapters
22
+ from nl2sql.cli.commands.doctor import doctor_command
23
+ from nl2sql.cli.commands.setup import setup_command
24
+ from nl2sql.cli.commands.install import install_command
25
+ from nl2sql.cli.commands.policy import app as policy_app
26
+ from nl2sql.cli.console import configure_output_encoding
27
+ from nl2sql.cli.types import RunConfig
28
+
29
+ app = typer.Typer(
30
+ name="nl2sql",
31
+ help="Production-Grade Natural Language to SQL Engine.",
32
+ no_args_is_help=True,
33
+ add_completion=False,
34
+ )
35
+
36
+ app.add_typer(policy_app, name="policy", help="Manage RBAC policies and security.")
37
+
38
+ DatasourceConfigOption = Annotated[Optional[pathlib.Path], typer.Option("--config", help="Path to datasource config YAML")]
39
+ SecretsConfigOption = Annotated[Optional[pathlib.Path], typer.Option("--secrets-config", help="Path to secrets config YAML")]
40
+ LLMConfigOption = Annotated[Optional[pathlib.Path], typer.Option("--llm-config", help="Path to LLM config YAML")]
41
+ VectorStoreOption = Annotated[Optional[str], typer.Option("--vector-store", help="Path to vector store directory")]
42
+
43
+
44
+ @app.callback()
45
+ def global_callback(
46
+ ctx: typer.Context,
47
+ env: Annotated[Optional[str], typer.Option("--env", help="Environment name to load (.env.<name>)")] = None,
48
+ env_file: Annotated[Optional[pathlib.Path], typer.Option("--env-file", help="Explicit path to an env file (wins over --env)")] = None,
49
+ ):
50
+ """
51
+ NL2SQL CLI Entry Point.
52
+ """
53
+ # `settings` is built when nl2sql.common.settings is first imported, which
54
+ # happens above. Setting the variables is therefore not enough on its own;
55
+ # the singleton has to be refreshed before any command builds a context.
56
+ if env:
57
+ os.environ["ENV"] = env
58
+ if env_file:
59
+ os.environ["ENV_FILE_PATH"] = str(env_file)
60
+ if env or env_file:
61
+ reload_settings()
62
+
63
+ @app.command()
64
+ def run(
65
+ query: Annotated[str, typer.Argument(help="Natural language query")],
66
+ ds_config_path: DatasourceConfigOption = None,
67
+ secrets_config_path: SecretsConfigOption = None,
68
+ ds_id: Annotated[Optional[str], typer.Option(help="Target specific datasource ID")] = None,
69
+ llm_config_path: LLMConfigOption = None,
70
+ vector_store_path: VectorStoreOption = None,
71
+ role: Annotated[str, typer.Option(help="Role ID for RBAC policies")] = "admin",
72
+ no_exec: Annotated[bool, typer.Option("--no-exec", help="Skip execution (plan & validate only)")] = False,
73
+ verbose: Annotated[bool, typer.Option("--verbose", "-v", help="Show detailed reasoning")] = False,
74
+ show_perf: Annotated[bool, typer.Option("--show-perf", help="Show performance metrics")] = False,
75
+ policies_config_path: Annotated[Optional[str], typer.Option("--policies-config", help="Path to policies config")] = None,
76
+ ):
77
+ """
78
+ Execute a query against the knowledge graph.
79
+ """
80
+
81
+ run_config = RunConfig(
82
+ query=query,
83
+ ds_id=ds_id,
84
+ role=role,
85
+ no_exec=no_exec,
86
+ verbose=verbose,
87
+ show_perf=show_perf
88
+ )
89
+ ctx = NL2SQLContext(ds_config_path, secrets_config_path, llm_config_path, vector_store_path, policies_config_path)
90
+
91
+ run_pipeline(run_config, ctx)
92
+
93
+
94
+ @app.command()
95
+ def index(
96
+ ds_config_path: DatasourceConfigOption = None,
97
+ secrets_config_path: SecretsConfigOption = None,
98
+ vector_store_path: VectorStoreOption = None,
99
+ llm_config_path: LLMConfigOption = None,
100
+ ):
101
+ """
102
+ Index schemas and examples into the Vector Store.
103
+ """
104
+ ctx = NL2SQLContext(ds_config_path, secrets_config_path, llm_config_path, vector_store_path)
105
+
106
+ run_indexing(ctx)
107
+
108
+ @app.command()
109
+ def doctor():
110
+ """
111
+ Diagnose environment issues (Python, Packages, Connectivity).
112
+ """
113
+ doctor_command()
114
+
115
+
116
+ @app.command()
117
+ def setup(
118
+ demo: Annotated[bool, typer.Option("--demo", help="Quickstart specific demo environment")] = False,
119
+ docker: Annotated[bool, typer.Option("--docker", help="Use Docker for demo (Full fidelity)")] = False,
120
+ lite: Annotated[bool, typer.Option("--lite", help="Use local SQLite files for the demo (default)")] = False,
121
+ api_key: Annotated[Optional[str], typer.Option("--api-key", help="API Key for LLM provider (e.g. OpenAI)")] = None,
122
+ ):
123
+ """
124
+ Interactive setup wizard for first-time users.
125
+ """
126
+ if lite and docker:
127
+ raise typer.BadParameter("--lite and --docker are mutually exclusive; pick one.")
128
+
129
+ # Lite is the default: it only turns off when --docker is requested.
130
+ setup_command(demo=demo, lite=not docker, docker=docker, api_key=api_key)
131
+
132
+
133
+ @app.command()
134
+ def install(package: str):
135
+ """
136
+ Helper to install adapter packages (e.g. 'postgres').
137
+ """
138
+ install_command(package)
139
+
140
+
141
+ @app.command("list-adapters")
142
+ def list_adapters():
143
+ """
144
+ List all installed datasource adapters.
145
+ """
146
+ list_available_adapters()
147
+
148
+ @app.command()
149
+ def benchmark(
150
+ dataset: Annotated[pathlib.Path, typer.Option(help="Path to golden dataset YAML")],
151
+ ds_config_path: DatasourceConfigOption = None,
152
+ secrets_config_path: SecretsConfigOption = None,
153
+ vector_store_path: VectorStoreOption = None,
154
+ bench_config_path: Annotated[Optional[pathlib.Path], typer.Option(help="Path to LLM matrix config")] = None,
155
+ iterations: Annotated[int, typer.Option(help="Iterations per test case")] = 3,
156
+ routing_only: Annotated[bool, typer.Option(help="Verify routing only, skip SQL execution")] = False,
157
+ include_ids: Annotated[Optional[List[str]], typer.Option(help="Specific Test IDs to run")] = None,
158
+ export_path: Annotated[Optional[pathlib.Path], typer.Option(help="Export results to JSON/CSV")] = None,
159
+ ):
160
+ """
161
+ Run accuracy benchmarks against a golden dataset.
162
+ """
163
+
164
+
165
+ bench_run_config = BenchmarkConfig(
166
+ dataset_path=dataset,
167
+ config_path=ds_config_path,
168
+ bench_config_path=bench_config_path,
169
+ llm_config_path=None, # Matrix uses bench_config
170
+ iterations=iterations,
171
+ routing_only=routing_only,
172
+ include_ids=include_ids,
173
+ export_path=export_path,
174
+ vector_store_path=vector_store_path,
175
+ secrets_path=secrets_config_path,
176
+ stub_llm=False,
177
+ )
178
+
179
+ exec_benchmark(bench_run_config)
180
+
181
+
182
+ def main():
183
+ # The library no longer configures logging on import, so the application
184
+ # entry point owns it: without this call the CLI emits no log output.
185
+ configure_logging(
186
+ level="INFO",
187
+ json_format=(settings.observability_exporter == "otlp"),
188
+ )
189
+ # Before any command writes: rich emits symbols a legacy Windows code page
190
+ # cannot encode, and an unconfigured stream turns that into a crash.
191
+ configure_output_encoding()
192
+ app()
193
+
194
+ if __name__ == "__main__":
195
+ main()