ApiLogicServer 15.0.35__py3-none-any.whl → 15.0.38__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/api_logic_server.py +2 -2
- api_logic_server_cli/api_logic_server_info.yaml +3 -3
- api_logic_server_cli/genai/genai_svcs.py +4 -3
- api_logic_server_cli/manager.py +7 -5
- api_logic_server_cli/prototypes/base/docs/training/react_map.prompt.md +13 -0
- api_logic_server_cli/prototypes/base/docs/training/react_tree.prompt.md +10 -0
- api_logic_server_cli/prototypes/base/integration/mcp/examples/mcp_context_results.txt +142 -0
- api_logic_server_cli/prototypes/base/integration/mcp/mcp_client_executor.py +65 -29
- api_logic_server_cli/prototypes/manager/system/Manager_workspace.code-workspace +3 -3
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/app_learning/Admin-App-Resource-Learning-Prompt.md +3 -2
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/app_learning/Admin-App-js-Learning-Prompt.md +4 -14
- api_logic_server_cli/prototypes/nw/api/api_discovery/authentication_expose_api_models.py +53 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/auto_discovery.py +27 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/count_orders_by_month.html +76 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/count_orders_by_month.sql +1 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/dashboard_services.py +143 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/mcp_discovery.py +97 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/new_service.py +21 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/newer_service.py +21 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/number_of_sales_per_category.html +76 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/number_of_sales_per_category.sql +1 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/ontimize_api.py +495 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/sales_by_category.html +76 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/sales_by_category.sql +1 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/system.py +77 -0
- api_logic_server_cli/prototypes/nw/database/database_discovery/graphics_services.py +173 -0
- api_logic_server_cli/prototypes/nw/ui/admin/home.js +5 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/DEPARTMENT_TREE_VIEW.md +66 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/package.json +4 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/public/index.html +3 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/App.js +8 -1
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/CustomLayout.js +20 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/Department.js +511 -24
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/DepartmentTree.js +147 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/Employee.js +230 -18
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/LandingPage.js +264 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/Supplier.js +359 -121
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/index.js +1 -0
- {apilogicserver-15.0.35.dist-info → apilogicserver-15.0.38.dist-info}/METADATA +1 -1
- {apilogicserver-15.0.35.dist-info → apilogicserver-15.0.38.dist-info}/RECORD +44 -23
- api_logic_server_cli/prototypes/base/docs/training/admin_app_unused.md +0 -156
- {apilogicserver-15.0.35.dist-info → apilogicserver-15.0.38.dist-info}/WHEEL +0 -0
- {apilogicserver-15.0.35.dist-info → apilogicserver-15.0.38.dist-info}/entry_points.txt +0 -0
- {apilogicserver-15.0.35.dist-info → apilogicserver-15.0.38.dist-info}/licenses/LICENSE +0 -0
- {apilogicserver-15.0.35.dist-info → apilogicserver-15.0.38.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from database import models
|
|
2
|
+
import logging
|
|
3
|
+
from safrs import jsonapi_attr
|
|
4
|
+
from sqlalchemy.orm import relationship, remote, foreign
|
|
5
|
+
from functools import wraps # This convenience func preserves name and docstring
|
|
6
|
+
import decimal as decimal
|
|
7
|
+
from sqlalchemy import extract, func
|
|
8
|
+
from flask import request, jsonify
|
|
9
|
+
from safrs import jsonapi_rpc, SAFRSAPI
|
|
10
|
+
from database import models
|
|
11
|
+
import safrs
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
Called by iFrame in home.js when admin app is loaded
|
|
16
|
+
|
|
17
|
+
Returns iFrame for ui/admin/home.js, with entries like this to populate the dashboard:
|
|
18
|
+
<iframe src="http://localhost:5656/chart_graphics/sales_per_category" style="flex: 1; border: none; width: 90%; height: 200px;">
|
|
19
|
+
|
|
20
|
+
curl -X GET "http://localhost:5656/dashboard"
|
|
21
|
+
|
|
22
|
+
You typically do not alter this file - it is rebuilt on `als genai-graphics`
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
app_logger = logging.getLogger(__name__)
|
|
27
|
+
dashboard_result = {}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators = []):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
@app.route('/dashboard', methods=['GET','OPTIONS'])
|
|
34
|
+
def dashboard():
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if request.method == 'OPTIONS':
|
|
38
|
+
return jsonify({"result": "ok"})
|
|
39
|
+
|
|
40
|
+
server = request.host_url
|
|
41
|
+
|
|
42
|
+
iframe_template = '<div class="dashboard-iframe"><iframe src="{url}" style="flex: 1; border: none; width: 90%; height: 200px;"></iframe></div>'
|
|
43
|
+
|
|
44
|
+
iframe_1 = iframe_template.format(url=f"{server}chart_graphics/sales_by_category")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
iframe_2 = iframe_template.format(url=f"{server}chart_graphics/number_of_sales_per_category")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
iframe_3 = iframe_template.format(url=f"{server}chart_graphics/count_orders_by_month")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
return f'<div style="display: flex; flex-direction: row; gap: 10px; border: none; ">{iframe_1} {iframe_2} {iframe_3}</div>'
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@app.route('/chart_graphics/<path:path>', methods=['GET','OPTIONS'])
|
|
60
|
+
def chart_graphics(path):
|
|
61
|
+
if request.method == 'OPTIONS':
|
|
62
|
+
return jsonify({"result": "ok"})
|
|
63
|
+
|
|
64
|
+
dashboards = get_dashboards()
|
|
65
|
+
if len(dashboards) == 0:
|
|
66
|
+
return jsonify({"result": "No dashboards Found"}), 404
|
|
67
|
+
if path in dashboards:
|
|
68
|
+
return dashboards[path]
|
|
69
|
+
return jsonify({"result": "not found"}), 404
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
##############################
|
|
73
|
+
# generated queries follow
|
|
74
|
+
##############################
|
|
75
|
+
|
|
76
|
+
def get_dashboards():
|
|
77
|
+
if len(dashboard_result) > 0:
|
|
78
|
+
return dashboard_result
|
|
79
|
+
|
|
80
|
+
from jinja2 import Environment, FileSystemLoader
|
|
81
|
+
env = Environment(loader = FileSystemLoader('ui/templates'))
|
|
82
|
+
template = env.get_template('bar_chart.jinja')
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
previously_failed = Path('docs/graphics/sales_by_category.err').exists()
|
|
87
|
+
if previously_failed:
|
|
88
|
+
pass # query has previously failed, so skip it
|
|
89
|
+
else:
|
|
90
|
+
try:
|
|
91
|
+
results = models.Category.sales_by_category(None)
|
|
92
|
+
color = 'rgba(75, 192, 192, 0.2)'
|
|
93
|
+
dashboard1 = template.render(result=results, color=color)
|
|
94
|
+
dashboard_result['sales_by_category']= dashboard1
|
|
95
|
+
except Exception as e:
|
|
96
|
+
msg = f"GenAI query creation error on models.Category.sales_by_category: " + str(e)
|
|
97
|
+
dashboard_result['sales_by_category'] = msg
|
|
98
|
+
app_logger.error(msg)
|
|
99
|
+
with open('docs/graphics//sales_by_category.err', 'w') as err_file:
|
|
100
|
+
err_file.write(msg) # this logs the error to prevent future calls
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
previously_failed = Path('docs/graphics/number_of_sales_per_category.err').exists()
|
|
106
|
+
if previously_failed:
|
|
107
|
+
pass # query has previously failed, so skip it
|
|
108
|
+
else:
|
|
109
|
+
try:
|
|
110
|
+
results = models.Category.number_of_sales_per_category(None)
|
|
111
|
+
color = 'rgba(75, 192, 192, 0.2)'
|
|
112
|
+
dashboard2 = template.render(result=results, color=color)
|
|
113
|
+
dashboard_result['number_of_sales_per_category']= dashboard2
|
|
114
|
+
except Exception as e:
|
|
115
|
+
msg = f"GenAI query creation error on models.Category.number_of_sales_per_category: " + str(e)
|
|
116
|
+
dashboard_result['number_of_sales_per_category'] = msg
|
|
117
|
+
app_logger.error(msg)
|
|
118
|
+
with open('docs/graphics//number_of_sales_per_category.err', 'w') as err_file:
|
|
119
|
+
err_file.write(msg) # this logs the error to prevent future calls
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
previously_failed = Path('docs/graphics/count_orders_by_month.err').exists()
|
|
125
|
+
if previously_failed:
|
|
126
|
+
pass # query has previously failed, so skip it
|
|
127
|
+
else:
|
|
128
|
+
try:
|
|
129
|
+
results = models.Order.count_orders_by_month(None)
|
|
130
|
+
color = 'rgba(75, 192, 192, 0.2)'
|
|
131
|
+
dashboard3 = template.render(result=results, color=color)
|
|
132
|
+
dashboard_result['count_orders_by_month']= dashboard3
|
|
133
|
+
except Exception as e:
|
|
134
|
+
msg = f"GenAI query creation error on models.Order.count_orders_by_month: " + str(e)
|
|
135
|
+
dashboard_result['count_orders_by_month'] = msg
|
|
136
|
+
app_logger.error(msg)
|
|
137
|
+
with open('docs/graphics//count_orders_by_month.err', 'w') as err_file:
|
|
138
|
+
err_file.write(msg) # this logs the error to prevent future calls
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
return dashboard_result
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from flask import request, jsonify
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
from config.config import Args # circular import error if at top
|
|
9
|
+
|
|
10
|
+
app_logger = logging.getLogger("api_logic_server_app")
|
|
11
|
+
|
|
12
|
+
def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators = []):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
def get_server_url():
|
|
16
|
+
""" return the server URL for the OpenAPI spec """
|
|
17
|
+
result = f'http://{Args.instance.swagger_host}:{Args.instance.swagger_port}'
|
|
18
|
+
# get env variable API_LOGIC_SERVER_TUNNEL (or None)
|
|
19
|
+
if tunnel_url := os.getenv("API_LOGIC_SERVER_TUNNEL", None):
|
|
20
|
+
app_logger.info(f".. tunnel URL: {tunnel_url}")
|
|
21
|
+
result = tunnel_url
|
|
22
|
+
return result # + '/api'
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@app.before_request
|
|
26
|
+
def before_any_request():
|
|
27
|
+
# print(f"[DEBUG] Incoming request: {request.method} {request.url}")
|
|
28
|
+
if activate_openapi_logging := False:
|
|
29
|
+
if request.content_type == 'application/json' and request.method in ['POST', 'PUT', 'PATCH']:
|
|
30
|
+
# openapi: Incoming request: PATCH http://localhost:5656/api/Customer/1/ {'data': {'attributes': {'credit_limit': 5555}, 'type': 'Customer', 'id': '1'}}
|
|
31
|
+
# openapi: Incoming request: PATCH http://6f6f-2601-644-4900-d6f0-ecc9-6df3-8863-c5b2.ngrok-free.app/api/Customer/1 {'credit_limit': 5555}
|
|
32
|
+
|
|
33
|
+
app_logger.info(f"openapi: Incoming request: {request.method} {request.url} {str(request.json)}")
|
|
34
|
+
else:
|
|
35
|
+
app_logger.info(f"openapi: Incoming request: {request.method} {request.url}")
|
|
36
|
+
# app_logger.info(f"openapi: Incoming request headers: {request.headers}")
|
|
37
|
+
|
|
38
|
+
chatgpt_request_json = {
|
|
39
|
+
"credit_limit": 25000,
|
|
40
|
+
}
|
|
41
|
+
standard_request_json = {
|
|
42
|
+
"data": {
|
|
43
|
+
"type": "Customer",
|
|
44
|
+
"id": "ALFKI",
|
|
45
|
+
"attributes": {
|
|
46
|
+
"name": "Alice",
|
|
47
|
+
"credit_limit": 25000,
|
|
48
|
+
"balance": 12345
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
swagger_request_json = {
|
|
53
|
+
'data': {
|
|
54
|
+
'attributes': {
|
|
55
|
+
'credit_limit': 5555
|
|
56
|
+
},
|
|
57
|
+
'type': 'Customer',
|
|
58
|
+
'id': '1'
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@app.route('/.well-known/mcp.json', methods=['GET'])
|
|
65
|
+
def mcp_discovery(path=None):
|
|
66
|
+
''' called by mcp_client_executor for discovery; read docs/mcp_learning/ for:
|
|
67
|
+
1. learning, including fan-out & response format, and email requests.
|
|
68
|
+
2. schema
|
|
69
|
+
|
|
70
|
+
see: https://apilogicserver.github.io/Docs/Integration-MCP/#1-discovery
|
|
71
|
+
|
|
72
|
+
test: curl -X GET "http://localhost:5656/.well-known/mcp.json"
|
|
73
|
+
'''
|
|
74
|
+
# return docs/mcp_schema.json
|
|
75
|
+
mcp_learning_path = Path(project_dir + "/docs/mcp_learning")
|
|
76
|
+
try:
|
|
77
|
+
schema_path = mcp_learning_path.joinpath('mcp_schema.json')
|
|
78
|
+
if not schema_path.exists():
|
|
79
|
+
return jsonify({"error": "System Error - /docs/mcp_learning/mcp_schema.json not found"}), 404
|
|
80
|
+
with open(schema_path, "r") as schema_file:
|
|
81
|
+
schema = json.load(schema_file)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
app_logger.error(f"Error loading MCP schema: {e}")
|
|
84
|
+
return jsonify({"error": "MCP schema not found"}), 404
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
learnings_path = mcp_learning_path.joinpath('mcp.prompt')
|
|
88
|
+
if not learnings_path.exists():
|
|
89
|
+
return jsonify({"error": "System Error - /docs/mcp_learning/mcp,prompt not found"}), 404
|
|
90
|
+
with open(learnings_path, "r") as learnings_file:
|
|
91
|
+
learnings = learnings_file.read()
|
|
92
|
+
except Exception as e:
|
|
93
|
+
app_logger.error(f"Error loading MCP learnings: {e}")
|
|
94
|
+
return jsonify({"error": "MCP learnings not found"}), 404
|
|
95
|
+
schema['learning'] = learnings
|
|
96
|
+
|
|
97
|
+
return jsonify(schema), 200
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from flask import request, jsonify
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
app_logger = logging.getLogger("api_logic_server_app")
|
|
5
|
+
|
|
6
|
+
def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators = []):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
@app.route('/hello_service')
|
|
10
|
+
def hello_service():
|
|
11
|
+
"""
|
|
12
|
+
Illustrates:
|
|
13
|
+
* Use standard Flask, here for non-database endpoints.
|
|
14
|
+
|
|
15
|
+
Test it with:
|
|
16
|
+
|
|
17
|
+
http://localhost:5656/hello_service?user=ApiLogicServer
|
|
18
|
+
"""
|
|
19
|
+
user = request.args.get('user')
|
|
20
|
+
app_logger.info(f'{user}')
|
|
21
|
+
return jsonify({"result": f'hello from new_service! from {user}'})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from flask import request, jsonify
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
app_logger = logging.getLogger("api_logic_server_app")
|
|
5
|
+
|
|
6
|
+
def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators ):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
@app.route('/hello_newer_service')
|
|
10
|
+
def hello_newer_service():
|
|
11
|
+
"""
|
|
12
|
+
Illustrates:
|
|
13
|
+
* Use standard Flask, here for non-database endpoints.
|
|
14
|
+
|
|
15
|
+
Test it with:
|
|
16
|
+
|
|
17
|
+
http://localhost:5656/hello_newer_service?user=ApiLogicServer
|
|
18
|
+
"""
|
|
19
|
+
user = request.args.get('user')
|
|
20
|
+
app_logger.info(f'{user}')
|
|
21
|
+
return jsonify({"result": f'hello from even_newer_service! from {user}'})
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Bar Chart Example</title>
|
|
7
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<canvas id="myChart" width="400" height="200"></canvas>
|
|
11
|
+
<script>
|
|
12
|
+
// Input data
|
|
13
|
+
fetch('http://localhost:5656/api/GraphicsServices/number_of_sales_per_category', {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
headers: {
|
|
16
|
+
'accept': 'application/vnd.api+json',
|
|
17
|
+
'Content-Type': 'application/json'
|
|
18
|
+
},
|
|
19
|
+
body: JSON.stringify({}) // Add any required payload here
|
|
20
|
+
})
|
|
21
|
+
.then(response => {
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
24
|
+
}
|
|
25
|
+
return response.json();
|
|
26
|
+
})
|
|
27
|
+
.then(result => {
|
|
28
|
+
const labels = [];
|
|
29
|
+
const data = [];
|
|
30
|
+
const type = result.meta.result.chart_type;
|
|
31
|
+
const title = result.meta.result.title;
|
|
32
|
+
const columns = result.meta.result.columns;
|
|
33
|
+
console.log(JSON.stringify(result));
|
|
34
|
+
result.meta.result.results.forEach(item => {
|
|
35
|
+
labels.push(item[columns[0]]);
|
|
36
|
+
data.push(parseFloat(item[columns[1]]));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Create the bar chart
|
|
40
|
+
const ctx = document.getElementById('myChart').getContext('2d');
|
|
41
|
+
const myChart = new Chart(ctx, {
|
|
42
|
+
type: type,
|
|
43
|
+
data: {
|
|
44
|
+
labels: labels,
|
|
45
|
+
datasets: [{
|
|
46
|
+
label: title,
|
|
47
|
+
data: data,
|
|
48
|
+
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
|
49
|
+
borderColor: 'rgba(75, 192, 192, 1)',
|
|
50
|
+
borderWidth: 1
|
|
51
|
+
}]
|
|
52
|
+
},
|
|
53
|
+
options: {
|
|
54
|
+
responsive: true,
|
|
55
|
+
plugins: {
|
|
56
|
+
legend: {
|
|
57
|
+
position: 'top',
|
|
58
|
+
},
|
|
59
|
+
title: {
|
|
60
|
+
display: true,
|
|
61
|
+
text: title
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
scales: {
|
|
65
|
+
y: {
|
|
66
|
+
beginAtZero: true
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
})
|
|
72
|
+
.catch(error => console.error('Error fetching data:', error));
|
|
73
|
+
|
|
74
|
+
</script>
|
|
75
|
+
</body>
|
|
76
|
+
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SELECT Category.CategoryName, COUNT(`Order.Id`) AS NumberOfSales FROM Category JOIN Product ON Product.CategoryId = Category.Id JOIN OrderDetail ON OrderDetail.ProductId = Product.Id JOIN `Order` ON `Order`.Id = OrderDetail.OrderId WHERE `Order`.ShippedDate IS NOT NULL GROUP BY Category.CategoryName ORDER BY NumberOfSales DESC;
|