ApiLogicServer 14.5.0__py3-none-any.whl → 14.5.4__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.
- api_logic_server_cli/add_cust/add_cust.py +7 -21
- api_logic_server_cli/api_logic_server.py +4 -2
- api_logic_server_cli/api_logic_server_info.yaml +2 -2
- api_logic_server_cli/create_from_model/__pycache__/dbml.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/__pycache__/ont_build.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/dbml.py +3 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/api/api_discovery/{mcp_server_executor.py → mcp_discovery.py} +1 -43
- api_logic_server_cli/prototypes/basic_demo/customizations/config/server_setup.py +388 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/database/system/SAFRSBaseX.py +139 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/.DS_Store +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/README_mcp.md +3 -1
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/mcp_client_executor.py +144 -131
- api_logic_server_cli/prototypes/basic_demo/customizations/logic/declare_logic.py +22 -2
- api_logic_server_cli/prototypes/basic_demo/iteration/logic/declare_logic.py +1 -1
- api_logic_server_cli/prototypes/nw/logic/declare_logic.py +2 -2
- api_logic_server_cli/prototypes/nw_no_cust/.obsidian/app.json +1 -0
- api_logic_server_cli/prototypes/nw_no_cust/.obsidian/appearance.json +1 -0
- api_logic_server_cli/prototypes/nw_no_cust/.obsidian/core-plugins.json +31 -0
- api_logic_server_cli/prototypes/nw_no_cust/.obsidian/workspace.json +166 -0
- apilogicserver-14.5.4.dist-info/METADATA +168 -0
- {apilogicserver-14.5.0.dist-info → apilogicserver-14.5.4.dist-info}/RECORD +25 -43
- {apilogicserver-14.5.0.dist-info → apilogicserver-14.5.4.dist-info}/WHEEL +1 -1
- api_logic_server_cli/prototypes/basic_demo/customizations/api/api_discovery/proper_update_def.json +0 -71
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/1_langchain_loader.py +0 -71
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/2_gpt_mcp_prompt.txt +0 -19
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/multi_mcp_flow/multi_mcp_flow.png +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/multi_mcp_flow/multi_mcp_orchestration.yaml +0 -49
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/multi_mcp_flow/wny mcp flows.png +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/natlang_to_api.py +0 -73
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/curl.txt +0 -5
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/images/MCP Overview.png +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/images/MCP_Arch.png +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/images/MCP_Overview_Executor.png +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/invoke_llm/1 - prompt_messages_array.json +0 -10
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/invoke_llm/2 - completion_tool_context.json +0 -12
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/llm_schema.txt +0 -38
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/nw_swagger_2.yaml +0 -17393
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/nw_swagger_3.yaml +0 -16660
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/nw_swagger_3_relaxed.yaml +0 -109
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/proxy_server.py +0 -51
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/proxy_serverZ.py +0 -72
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/validate_jsonapi.py +0 -64
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/run_executor.py +0 -23
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/swagger_converter.py +0 -65
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/z_old/3_executor_test_agent.py +0 -52
- api_logic_server_cli/prototypes/manager/README_X.md +0 -663
- apilogicserver-14.5.0.dist-info/METADATA +0 -76
- {apilogicserver-14.5.0.dist-info → apilogicserver-14.5.4.dist-info}/entry_points.txt +0 -0
- {apilogicserver-14.5.0.dist-info → apilogicserver-14.5.4.dist-info}/licenses/LICENSE +0 -0
- {apilogicserver-14.5.0.dist-info → apilogicserver-14.5.4.dist-info}/top_level.txt +0 -0
|
@@ -25,28 +25,12 @@ Note: many require: rebuild-from-database --project_name=./ --db_url=sqlite:///d
|
|
|
25
25
|
'''
|
|
26
26
|
|
|
27
27
|
def add_genai_customizations(project: Project, do_show_messages: bool = True, do_security: bool = True):
|
|
28
|
-
""" Add customizations to genai (default creation)
|
|
28
|
+
""" Add customizations `prototypes/genai_demo` to genai (default creation)
|
|
29
29
|
|
|
30
30
|
0. Initial: create_project_and_overlay_prototypes() -- minor: just creates the readme
|
|
31
31
|
* When done with genai logic prompt, logic is pre-created (in logic/declare_logic.py)
|
|
32
32
|
1. Deep copy prototypes/genai_demo (adds logic and security, and custom end point)
|
|
33
33
|
|
|
34
|
-
WebGenAI DX:
|
|
35
|
-
|
|
36
|
-
0. Convention: click the Blue Button
|
|
37
|
-
* Home/Create Project
|
|
38
|
-
* Home/Open App
|
|
39
|
-
* Landing
|
|
40
|
-
* Overview[Manager]/Open
|
|
41
|
-
* Overview/GitHub
|
|
42
|
-
* App Home / Develop --> GitHub
|
|
43
|
-
0. demo --> codespaces. Where are instructions (what is CS, how do I load/run)?
|
|
44
|
-
1. Name can be any, iff created with APILOGICPROJECT_IS_GENAI_DEMO
|
|
45
|
-
2. Bypass duplicate discovery logic iff created with APILOGICPROJECT_IS_GENAI_DEMO
|
|
46
|
-
3. TODO:
|
|
47
|
-
* cd project
|
|
48
|
-
* als add-cust # add customizations
|
|
49
|
-
* run, and use place b2b order service - end point is not activated.
|
|
50
34
|
|
|
51
35
|
Args:
|
|
52
36
|
"""
|
|
@@ -251,7 +235,9 @@ def add_cust(project: Project, models_py_path: Path, project_name: str):
|
|
|
251
235
|
if not models_py_path.exists():
|
|
252
236
|
raise Exception("Customizations are northwind/genai-specific - models.py does not exist")
|
|
253
237
|
|
|
254
|
-
project_is_genai_demo = False
|
|
238
|
+
project_is_genai_demo = False
|
|
239
|
+
''' can't use project.is_genai_demo because this is not the create command...'''
|
|
240
|
+
|
|
255
241
|
if project.project_directory_path.joinpath('docs/project_is_genai_demo.txt').exists():
|
|
256
242
|
project_is_genai_demo = True
|
|
257
243
|
|
|
@@ -260,8 +246,8 @@ def add_cust(project: Project, models_py_path: Path, project_name: str):
|
|
|
260
246
|
add_nw_customizations(project=project, do_security=False)
|
|
261
247
|
log.info("\nNext step - add authentication:\n $ ApiLogicServer add-auth --db_url=auth\n\n")
|
|
262
248
|
|
|
263
|
-
elif project_is_genai_demo and create_utils.does_file_contain(search_for="Customer", in_file=models_py_path):
|
|
264
|
-
|
|
249
|
+
# elif project_is_genai_demo and create_utils.does_file_contain(search_for="Customer", in_file=models_py_path):
|
|
250
|
+
# add_genai_customizations(project=project, do_security=False)
|
|
265
251
|
|
|
266
252
|
elif project_name == 'sample_ai' and create_utils.does_file_contain(search_for="CustomerName = Column(Text", in_file=models_py_path):
|
|
267
253
|
cocktail_napkin_path = project.project_directory_path.joinpath('logic/cocktail-napkin.jpg')
|
|
@@ -271,7 +257,7 @@ def add_cust(project: Project, models_py_path: Path, project_name: str):
|
|
|
271
257
|
else:
|
|
272
258
|
add_sample_ai_iteration(project=project)
|
|
273
259
|
|
|
274
|
-
elif project_name == 'basic_demo' and create_utils.does_file_contain(search_for="Customer", in_file=models_py_path):
|
|
260
|
+
elif (project_is_genai_demo or project_name == 'basic_demo') and create_utils.does_file_contain(search_for="Customer", in_file=models_py_path):
|
|
275
261
|
cocktail_napkin_path = project.project_directory_path.joinpath('logic/cocktail-napkin.jpg')
|
|
276
262
|
is_customized = cocktail_napkin_path.exists()
|
|
277
263
|
if not is_customized:
|
|
@@ -12,9 +12,10 @@ ApiLogicServer CLI: given a database url, create [and run] customizable ApiLogic
|
|
|
12
12
|
Called from api_logic_server_cli.py, by instantiating the ProjectRun object.
|
|
13
13
|
'''
|
|
14
14
|
|
|
15
|
-
__version__ = "14.05.
|
|
15
|
+
__version__ = "14.05.04" # last public release: 14.05.00
|
|
16
16
|
recent_changes = \
|
|
17
17
|
f'\n\nRecent Changes:\n' +\
|
|
18
|
+
"\t05/20/2024 - 14.05.04: factored mcp filters with working date range (AND), email stub, use basic_demo custs for genai_demo \n"\
|
|
18
19
|
"\t05/16/2024 - 14.05.00: safrs 3.1.7, running mcp preview \n"\
|
|
19
20
|
"\t04/27/2024 - 14.04.00: Graphics preview, Vibe install fix, Improved IDE Chat Logic, MCP Exploration \n"\
|
|
20
21
|
"\t03/30/2024 - 14.03.25: WebGenAI fixes for Kafka and Keycloak \n"\
|
|
@@ -417,7 +418,8 @@ def create_project_and_overlay_prototypes(project: 'ProjectRun', msg: str) -> st
|
|
|
417
418
|
# readme now opens automatically, so use that...
|
|
418
419
|
shutil.move(project.project_directory_path.joinpath('readme.md'),
|
|
419
420
|
project.project_directory_path.joinpath('readme_standard.md'))
|
|
420
|
-
create_utils.copy_md(project = project, from_doc_file = "Sample-Genai.md", to_project_file='readme.md')
|
|
421
|
+
# create_utils.copy_md(project = project, from_doc_file = "Sample-Genai.md", to_project_file='readme.md')
|
|
422
|
+
create_utils.copy_md(project = project, from_doc_file = "Sample-Basic-Demo.md", to_project_file='readme.md')
|
|
421
423
|
|
|
422
424
|
if "postgres" or "mysql" in project.db_url:
|
|
423
425
|
fixup_devops_for_postgres_mysql(project)
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
last_created_date: May
|
|
1
|
+
last_created_date: May 20, 2025 15:00:15
|
|
2
2
|
last_created_project_name: ../../../servers/basic_demo
|
|
3
|
-
last_created_version: 14.
|
|
3
|
+
last_created_version: 14.05.03
|
|
Binary file
|
|
Binary file
|
|
@@ -74,6 +74,9 @@ class DBMLCreator(object):
|
|
|
74
74
|
"tool_type": "json-api",
|
|
75
75
|
"schema_version": "1.0",
|
|
76
76
|
"base_url": "http://localhost:5656/api",
|
|
77
|
+
"query_params": "- JSON:API custom filtering using a filter array (e.g., filter=[{\"name\":\"date_shipped\",\"op\":\"gt\",\"val\":\"2023-07-14\"}])",
|
|
78
|
+
"expected_response": "Respond with a JSON object with tool_type, schema_version, base_url, and an array of resources including: path, method, query_params array or body, headers, expected_output.",
|
|
79
|
+
"email_services": "iff email is requested, Send email by issing a POST request to the Email endpoint, setting the customer_id and message in the body.",
|
|
77
80
|
"description": f"API Logic Project: {self.mod_gen.project.project_name_last_node}",
|
|
78
81
|
"resources": [
|
|
79
82
|
]
|
|
@@ -62,55 +62,13 @@ def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_deco
|
|
|
62
62
|
pass
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
@app.route('/mcp_server_executor', methods=['GET'])
|
|
66
|
-
def mcp_server_executor(path=None):
|
|
67
|
-
''' sample response printed in mcp_client_executor.py:
|
|
68
|
-
FIXME - incorrect.
|
|
69
|
-
But do provide: https://localhost:5656/.well-known/mcp.json
|
|
70
|
-
```
|
|
71
|
-
MCP MCP Response (simulated):
|
|
72
|
-
{
|
|
73
|
-
"get_json": {
|
|
74
|
-
"filter": {
|
|
75
|
-
"filter": {
|
|
76
|
-
"credit_limit": {
|
|
77
|
-
"gt": 4000
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
"headers": {
|
|
81
|
-
"Accept": "application/vnd.api+json",
|
|
82
|
-
"Authorization": "Bearer your_token"
|
|
83
|
-
},
|
|
84
|
-
"type": "Customer",
|
|
85
|
-
"url": "http://localhost:5656/api/Customer"
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
"name": "mcp_server_executor",
|
|
89
|
-
"openapiUrl": "TUNNEL_URL/api/openapi.json",
|
|
90
|
-
"serverUrl": "TUNNEL_URL/api"
|
|
91
|
-
}
|
|
92
|
-
```
|
|
93
|
-
'''
|
|
94
|
-
get_json = request.get_json()
|
|
95
|
-
app_logger.info(f"mcp_server_executor sees mcp request: \n{json.dumps(get_json, indent=4)}")
|
|
96
|
-
|
|
97
|
-
# process verb, filter here (stub for now)
|
|
98
|
-
filter_json = get_json['filter'] # {"credit_limit": {"gt": 4000}} # todo: bunch'o parsing here
|
|
99
|
-
|
|
100
|
-
filter_json = {"name": "credit_limit", "op": "gt", "val":4000} # https://github.com/thomaxxl/safrs/wiki/JsonApi-filtering
|
|
101
|
-
filter = json.dumps(filter_json) # {"name": "credit_limit", "op": "gt", "val": 4000}
|
|
102
|
-
get_uri = get_json['url'] + '?filter=' + filter # get_uri = "http://localhost:5656/api/Customer?filter[credit_limit]=1000"
|
|
103
|
-
response = requests.get(url=get_uri, headers= request.headers)
|
|
104
|
-
|
|
105
|
-
return response.json(), 200, {'Content-Type': 'application/json; charset=utf-8'}
|
|
106
|
-
|
|
107
|
-
|
|
108
65
|
@app.route('/.well-known/mcp.json', methods=['GET'])
|
|
109
66
|
def mcp_discovery(path=None):
|
|
110
67
|
''' called by mcp_client_executor for discovery, eg:
|
|
111
68
|
```
|
|
112
69
|
{
|
|
113
70
|
"tool_type": "json-api",
|
|
71
|
+
"schema_version": "1.0",
|
|
114
72
|
"base_url": "https://crm.company.com",
|
|
115
73
|
"resources": [
|
|
116
74
|
{
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
###############################################################################
|
|
4
|
+
#
|
|
5
|
+
# Initalization functions used by api_logic_server_run.py
|
|
6
|
+
#
|
|
7
|
+
# You typically do not customize this file,
|
|
8
|
+
# except to override Creation Defaults and Logging, below.
|
|
9
|
+
#
|
|
10
|
+
###############################################################################
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
Operation:
|
|
14
|
+
1. api_logic_server_run.py - imports config
|
|
15
|
+
1. captures args
|
|
16
|
+
2. api_logic_server_run.py - imports server_setup
|
|
17
|
+
1. server_setup#logging_setup()
|
|
18
|
+
3. api_logic_server_run.py - server_setup.api_logic_server_setup
|
|
19
|
+
On error, NOT CALLED: constraint_handler or ValidationErrorExt (!)
|
|
20
|
+
|
|
21
|
+
+ Operation:
|
|
22
|
+
1. api_logic_server_run.py - imports config
|
|
23
|
+
1. captures args
|
|
24
|
+
1. config#logging_setup()
|
|
25
|
+
2. api_logic_server_run.py - imports server_setup
|
|
26
|
+
3. api_logic_server_run.py - server_setup.api_logic_server_setup
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
start_up_message = "normal start"
|
|
31
|
+
|
|
32
|
+
import traceback
|
|
33
|
+
try:
|
|
34
|
+
import os, logging, logging.config, sys, yaml # failure here means venv probably not set
|
|
35
|
+
except:
|
|
36
|
+
track = traceback.format_exc()
|
|
37
|
+
print(" ")
|
|
38
|
+
print(track)
|
|
39
|
+
print("venv probably not set")
|
|
40
|
+
print("Please see https://apilogicserver.github.io/Docs/Project-Env/ \n")
|
|
41
|
+
exit(1)
|
|
42
|
+
|
|
43
|
+
from flask_sqlalchemy import SQLAlchemy
|
|
44
|
+
import json
|
|
45
|
+
from pathlib import Path
|
|
46
|
+
if os.getenv("EXPERIMENT") == '+':
|
|
47
|
+
import config
|
|
48
|
+
from config.config import Args
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
setup_path = Path(os.path.abspath(os.path.dirname(__file__)))
|
|
52
|
+
project_path = setup_path.parent
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def is_docker() -> bool:
|
|
56
|
+
""" running docker? dir exists: /home/api_logic_server """
|
|
57
|
+
path = '/home/api_logic_server'
|
|
58
|
+
path_result = os.path.isdir(path) # this *should* exist only on docker
|
|
59
|
+
env_result = "DOCKER" == os.getenv('APILOGICSERVER_RUNNING')
|
|
60
|
+
# assert path_result == env_result
|
|
61
|
+
return path_result
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if is_docker():
|
|
65
|
+
sys.path.append(os.path.abspath('/home/api_logic_server'))
|
|
66
|
+
|
|
67
|
+
logic_alerts = True
|
|
68
|
+
""" Set False to silence startup message """
|
|
69
|
+
declare_logic_message = ""
|
|
70
|
+
declare_security_message = "ALERT: *** Security Not Enabled ***"
|
|
71
|
+
|
|
72
|
+
project_dir = str(project_path)
|
|
73
|
+
os.chdir(project_dir) # so admin app can find images, code
|
|
74
|
+
import api.system.api_utils as api_utils
|
|
75
|
+
logic_logger_activate_debug = False
|
|
76
|
+
""" True prints all rules on startup """
|
|
77
|
+
|
|
78
|
+
args = ""
|
|
79
|
+
arg_num = 0
|
|
80
|
+
for each_arg in sys.argv:
|
|
81
|
+
args += each_arg
|
|
82
|
+
arg_num += 1
|
|
83
|
+
if arg_num < len(sys.argv):
|
|
84
|
+
args += ", "
|
|
85
|
+
project_name = os.path.basename(os.path.normpath(project_path))
|
|
86
|
+
|
|
87
|
+
from typing import TypedDict
|
|
88
|
+
import safrs # fails without venv - see https://apilogicserver.github.io/Docs/Project-Env/
|
|
89
|
+
from database.system.SAFRSBaseX import SAFRSBase
|
|
90
|
+
from safrs import ValidationError, SAFRSAPI as _SAFRSAPI
|
|
91
|
+
#from safrs import ValidationError, SAFRSBase, SAFRSAPI as _SAFRSAPI
|
|
92
|
+
from logic_bank.logic_bank import LogicBank
|
|
93
|
+
from logic_bank.exceptions import LBActivateException
|
|
94
|
+
from logic_bank.exec_row_logic.logic_row import LogicRow
|
|
95
|
+
from logic_bank.rule_type.constraint import Constraint
|
|
96
|
+
from .activate_logicbank import activate_logicbank
|
|
97
|
+
from sqlalchemy.ext.declarative import declarative_base
|
|
98
|
+
from sqlalchemy.orm import Session
|
|
99
|
+
import socket
|
|
100
|
+
import warnings
|
|
101
|
+
from flask import Flask, redirect, send_from_directory, send_file
|
|
102
|
+
from flask_cors import CORS
|
|
103
|
+
from safrs import ValidationError, SAFRSAPI
|
|
104
|
+
import ui.admin.admin_loader as AdminLoader
|
|
105
|
+
from security.system.authentication import configure_auth
|
|
106
|
+
import database.bind_dbs as bind_dbs
|
|
107
|
+
import oracledb
|
|
108
|
+
import integration.kafka.kafka_producer as kafka_producer
|
|
109
|
+
import integration.kafka.kafka_consumer as kafka_consumer
|
|
110
|
+
import integration.n8n.n8n_producer as n8n_producer
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if os.getenv("EXPERIMENT") == '+':
|
|
114
|
+
app_logger = logging.getLogger("api_logic_server_app")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class SAFRSAPI(_SAFRSAPI):
|
|
118
|
+
"""
|
|
119
|
+
Extends SAFRSAPI to handle client_uri
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
_SAFRSAPI (_type_): _description_
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
def __init__(self, *args, **kwargs):
|
|
126
|
+
client_uri = kwargs.pop('client_uri', None)
|
|
127
|
+
if client_uri:
|
|
128
|
+
kwargs['port'] = None
|
|
129
|
+
kwargs['host'] = client_uri
|
|
130
|
+
super().__init__(*args, **kwargs)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# ==================================
|
|
135
|
+
# Set
|
|
136
|
+
# ==================================
|
|
137
|
+
|
|
138
|
+
def get_args(flask_app: Flask) -> Args:
|
|
139
|
+
"""
|
|
140
|
+
Get Args, update logging
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Args: typed access to flask_app.config
|
|
144
|
+
"""
|
|
145
|
+
args = Args(flask_app=flask_app) # creation defaults
|
|
146
|
+
|
|
147
|
+
import config.config as config
|
|
148
|
+
flask_app.config.from_object(config.Config)
|
|
149
|
+
app_logger.debug(f"\nserver_setup - get_args: Config args: \n{args}") # # config file (e.g., db uri's)
|
|
150
|
+
|
|
151
|
+
args.get_cli_args(dunder_name=__name__, args=args)
|
|
152
|
+
app_logger.debug(f"\nserver_setup - get_args: CLI args: \n{args}") # api_logic_server_run cl args
|
|
153
|
+
|
|
154
|
+
flask_app.config.from_prefixed_env(prefix="APILOGICPROJECT") # env overrides (e.g., docker)
|
|
155
|
+
app_logger.debug(f"\nserver_setup - get_args: ENV args: \n{args}\n\n")
|
|
156
|
+
|
|
157
|
+
if args.verbose: # export APILOGICPROJECT_VERBOSE=True
|
|
158
|
+
app_logger.setLevel(logging.DEBUG)
|
|
159
|
+
safrs.log.setLevel(logging.DEBUG) # notset 0, debug 10, info 20, warn 30, error 40, critical 50
|
|
160
|
+
authentication_logger = logging.getLogger('security.system.authentication')
|
|
161
|
+
authentication_logger.setLevel(logging.DEBUG)
|
|
162
|
+
authorization_logger = logging.getLogger('security.system.authorization')
|
|
163
|
+
authorization_logger.setLevel(logging.DEBUG)
|
|
164
|
+
auth_provider_logger = logging.getLogger('security.authentication_provider.sql.auth_provider')
|
|
165
|
+
auth_provider_logger.setLevel(logging.DEBUG)
|
|
166
|
+
# sqlachemy_logger = logging.getLogger('sqlalchemy.engine')
|
|
167
|
+
# sqlachemy_logger.setLevel(logging.DEBUG)
|
|
168
|
+
|
|
169
|
+
if app_logger.getEffectiveLevel() <= logging.DEBUG:
|
|
170
|
+
api_utils.sys_info(flask_app.config)
|
|
171
|
+
app_logger.debug(f"\nserver_setup - get_args: ENV args: \n{args}\n\n")
|
|
172
|
+
return args
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# ==================================
|
|
176
|
+
# LOGGING SETUP
|
|
177
|
+
# ==================================
|
|
178
|
+
|
|
179
|
+
def logging_setup() -> logging.Logger:
|
|
180
|
+
"""
|
|
181
|
+
Setup Logging
|
|
182
|
+
"""
|
|
183
|
+
global app_logger, debug_value, project_path
|
|
184
|
+
logging_config = f'{project_path}/config/logging.yml'
|
|
185
|
+
if os.getenv('APILOGICPROJECT_LOGGING_CONFIG'):
|
|
186
|
+
logging_config = project_path.joinpath(os.getenv("APILOGICPROJECT_LOGGING_CONFIG"))
|
|
187
|
+
with open(logging_config,'rt') as f: # see also logic/declare_logic.py
|
|
188
|
+
config=yaml.safe_load(f.read())
|
|
189
|
+
f.close()
|
|
190
|
+
logging.config.dictConfig(config) # log levels: notset 0, debug 10, info 20, warn 30, error 40, critical 50
|
|
191
|
+
app_logger = logging.getLogger("api_logic_server_app")
|
|
192
|
+
debug_value = os.getenv('APILOGICPROJECT_DEBUG')
|
|
193
|
+
if debug_value is not None: # > export APILOGICPROJECT_DEBUG=True
|
|
194
|
+
debug_value = debug_value.upper()
|
|
195
|
+
if debug_value.startswith("F") or debug_value.startswith("N"):
|
|
196
|
+
app_logger.setLevel(logging.INFO)
|
|
197
|
+
else:
|
|
198
|
+
app_logger.setLevel(logging.DEBUG)
|
|
199
|
+
app_logger.debug(f'\nDEBUG level set from env\n')
|
|
200
|
+
app_logger.info(f'\nAPI Logic Project Server Setup ({project_name}) Starting with CLI args: \n.. {args}\n')
|
|
201
|
+
app_logger.info(f'Created August 03, 2024 09:34:01 at {str(project_path)}\n')
|
|
202
|
+
return app_logger
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class ValidationErrorExt(ValidationError):
|
|
206
|
+
"""
|
|
207
|
+
This exception is raised when invalid input has been detected (client side input)
|
|
208
|
+
Always send back the message to the client in the response
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
def __init__(self, message="", status_code=400, api_code=2001, detail=None, error_attributes=None):
|
|
212
|
+
Exception.__init__(self)
|
|
213
|
+
self.error_attributes = error_attributes
|
|
214
|
+
self.status_code = status_code
|
|
215
|
+
self.message = message
|
|
216
|
+
self.api_code = api_code
|
|
217
|
+
self.detail: TypedDict = detail
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def validate_db_uri(flask_app):
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
For sqlite, verify the SQLALCHEMY_DATABASE_URI file exists
|
|
224
|
+
|
|
225
|
+
* Since the name is not reported by SQLAlchemy
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
flask_app (_type_): initialize flask app
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
db_uri = flask_app.config['SQLALCHEMY_DATABASE_URI']
|
|
232
|
+
app_logger.debug(f'sqlite_db_path validity check with db_uri: {db_uri}')
|
|
233
|
+
if 'sqlite' not in db_uri:
|
|
234
|
+
return
|
|
235
|
+
sqlite_db_path = ""
|
|
236
|
+
if db_uri.startswith('sqlite:////'): # eg, sqlite:////Users/val/dev/ApiLogicServer/ApiLogicServer-dev/servers/ai_customer_orders/database/db.sqlite
|
|
237
|
+
sqlite_db_path = Path(db_uri[9:])
|
|
238
|
+
app_logger.debug(f'\t.. Absolute: {str(sqlite_db_path)}')
|
|
239
|
+
else: # eg, sqlite:///../database/db.sqlite
|
|
240
|
+
db_relative_path = db_uri[10:]
|
|
241
|
+
db_relative_path = db_relative_path.replace('../', '') # relative
|
|
242
|
+
sqlite_db_path = Path(os.getcwd()).joinpath(db_relative_path)
|
|
243
|
+
app_logger.debug(f'\t.. Relative: {str(sqlite_db_path)}')
|
|
244
|
+
if db_uri == 'sqlite:///database/db.sqlite':
|
|
245
|
+
raise ValueError(f'This fails, please use; sqlite:///../database/db.sqlite')
|
|
246
|
+
if sqlite_db_path.is_file():
|
|
247
|
+
app_logger.debug(f'\t.. sqlite_db_path is a valid file\n')
|
|
248
|
+
else: # remove this if you wish
|
|
249
|
+
raise ValueError(f'sqlite database does not exist: {str(sqlite_db_path)}')
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# ==========================================================
|
|
254
|
+
# API Logic Server Setup
|
|
255
|
+
# - Opens Database(s)
|
|
256
|
+
# - Setup API, Logic, Security, Optimistic Locking
|
|
257
|
+
# ==========================================================
|
|
258
|
+
|
|
259
|
+
def api_logic_server_setup(flask_app: Flask, args: Args):
|
|
260
|
+
"""
|
|
261
|
+
API Logic Server Setup
|
|
262
|
+
|
|
263
|
+
1. Opens Database(s)
|
|
264
|
+
2. Setup API, Logic, Security, Optimistic Locking
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
flask_app (_type_): configured flask_app (servers, ports, db uri's)
|
|
269
|
+
args (_type_): typed access to flask_app.config
|
|
270
|
+
|
|
271
|
+
Raises:
|
|
272
|
+
ValidationErrorExt: rebadge LogicBank errors for SAFRS API
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
from sqlalchemy import exc as sa_exc
|
|
276
|
+
|
|
277
|
+
global logic_logger_activate_debug, declare_logic_message, declare_security_message
|
|
278
|
+
|
|
279
|
+
with warnings.catch_warnings():
|
|
280
|
+
|
|
281
|
+
safrs_log_level = safrs.log.getEffectiveLevel()
|
|
282
|
+
db_logger = logging.getLogger('sqlalchemy')
|
|
283
|
+
db_log_level = db_logger.getEffectiveLevel()
|
|
284
|
+
safrs_init_logger = logging.getLogger("safrs.safrs_init")
|
|
285
|
+
authorization_logger = logging.getLogger('security.system.authorization')
|
|
286
|
+
authorization_log_level = authorization_logger.getEffectiveLevel()
|
|
287
|
+
do_hide_chatty_logging = True and not args.verbose
|
|
288
|
+
# eg, system startup health check: read on API and relationship - hide many log entries
|
|
289
|
+
if do_hide_chatty_logging and app_logger.getEffectiveLevel() <= logging.INFO:
|
|
290
|
+
safrs.log.setLevel(logging.WARN) # notset 0, debug 10, info 20, warn 30, error 40, critical 50
|
|
291
|
+
db_logger.setLevel(logging.WARN)
|
|
292
|
+
safrs_init_logger.setLevel(logging.WARN)
|
|
293
|
+
authorization_logger.setLevel(logging.WARN)
|
|
294
|
+
|
|
295
|
+
bind_dbs.bind_dbs(flask_app)
|
|
296
|
+
|
|
297
|
+
# https://stackoverflow.com/questions/34674029/sqlalchemy-query-raises-unnecessary-warning-about-sqlite-and-decimal-how-to-spe
|
|
298
|
+
warnings.simplefilter("ignore", category=sa_exc.SAWarning) # alert - disable for safety msgs
|
|
299
|
+
|
|
300
|
+
def constraint_handler(message: str, constraint: Constraint, logic_row: LogicRow):
|
|
301
|
+
""" format LogicBank constraint exception for SAFRS """
|
|
302
|
+
if constraint is not None and hasattr(constraint, 'error_attributes'):
|
|
303
|
+
|
|
304
|
+
detail = {"model": logic_row.name, "error_attributes": constraint.error_attributes}
|
|
305
|
+
else:
|
|
306
|
+
detail = {"model": logic_row.name}
|
|
307
|
+
raise ValidationErrorExt(message=message, detail=detail)
|
|
308
|
+
|
|
309
|
+
admin_enabled = os.name != "nt"
|
|
310
|
+
admin_enabled = False
|
|
311
|
+
""" internal use, for future enhancements """
|
|
312
|
+
if admin_enabled:
|
|
313
|
+
flask_app.config.update(SQLALCHEMY_BINDS={'admin': 'sqlite:////tmp/4LSBE.sqlite.4'})
|
|
314
|
+
|
|
315
|
+
db = SQLAlchemy()
|
|
316
|
+
db.init_app(flask_app)
|
|
317
|
+
flask_app.db = db
|
|
318
|
+
with flask_app.app_context():
|
|
319
|
+
|
|
320
|
+
with open(Path(project_path).joinpath('security/system/custom_swagger.json')) as json_file:
|
|
321
|
+
custom_swagger = json.load(json_file)
|
|
322
|
+
safrs_api = SAFRSAPI(flask_app, app_db= db, host=args.swagger_host, port=args.swagger_port, client_uri=args.client_uri,
|
|
323
|
+
prefix = args.api_prefix, custom_swagger=custom_swagger)
|
|
324
|
+
|
|
325
|
+
if os.getenv('APILOGICSERVER_ORACLE_THICK'):
|
|
326
|
+
oracledb.init_oracle_client(lib_dir=os.getenv('APILOGICSERVER_ORACLE_THICK'))
|
|
327
|
+
|
|
328
|
+
db = safrs.DB # valid only after is initialized, above
|
|
329
|
+
session: Session = db.session
|
|
330
|
+
|
|
331
|
+
if admin_enabled: # unused (internal dev use)
|
|
332
|
+
db.create_all()
|
|
333
|
+
db.create_all(bind='admin')
|
|
334
|
+
session.commit()
|
|
335
|
+
|
|
336
|
+
from api import expose_api_models, customize_api
|
|
337
|
+
|
|
338
|
+
import database.models
|
|
339
|
+
app_logger.info("Data Model Loaded, customizing...")
|
|
340
|
+
from database import customize_models
|
|
341
|
+
|
|
342
|
+
activate_logicbank(session, constraint_handler)
|
|
343
|
+
|
|
344
|
+
method_decorators : list = []
|
|
345
|
+
safrs_init_logger.setLevel(logging.WARN)
|
|
346
|
+
expose_api_models.expose_models(safrs_api, method_decorators)
|
|
347
|
+
app_logger.info(f'Declare API - api/expose_api_models, endpoint for each table on {args.swagger_host}:{args.swagger_port}, customizing...')
|
|
348
|
+
customize_api.expose_services(flask_app, safrs_api, project_dir, swagger_host=args.swagger_host, PORT=args.port) # custom services
|
|
349
|
+
|
|
350
|
+
if args.security_enabled:
|
|
351
|
+
configure_auth(flask_app, database, method_decorators)
|
|
352
|
+
|
|
353
|
+
if args.security_enabled:
|
|
354
|
+
from security import declare_security # activate security
|
|
355
|
+
app_logger.info("..declare security - security/declare_security.py"
|
|
356
|
+
# not accurate: + f' -- {len(database.database_discovery.authentication_models.metadata.tables)}'
|
|
357
|
+
+ ' authentication tables loaded')
|
|
358
|
+
declare_security_message = declare_security.declare_security_message
|
|
359
|
+
|
|
360
|
+
from api.system.opt_locking import opt_locking
|
|
361
|
+
from config.config import OptLocking
|
|
362
|
+
if args.opt_locking == OptLocking.IGNORED.value:
|
|
363
|
+
app_logger.info("\nOptimistic Locking: ignored")
|
|
364
|
+
else:
|
|
365
|
+
opt_locking.opt_locking_setup(session)
|
|
366
|
+
|
|
367
|
+
kafka_producer.kafka_producer()
|
|
368
|
+
kafka_consumer.kafka_consumer(safrs_api = safrs_api)
|
|
369
|
+
|
|
370
|
+
n8n_producer.n8n_producer()
|
|
371
|
+
|
|
372
|
+
SAFRSBase._s_auto_commit = False
|
|
373
|
+
session.close()
|
|
374
|
+
|
|
375
|
+
safrs.log.setLevel(safrs_log_level)
|
|
376
|
+
db_logger.setLevel(db_log_level)
|
|
377
|
+
authorization_logger.setLevel(authorization_log_level)
|
|
378
|
+
|
|
379
|
+
if os.getenv('APILOGICPROJECT_DEBUG'): # temp debug since logging in config is not happening
|
|
380
|
+
KAFKA_SERVER = os.getenv('KAFKA_SERVER')
|
|
381
|
+
is_empty = False
|
|
382
|
+
if KAFKA_SERVER is not None:
|
|
383
|
+
is_empty = KAFKA_SERVER == ""
|
|
384
|
+
is_none = KAFKA_SERVER is None
|
|
385
|
+
app_logger.debug(f'\nDEBUG KAFKA_SERVER: [{KAFKA_SERVER}] (is_empty: {is_empty}) (is_none: {is_none}) \n')
|
|
386
|
+
app_logger.debug(f'... Args.instance.kafka_producer: {Args.instance.kafka_producer}\n')
|
|
387
|
+
|
|
388
|
+
|