ApiLogicServer 14.5.4__py3-none-any.whl → 14.5.14__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 +4 -122
- api_logic_server_cli/api_logic_server_info.yaml +3 -3
- api_logic_server_cli/create_from_model/__pycache__/api_logic_server_utils.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/__pycache__/dbml.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/api_logic_server_utils.py +29 -10
- api_logic_server_cli/create_from_model/dbml.py +16 -12
- api_logic_server_cli/database/basic_demo.sqlite +0 -0
- api_logic_server_cli/genai/genai.py +4 -0
- api_logic_server_cli/genai/genai_svcs.py +10 -2
- api_logic_server_cli/manager.py +3 -1
- api_logic_server_cli/prototypes/base/.vscode/launch.json +40 -0
- api_logic_server_cli/prototypes/base/api/api_discovery/mcp_discovery.py +58 -0
- api_logic_server_cli/prototypes/base/database/system/SAFRSBaseX.py +68 -2
- api_logic_server_cli/prototypes/base/database/system/SAFRSBaseX.pyZ +73 -0
- api_logic_server_cli/prototypes/{basic_demo/customizations/integration → base/integration/mcp}/.DS_Store +0 -0
- api_logic_server_cli/prototypes/base/integration/mcp/README_mcp.md +15 -0
- api_logic_server_cli/prototypes/{basic_demo/customizations → base}/integration/mcp/mcp_client_executor.py +91 -103
- api_logic_server_cli/prototypes/base/integration/mcp/mcp_schema.txt +47 -0
- api_logic_server_cli/prototypes/base/integration/mcp/mcp_server_discovery.json +9 -0
- api_logic_server_cli/prototypes/base/integration/mcp/test_notes.txt +37 -0
- api_logic_server_cli/prototypes/basic_demo/README.md +251 -91
- api_logic_server_cli/prototypes/basic_demo/customizations/api/api_discovery/mcp_discovery.py +1 -1
- api_logic_server_cli/prototypes/basic_demo/customizations/database/db.sqlite +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/database/models.py +27 -12
- api_logic_server_cli/prototypes/basic_demo/customizations/database/system/SAFRSBaseX.py +1 -1
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/Zmcp_client_executor.py +294 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/mcp_tool_context.json +25 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/test_notes.txt +37 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/logic/declare_logic.py +1 -20
- api_logic_server_cli/prototypes/basic_demo/customizations/logic/logic_discovery/email_request.py +47 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/logic/logic_discovery/mcp_client_executor_request.py +320 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/logic/logic_discovery/simple_constraints.py +25 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/admin/admin.yaml +39 -32
- api_logic_server_cli/prototypes/basic_demo/iteration/database/db.sqlite +0 -0
- api_logic_server_cli/prototypes/basic_demo/iteration/ui/admin/admin.yaml +39 -44
- api_logic_server_cli/prototypes/manager/.vscode/launch.json +21 -0
- api_logic_server_cli/prototypes/manager/{README.md → READMEz.md} +4 -4
- api_logic_server_cli/prototypes/manager/REAMDE.md +1057 -0
- api_logic_server_cli/prototypes/manager/system/genai/mcp_learning/mcp.prompt +27 -0
- api_logic_server_cli/prototypes/manager/system/install-ApiLogicServer-dev/install-ApiLogicServer-dev.sh +2 -1
- {apilogicserver-14.5.4.dist-info → apilogicserver-14.5.14.dist-info}/METADATA +1 -1
- {apilogicserver-14.5.4.dist-info → apilogicserver-14.5.14.dist-info}/RECORD +46 -42
- {apilogicserver-14.5.4.dist-info → apilogicserver-14.5.14.dist-info}/WHEEL +1 -1
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/3_executor_test_agent.py +0 -52
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/README_functon.md +0 -201
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/ai_plugin.json +0 -17
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/nw-swagger_3.json +0 -1731
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/snippets.txt +0 -5
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3 genai_demo_with_get.json +0 -1731
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3.json +0 -1782
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3_genai_demo.json +0 -264
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3_genai_demo_with_update.json +0 -1782
- api_logic_server_cli/prototypes/genai_demo/logic/logic_discovery/auto_discovery.py +0 -52
- {apilogicserver-14.5.4.dist-info → apilogicserver-14.5.14.dist-info}/entry_points.txt +0 -0
- {apilogicserver-14.5.4.dist-info → apilogicserver-14.5.14.dist-info}/licenses/LICENSE +0 -0
- {apilogicserver-14.5.4.dist-info → apilogicserver-14.5.14.dist-info}/top_level.txt +0 -0
|
@@ -12,10 +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.14" # last public release: 14.05.00
|
|
16
16
|
recent_changes = \
|
|
17
17
|
f'\n\nRecent Changes:\n' +\
|
|
18
|
-
"\t05/
|
|
18
|
+
"\t05/27/2024 - 14.05.14: tour - re-factored default mcp req with generic Post, email stub, use basic_demo custs for genai_demo \n"\
|
|
19
19
|
"\t05/16/2024 - 14.05.00: safrs 3.1.7, running mcp preview \n"\
|
|
20
20
|
"\t04/27/2024 - 14.04.00: Graphics preview, Vibe install fix, Improved IDE Chat Logic, MCP Exploration \n"\
|
|
21
21
|
"\t03/30/2024 - 14.03.25: WebGenAI fixes for Kafka and Keycloak \n"\
|
|
@@ -100,7 +100,7 @@ if debug_value is not None:
|
|
|
100
100
|
logging.getLogger('create_from_model.model_creation_services').setLevel(logging.DEBUG)
|
|
101
101
|
|
|
102
102
|
|
|
103
|
-
log.debug("Patch to enable import of outer directories")
|
|
103
|
+
# log.debug("Patch to enable import of outer directories")
|
|
104
104
|
sys.path.append(get_api_logic_server_dir()) # e.g, on Docker: export PATH="/home/api_logic_server/api_logic_server_cli"
|
|
105
105
|
api_logic_server_path = os.path.dirname(get_api_logic_server_dir()) # e.g: export PATH="/home/api_logic_server"
|
|
106
106
|
sys.path.append(api_logic_server_path)
|
|
@@ -1341,7 +1341,7 @@ from database import <project.bind_key>_models
|
|
|
1341
1341
|
|
|
1342
1342
|
log.debug("\n==================================================================")
|
|
1343
1343
|
if msg != "":
|
|
1344
|
-
log.debug(" 2. Add User.Login endpoint")
|
|
1344
|
+
log.debug(" 2. Add User.Login endpoint for Swagger")
|
|
1345
1345
|
log.debug("==================================================================\n")
|
|
1346
1346
|
login_endpoint_filename = f'{self.api_logic_server_dir_path.joinpath("templates/login_endpoint.txt")}'
|
|
1347
1347
|
auth_models_file_name = f'{self.project_directory_path.joinpath("database/database_discovery/authentication_models.py")}'
|
|
@@ -1380,124 +1380,6 @@ from database import <project.bind_key>_models
|
|
|
1380
1380
|
self.model_creation_services.resource_list = save_resource_list
|
|
1381
1381
|
|
|
1382
1382
|
|
|
1383
|
-
def add_auth_from_scratch(self, msg: str, is_nw: bool = False): # TODO: remove
|
|
1384
|
-
"""add authentication models to project, update config; leverage multi-db support. kat
|
|
1385
|
-
|
|
1386
|
-
Prior to 10.04.50, if provider_type is sql:
|
|
1387
|
-
1. add-db --auth-db_url= [ auth | db_url ] by calling self.create_project()
|
|
1388
|
-
2. add user.login endpoint
|
|
1389
|
-
3. Set SECURITY_ENABLED in config.py
|
|
1390
|
-
4. Adding Sample authorization to security/declare_security.py, or user
|
|
1391
|
-
|
|
1392
|
-
Alert: complicated self.create_project() non-recursive flow:
|
|
1393
|
-
1. Use model_creation_services to create auth models
|
|
1394
|
-
2. By re-running app_creator (which leaves resources at auth, not db)
|
|
1395
|
-
3. So, save/restore resource_list, bind_key, db_url, abs_db_url
|
|
1396
|
-
|
|
1397
|
-
Args:
|
|
1398
|
-
msg (str): eg: "ApiLogicProject customizable project created. Adding Security:")
|
|
1399
|
-
is_nw (bool): is northwind, which means we add the nw security logic
|
|
1400
|
-
provider_type (str): sql or keycloak
|
|
1401
|
-
"""
|
|
1402
|
-
|
|
1403
|
-
if create_utils.does_file_contain(search_for="SECURITY_ENABLED = True #",
|
|
1404
|
-
in_file=f'{self.project_directory}/config/config.py'):
|
|
1405
|
-
if create_utils.does_file_contain(search_for="sql.auth_provider import Authentication_Provider",
|
|
1406
|
-
in_file=f'{self.project_directory}/config/config.py'):
|
|
1407
|
-
pass # ok to go from sql -> keycloak
|
|
1408
|
-
else:
|
|
1409
|
-
log.info(f'\nAlready present in config/config.py - no action taken\n')
|
|
1410
|
-
return
|
|
1411
|
-
|
|
1412
|
-
if self.auth_provider_type == 'keycloak':
|
|
1413
|
-
log.debug("\n==================================================================")
|
|
1414
|
-
if msg != "":
|
|
1415
|
-
log.debug(" 1. Set SECURITY_ENABLED in config.py")
|
|
1416
|
-
log.debug("==================================================================\n")
|
|
1417
|
-
create_utils.replace_string_in_file(search_for="SECURITY_ENABLED = False #",
|
|
1418
|
-
replace_with='SECURITY_ENABLED = True #',
|
|
1419
|
-
in_file=f'{self.project_directory}/config/config.py')
|
|
1420
|
-
create_utils.replace_string_in_file(search_for="security.authentication_provider.sql.auth_provider",
|
|
1421
|
-
replace_with='security.authentication_provider.keycloak.auth_provider',
|
|
1422
|
-
in_file=f'{self.project_directory}/config/config.py')
|
|
1423
|
-
create_utils.copy_md(project = self, from_doc_file = "Keycloak-devnotes.md",
|
|
1424
|
-
to_project_file='devops/keycloak/Readme-keycloak.md')
|
|
1425
|
-
else:
|
|
1426
|
-
save_run = self.run
|
|
1427
|
-
save_command = self.command
|
|
1428
|
-
save_db_url = self.db_url
|
|
1429
|
-
save_abs_db_url = self.abs_db_url
|
|
1430
|
-
save_nw_db_status = self.nw_db_status
|
|
1431
|
-
save_resource_list = None
|
|
1432
|
-
if self.model_creation_services is not None:
|
|
1433
|
-
save_resource_list = self.model_creation_services.resource_list
|
|
1434
|
-
save_bind_key = self.bind_key
|
|
1435
|
-
self.command = "add_db"
|
|
1436
|
-
if self.auth_db_url == "":
|
|
1437
|
-
self.auth_db_url = 'auth' # for create manager
|
|
1438
|
-
self.db_url = self.auth_db_url
|
|
1439
|
-
self.bind_key = "authentication"
|
|
1440
|
-
is_northwind = is_nw or self.nw_db_status in ["nw", "nw+"] # nw_db_status altered in create_project
|
|
1441
|
-
if is_northwind: # is_nw or self.nw_db_status == "nw":
|
|
1442
|
-
self.db_url = "auth" # shorthand for api_logic_server_cli/database/auth...
|
|
1443
|
-
|
|
1444
|
-
''' non-recursive call to create_project() '''
|
|
1445
|
-
self.run = False
|
|
1446
|
-
self.create_project() # not creating project, but using model creation svcs to add authdb
|
|
1447
|
-
|
|
1448
|
-
self.run = save_run
|
|
1449
|
-
self.command = save_command
|
|
1450
|
-
self.db_url = save_db_url
|
|
1451
|
-
self.abs_db_url = save_abs_db_url
|
|
1452
|
-
self.bind_key = save_bind_key
|
|
1453
|
-
self.nw_db_status = save_nw_db_status
|
|
1454
|
-
self.model_creation_services.resource_list = save_resource_list
|
|
1455
|
-
|
|
1456
|
-
log.debug("\n==================================================================")
|
|
1457
|
-
if msg != "":
|
|
1458
|
-
log.debug(" 2. Add User.Login endpoint")
|
|
1459
|
-
log.debug("==================================================================\n")
|
|
1460
|
-
login_endpoint_filename = f'{self.api_logic_server_dir_path.joinpath("templates/login_endpoint.txt")}'
|
|
1461
|
-
auth_models_file_name = f'{self.project_directory_path.joinpath("database/authentication_models.py")}'
|
|
1462
|
-
with open(login_endpoint_filename, 'r') as file:
|
|
1463
|
-
login_endpoint_data = file.read()
|
|
1464
|
-
create_utils.insert_lines_at(lines=login_endpoint_data,
|
|
1465
|
-
at='UserRoleList : Mapped[List["UserRole"]] = relationship(back_populates="user")',
|
|
1466
|
-
after=True,
|
|
1467
|
-
file_name=auth_models_file_name)
|
|
1468
|
-
login_endpoint_filename = f'{self.api_logic_server_dir_path.joinpath("templates/login_endpoint_imports.txt")}'
|
|
1469
|
-
auth_models_file_name = f'{self.project_directory_path.joinpath("database/authentication_models.py")}'
|
|
1470
|
-
with open(login_endpoint_filename, 'r') as file:
|
|
1471
|
-
login_endpoint_data = file.read()
|
|
1472
|
-
create_utils.insert_lines_at(lines=login_endpoint_data,
|
|
1473
|
-
at="import declarative_base", after=True,
|
|
1474
|
-
file_name=auth_models_file_name)
|
|
1475
|
-
|
|
1476
|
-
log.debug("\n==================================================================")
|
|
1477
|
-
if msg != "":
|
|
1478
|
-
log.debug(" 3. Set SECURITY_ENABLED in config.py")
|
|
1479
|
-
log.debug("==================================================================\n")
|
|
1480
|
-
create_utils.replace_string_in_file(search_for="SECURITY_ENABLED = False #",
|
|
1481
|
-
replace_with='SECURITY_ENABLED = True #',
|
|
1482
|
-
in_file=f'{self.project_directory}/config/config.py')
|
|
1483
|
-
if is_northwind: # is_nw or self.nw_db_status == "nw":
|
|
1484
|
-
log.debug("\n==================================================================")
|
|
1485
|
-
if msg != "":
|
|
1486
|
-
if msg != "":
|
|
1487
|
-
log.debug(" 4. Adding Sample authorization to security/declare_security.py")
|
|
1488
|
-
log.debug("==================================================================\n\n")
|
|
1489
|
-
nw_declare_security_py_path = self.api_logic_server_dir_path.\
|
|
1490
|
-
joinpath('prototypes/nw/security/declare_security.py')
|
|
1491
|
-
declare_security_py_path = self.project_directory_path.joinpath('security/declare_security.py')
|
|
1492
|
-
shutil.copyfile(nw_declare_security_py_path, declare_security_py_path)
|
|
1493
|
-
else:
|
|
1494
|
-
log.debug("\n==================================================================")
|
|
1495
|
-
if msg != "":
|
|
1496
|
-
log.debug(" 4. TODO: Declare authorization in security/declare_security.py")
|
|
1497
|
-
log.debug("==================================================================\n\n")
|
|
1498
|
-
self.add_auth_in_progress = False
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
1383
|
def insert_tutorial_into_readme(self):
|
|
1502
1384
|
""" insert docs tutorial.md into readme at --> Tip: create the sample """
|
|
1503
1385
|
project_readme_file_path = self.project_directory_path.joinpath('readme.md')
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
last_created_date: May
|
|
2
|
-
last_created_project_name:
|
|
3
|
-
last_created_version: 14.05.
|
|
1
|
+
last_created_date: May 27, 2025 20:52:10
|
|
2
|
+
last_created_project_name: samples/nw_sample_nocust
|
|
3
|
+
last_created_version: 14.05.12
|
|
Binary file
|
|
Binary file
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
|
|
3
|
+
import re
|
|
3
4
|
import shutil
|
|
4
5
|
import subprocess, os, sys
|
|
5
6
|
from pathlib import Path
|
|
@@ -78,14 +79,14 @@ def get_project_directory_and_api_name(project):
|
|
|
78
79
|
rtn_api_name, \
|
|
79
80
|
rtn_merge_into_prototype
|
|
80
81
|
|
|
81
|
-
def copy_md(project
|
|
82
|
+
def copy_md(project, from_doc_file: str, to_project_file: str = "README.md"):
|
|
82
83
|
""" Copy readme files (and remove !!!) from:
|
|
83
84
|
|
|
84
85
|
1. github (to acquire more recent version since release)
|
|
85
86
|
|
|
86
87
|
2. dev docs, if exists (gold version in docs, not prototypes).
|
|
87
88
|
|
|
88
|
-
Used by Sample-AI; Sample-Integration (nw-), Tutorial, Tutorial-3 (3 projects), Sample-Basic-Demo;
|
|
89
|
+
Used by Sample-AI; Sample-Integration (nw-), Tutorial, Tutorial-3 (3 projects), Sample-Basic-Demo; Manager
|
|
89
90
|
|
|
90
91
|
Removing !!! -- special handling
|
|
91
92
|
|
|
@@ -95,14 +96,18 @@ def copy_md(project: 'ProjectRun', from_doc_file: str, to_project_file: str = "R
|
|
|
95
96
|
|
|
96
97
|
Image references are made absolute (to github).
|
|
97
98
|
|
|
98
|
-
Doc Links are
|
|
99
|
+
Doc Links are made absolute.
|
|
99
100
|
|
|
100
101
|
Args:
|
|
101
|
-
project (ProjectRun): project object (project name, etc)
|
|
102
|
-
from_doc_file (str): eg, Sample-Basic_Demo.md
|
|
103
|
-
to_project_file (str, optional):
|
|
102
|
+
project (ProjectRun or Path): project object (project name, etc)
|
|
103
|
+
from_doc_file (str): eg, Sample-Basic_Demo.md (no docs/)
|
|
104
|
+
to_project_file (str, optional): location of target. Defaults to "README.md".
|
|
104
105
|
"""
|
|
105
|
-
|
|
106
|
+
if isinstance(project, Path):
|
|
107
|
+
project_path = project
|
|
108
|
+
else:
|
|
109
|
+
project_path = project.project_directory_path
|
|
110
|
+
|
|
106
111
|
to_file = project_path.joinpath(to_project_file)
|
|
107
112
|
docs_path = Path(get_api_logic_server_dir()).parent.parent
|
|
108
113
|
from_doc_file_path = docs_path.joinpath(f'Docs/docs/{from_doc_file}')
|
|
@@ -126,7 +131,10 @@ def copy_md(project: 'ProjectRun', from_doc_file: str, to_project_file: str = "R
|
|
|
126
131
|
copyfile(src = from_doc_file_path, dst = to_file)
|
|
127
132
|
|
|
128
133
|
# now remove the !!, and unindent (mkdocs features fail in a readme)
|
|
129
|
-
if to_file.exists():
|
|
134
|
+
if not to_file.exists(): # can occur if offline
|
|
135
|
+
shutil.copy(Path(get_api_logic_server_dir()).joinpath('prototypes/base').joinpath('readme.md'),
|
|
136
|
+
to_file)
|
|
137
|
+
else:
|
|
130
138
|
with open(str(to_file), "r") as readme_file:
|
|
131
139
|
readme_lines_mkdocs = readme_file.readlines()
|
|
132
140
|
readme_lines_md = []
|
|
@@ -134,7 +142,7 @@ def copy_md(project: 'ProjectRun', from_doc_file: str, to_project_file: str = "R
|
|
|
134
142
|
db_line_num = 0
|
|
135
143
|
for each_line in readme_lines_mkdocs:
|
|
136
144
|
db_line_num += 1
|
|
137
|
-
if "
|
|
145
|
+
if "title: Instant Microservices" in each_line:
|
|
138
146
|
debug_str = "Good Breakpoint"
|
|
139
147
|
if "from docsite" in each_line:
|
|
140
148
|
each_line = each_line.replace("from docsite", "from docsite, for readme")
|
|
@@ -160,14 +168,25 @@ def copy_md(project: 'ProjectRun', from_doc_file: str, to_project_file: str = "R
|
|
|
160
168
|
each_line = each_line[4:]
|
|
161
169
|
in_mkdocs_block_with_sections = True
|
|
162
170
|
each_line = each_line.replace('{:target="_blank" rel="noopener"}', '')
|
|
163
|
-
if each_line.startswith('![') or each_line.startswith('[!['):
|
|
171
|
+
if each_line.startswith('![') or each_line.startswith('[![') or each_line.startswith(' !['):
|
|
164
172
|
if "https://github.com/ApiLogicServer" not in each_line: # make doc-relative urls absolute...
|
|
165
173
|
if "creates-and-runs-video" in each_line:
|
|
166
174
|
debug_stop = "good stop"
|
|
167
175
|
each_line = each_line.replace('images', 'https://github.com/ApiLogicServer/Docs/blob/main/docs/images')
|
|
168
176
|
each_line = each_line.replace('png)', 'png?raw=true)')
|
|
177
|
+
each_line = each_line.replace('jpeg)', 'jpeg?raw=true)')
|
|
178
|
+
each_line = each_line.replace('jpg)', 'jpg?raw=true)')
|
|
169
179
|
else:
|
|
170
180
|
pass # image is absolute - don't alter
|
|
181
|
+
if '.md' in each_line:
|
|
182
|
+
# replace (<name>.md) with (https://apilogicserver.github.io/Docs/<name>)
|
|
183
|
+
each_line = re.sub(
|
|
184
|
+
r'\(([^)]+\.md)\)',
|
|
185
|
+
r'(https://apilogicserver.github.io/Docs/\1)',
|
|
186
|
+
each_line
|
|
187
|
+
)
|
|
188
|
+
each_line = each_line.replace('.md', '')
|
|
189
|
+
pass
|
|
171
190
|
readme_lines_md.append(each_line)
|
|
172
191
|
with open(str(to_file), "w") as readme_file:
|
|
173
192
|
readme_file.writelines(readme_lines_md)
|
|
@@ -7,15 +7,12 @@ import os
|
|
|
7
7
|
import pathlib
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import NewType, List, Tuple, Dict
|
|
10
|
-
|
|
11
|
-
import sqlalchemy
|
|
12
|
-
import yaml
|
|
13
10
|
from sqlalchemy import MetaData, false
|
|
14
11
|
import datetime
|
|
15
12
|
import api_logic_server_cli.create_from_model.model_creation_services as create_from_model
|
|
16
13
|
import api_logic_server_cli.create_from_model.api_logic_server_utils as create_utils
|
|
17
14
|
from dotmap import DotMap
|
|
18
|
-
|
|
15
|
+
import api_logic_server_cli.genai.genai_svcs as genai_svcs
|
|
19
16
|
from api_logic_server_cli.create_from_model.meta_model import Resource, ResourceAttribute, ResourceRelationship
|
|
20
17
|
|
|
21
18
|
|
|
@@ -74,9 +71,6 @@ class DBMLCreator(object):
|
|
|
74
71
|
"tool_type": "json-api",
|
|
75
72
|
"schema_version": "1.0",
|
|
76
73
|
"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.",
|
|
80
74
|
"description": f"API Logic Project: {self.mod_gen.project.project_name_last_node}",
|
|
81
75
|
"resources": [
|
|
82
76
|
]
|
|
@@ -198,7 +192,7 @@ class DBMLCreator(object):
|
|
|
198
192
|
|
|
199
193
|
|
|
200
194
|
|
|
201
|
-
def
|
|
195
|
+
def create_docs_mcp(self):
|
|
202
196
|
""" create docs/mcp_schema.json - create self.mcp_schema['resources'] entries like:
|
|
203
197
|
|
|
204
198
|
'''
|
|
@@ -213,6 +207,18 @@ class DBMLCreator(object):
|
|
|
213
207
|
'''
|
|
214
208
|
"""
|
|
215
209
|
|
|
210
|
+
docs_path = Path(self.mod_gen.project_directory).joinpath('docs')
|
|
211
|
+
docs_path.mkdir(parents=True, exist_ok=True)
|
|
212
|
+
# mcp_learning_src = Path(create_utils.get_api_logic_server_dir()).joinpath('prototypes/manager/system/genai/mcp_learning')
|
|
213
|
+
mcp_learning_src = genai_svcs.get_manager_path(use_env=True).joinpath('system/genai/mcp_learning')
|
|
214
|
+
mcp_learning_dst = Path(self.mod_gen.project_directory).joinpath('docs/mcp_learning')
|
|
215
|
+
if mcp_learning_dst.exists():
|
|
216
|
+
shutil.rmtree(mcp_learning_dst)
|
|
217
|
+
shutil.copytree(
|
|
218
|
+
src=mcp_learning_src,
|
|
219
|
+
dst=mcp_learning_dst
|
|
220
|
+
)
|
|
221
|
+
|
|
216
222
|
resources : list = self.mcp_schema['resources']
|
|
217
223
|
for each_resource_name in self.mod_gen.resource_list:
|
|
218
224
|
if self.do_process_resource(each_resource_name):
|
|
@@ -226,9 +232,7 @@ class DBMLCreator(object):
|
|
|
226
232
|
each_inserted_resource['fields'].append(each_attr.name)
|
|
227
233
|
each_inserted_resource['filterable'] = each_inserted_resource['fields']
|
|
228
234
|
resources.append(each_inserted_resource)
|
|
229
|
-
|
|
230
|
-
docs_path.mkdir(parents=True, exist_ok=True)
|
|
231
|
-
mcp_schema_path = Path(self.mod_gen.project_directory).joinpath('docs/mcp_schema.json')
|
|
235
|
+
mcp_schema_path = Path(self.mod_gen.project_directory).joinpath('docs/mcp_learning/mcp_schema.json')
|
|
232
236
|
# write self.mcp_schema dict to json file
|
|
233
237
|
with open(mcp_schema_path, 'w') as f:
|
|
234
238
|
json.dump(self.mcp_schema, f, indent=4)
|
|
@@ -240,5 +244,5 @@ def create(model_creation_services: create_from_model.ModelCreationServices):
|
|
|
240
244
|
"""
|
|
241
245
|
dbml_creator = DBMLCreator(model_creation_services)
|
|
242
246
|
dbml_creator.create_docs_dbml_file()
|
|
243
|
-
dbml_creator.
|
|
247
|
+
dbml_creator.create_docs_mcp()
|
|
244
248
|
|
|
Binary file
|
|
@@ -587,12 +587,16 @@ class GenAI(object):
|
|
|
587
587
|
shutil.copyfile(self.project.genai_repaired_response, response_file)
|
|
588
588
|
is_genai_demo = False
|
|
589
589
|
if os.getenv('APILOGICPROJECT_IS_GENAI_DEMO') is not None or self.project.project_name == 'genai_demo':
|
|
590
|
+
# fail safe demo - be sure AI does not fail, and that the data model names are predictable for add-cust
|
|
590
591
|
self.project.project_directory_path.joinpath('docs/project_is_genai_demo.txt').touch()
|
|
591
592
|
# and DON'T create test data (db.sqlite already set up in recursive copy)
|
|
592
593
|
project_docs_response = self.project.project_directory_path.joinpath('docs/response.json')
|
|
593
594
|
with open(project_docs_response, "w") as response_file: # WebG uses this for wg_rules
|
|
594
595
|
json.dump(self.response_dict, response_file, indent=4)
|
|
595
596
|
pass # not possible on create_db_models, since project paths not yet set by api_logic_server
|
|
597
|
+
if add_mcp := False: # bad idea - fails with Ont seeking email (which would be confusing)
|
|
598
|
+
from api_logic_server_cli.add_cust import add_cust
|
|
599
|
+
add_cust.add_basic_demo_customizations(project=self.project)
|
|
596
600
|
|
|
597
601
|
else: # normal path
|
|
598
602
|
genai_svcs.rebuild_test_data_for_project(
|
|
@@ -924,14 +924,22 @@ def call_chatgpt(messages: List[Dict[str, str]], api_version: str, using: str) -
|
|
|
924
924
|
log.error(f"\n\nError: ChatGPT call failed\n- please see https://apilogicserver.github.io/Docs/WebGenAI-CLI/#configuratio\n{inst}\n\n")
|
|
925
925
|
sys.exit(1)
|
|
926
926
|
|
|
927
|
-
def get_manager_path() -> Path:
|
|
927
|
+
def get_manager_path(use_env: bool = False) -> Path:
|
|
928
928
|
""" Checks cwd, parent, and grandparent for system/genai
|
|
929
929
|
|
|
930
930
|
* Possibly could add cli arg later
|
|
931
931
|
|
|
932
|
+
Args:
|
|
933
|
+
use_env: bool if cannot find manager from project, use install location
|
|
934
|
+
|
|
932
935
|
Returns:
|
|
933
936
|
Path: Manager path (contains system/genai)
|
|
934
937
|
"""
|
|
938
|
+
|
|
939
|
+
if use_env and os.getenv("APILOGICSERVER_HOME") is not None:
|
|
940
|
+
result_path = Path(os.getenv("APILOGICSERVER_HOME")).joinpath('api_logic_server_cli/prototypes/manager')
|
|
941
|
+
return result_path
|
|
942
|
+
|
|
935
943
|
result_path = Path(os.getcwd()) # normal case - project at manager root
|
|
936
944
|
check_system_genai = result_path.joinpath('system/genai')
|
|
937
945
|
if check_system_genai.exists():
|
|
@@ -964,8 +972,8 @@ def get_manager_path() -> Path:
|
|
|
964
972
|
|
|
965
973
|
result_path = result_path.parent # try ancestors - this is for import testing
|
|
966
974
|
check_system_genai = result_path.joinpath('system/genai')
|
|
975
|
+
|
|
967
976
|
assert check_system_genai.exists(), f"Manager Directory not found: {check_system_genai}"
|
|
968
|
-
|
|
969
977
|
return result_path
|
|
970
978
|
|
|
971
979
|
|
api_logic_server_cli/manager.py
CHANGED
|
@@ -117,7 +117,8 @@ def create_manager(clean: bool, open_with: str, api_logic_server_path: Path,
|
|
|
117
117
|
from_docker_dir = api_logic_server_path.joinpath('prototypes/manager_docker')
|
|
118
118
|
copied_path = shutil.copytree(src=from_docker_dir, dst=to_dir, dirs_exist_ok=True)
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
# get latest readme from git (eg, has been updated since pip install)
|
|
121
|
+
file_src = f"https://raw.githubusercontent.com/ApiLogicServer/ApiLogicServer-src/main/api_logic_server_cli/prototypes/manager/README.md"
|
|
121
122
|
readme_path = to_dir.joinpath('README.md')
|
|
122
123
|
try:
|
|
123
124
|
r = requests.get(file_src) # , params=params)
|
|
@@ -130,6 +131,7 @@ def create_manager(clean: bool, open_with: str, api_logic_server_path: Path,
|
|
|
130
131
|
pass # just fall back to using the pip-installed version
|
|
131
132
|
except: # do NOT fail
|
|
132
133
|
pass # just fall back to using the pip-installed version
|
|
134
|
+
create_utils.copy_md(from_doc_file='Sample-Basic-Tour.md', project = to_dir)
|
|
133
135
|
|
|
134
136
|
if not samples:
|
|
135
137
|
shutil.rmtree(to_dir.joinpath(f'{docker_volume}system/app_model_editor'))
|
|
@@ -107,6 +107,32 @@
|
|
|
107
107
|
"console": "internalConsole",
|
|
108
108
|
"internalConsoleOptions": "openOnSessionStart"
|
|
109
109
|
},
|
|
110
|
+
{
|
|
111
|
+
"name": " - Run designated Python file",
|
|
112
|
+
// use this for test programs, eg to test the server
|
|
113
|
+
"type": "debugpy",
|
|
114
|
+
"request": "launch",
|
|
115
|
+
"cwd": "${workspaceFolder}",
|
|
116
|
+
"program": "${input:inputProjectName}",
|
|
117
|
+
"redirectOutput": true,
|
|
118
|
+
"justMyCode": false,
|
|
119
|
+
"args": ["go"],
|
|
120
|
+
"console": "internalConsole",
|
|
121
|
+
"internalConsoleOptions": "openOnSessionStart"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"name": "MCP - Model Context Protocol - Client Executor",
|
|
125
|
+
// a simple client to test the Model Context Protocol, default request for basic_demo
|
|
126
|
+
"type": "debugpy",
|
|
127
|
+
"request": "launch",
|
|
128
|
+
"cwd": "${workspaceFolder}",
|
|
129
|
+
"program": "integration/mcp/mcp_client_executor.py",
|
|
130
|
+
"redirectOutput": true,
|
|
131
|
+
"justMyCode": false,
|
|
132
|
+
"args": ["list customers with balance over 100"],
|
|
133
|
+
"console": "internalConsole",
|
|
134
|
+
"internalConsoleOptions": "openOnSessionStart"
|
|
135
|
+
},
|
|
110
136
|
{
|
|
111
137
|
"name": "Install Ontimize (npm install)",
|
|
112
138
|
"type": "node",
|
|
@@ -270,5 +296,19 @@
|
|
|
270
296
|
"justMyCode": false,
|
|
271
297
|
"console": "integratedTerminal"
|
|
272
298
|
}
|
|
299
|
+
],
|
|
300
|
+
"inputs": [
|
|
301
|
+
{
|
|
302
|
+
"id": "inputProjectName",
|
|
303
|
+
"type": "promptString",
|
|
304
|
+
"description": "Python File Name to Run",
|
|
305
|
+
"default": "integration/mcp/mcp_client_executor.py"
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
"id": "inputMcpRequest",
|
|
309
|
+
"type": "promptString",
|
|
310
|
+
"description": "Request for MCP Client Executor",
|
|
311
|
+
"default": "find customers with balance over 500"
|
|
312
|
+
}
|
|
273
313
|
]
|
|
274
314
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from flask import request, jsonify
|
|
2
|
+
from flask import Flask, redirect, send_from_directory, send_file
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import json
|
|
6
|
+
import io
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
from config.config import Args # circular import error if at top
|
|
10
|
+
|
|
11
|
+
app_logger = logging.getLogger("api_logic_server_app")
|
|
12
|
+
|
|
13
|
+
def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators = []):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
def get_server_url():
|
|
17
|
+
""" return the server URL for the OpenAPI spec """
|
|
18
|
+
result = f'http://{Args.instance.swagger_host}:{Args.instance.swagger_port}'
|
|
19
|
+
# get env variable API_LOGIC_SERVER_TUNNEL (or None)
|
|
20
|
+
if tunnel_url := os.getenv("API_LOGIC_SERVER_TUNNEL", None):
|
|
21
|
+
app_logger.info(f".. tunnel URL: {tunnel_url}")
|
|
22
|
+
result = tunnel_url
|
|
23
|
+
return result # + '/api'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@app.route('/.well-known/mcp.json', methods=['GET'])
|
|
28
|
+
def mcp_discovery(path=None):
|
|
29
|
+
''' called by mcp_client_executor for discovery, eg:
|
|
30
|
+
```
|
|
31
|
+
{
|
|
32
|
+
"tool_type": "json-api",
|
|
33
|
+
"schema_version": "1.0",
|
|
34
|
+
"base_url": "https://crm.company.com",
|
|
35
|
+
"resources": [
|
|
36
|
+
{
|
|
37
|
+
"name": "Customer",
|
|
38
|
+
"path": "/Customer",
|
|
39
|
+
"methods": ["GET", "PATCH"],
|
|
40
|
+
"fields": ["id", "name", "balance", "credit_limit"],
|
|
41
|
+
"filterable": ["name", "credit_limit"],
|
|
42
|
+
"example": "List customers with credit over 5000"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
test: curl -X GET "http://localhost:5656/.well-known/mcp.json"
|
|
48
|
+
'''
|
|
49
|
+
# return docs/mcp_schema.json
|
|
50
|
+
schema_path = os.path.join(project_dir, "docs/mcp_learning/mcp_schema.json")
|
|
51
|
+
try:
|
|
52
|
+
with open(schema_path, "r") as schema_file:
|
|
53
|
+
schema = json.load(schema_file)
|
|
54
|
+
return jsonify(schema), 200
|
|
55
|
+
except Exception as e:
|
|
56
|
+
app_logger.error(f"Error loading MCP schema: {e}")
|
|
57
|
+
return jsonify({"error": "MCP schema not found"}), 404
|
|
58
|
+
pass
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import json
|
|
1
2
|
from sqlalchemy.ext.declarative import declarative_base
|
|
2
3
|
from sqlalchemy import Column, DECIMAL, Date, ForeignKey, Integer, String
|
|
3
|
-
from safrs import SAFRSBase
|
|
4
|
+
from safrs import SAFRSBase, ValidationError
|
|
4
5
|
from flask_login import UserMixin
|
|
5
6
|
import safrs, flask_sqlalchemy
|
|
6
7
|
from safrs import jsonapi_attr
|
|
7
8
|
from flask_sqlalchemy import SQLAlchemy
|
|
8
9
|
from datetime import datetime
|
|
10
|
+
import operator
|
|
11
|
+
import json
|
|
12
|
+
|
|
9
13
|
|
|
10
14
|
Base = declarative_base() # type: flask_sqlalchemy.model.DefaultMeta
|
|
11
15
|
#vh new x
|
|
@@ -28,7 +32,7 @@ def jsonapi_filter(cls):
|
|
|
28
32
|
if sqlWhere != "":
|
|
29
33
|
return query.filter(text(sqlWhere))
|
|
30
34
|
else:
|
|
31
|
-
return query.filter(
|
|
35
|
+
return query.filter(and_(*expressions))
|
|
32
36
|
|
|
33
37
|
class SAFRSBaseX(SAFRSBase, safrs.DB.Model):
|
|
34
38
|
__abstract__ = True
|
|
@@ -58,6 +62,68 @@ class SAFRSBaseX(SAFRSBase, safrs.DB.Model):
|
|
|
58
62
|
|
|
59
63
|
return super()._s_parse_attr_value(attr_name, attr_val)
|
|
60
64
|
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def _s_filter(cls, *filter_args, **filter_kwargs):
|
|
68
|
+
"""
|
|
69
|
+
Apply a filter to this model
|
|
70
|
+
:param filter_args: A list of filters information to apply, passed as a request URL parameter.
|
|
71
|
+
Each filter object has the following fields:
|
|
72
|
+
- name: The name of the field you want to filter on.
|
|
73
|
+
- op: The operation you want to use (all sqlalchemy operations are available). The valid values are:
|
|
74
|
+
- like: Invoke SQL like (or "ilike", "match", "notilike")
|
|
75
|
+
- eq: check if field is equal to something
|
|
76
|
+
- ge: check if field is greater than or equal to something
|
|
77
|
+
- gt: check if field is greater than to something
|
|
78
|
+
- ne: check if field is not equal to something
|
|
79
|
+
- is_: check if field is a value
|
|
80
|
+
- is_not: check if field is not a value
|
|
81
|
+
- le: check if field is less than or equal to something
|
|
82
|
+
- lt: check if field is less than to something
|
|
83
|
+
- val: The value that you want to compare.
|
|
84
|
+
:return: sqla query object
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
filters = json.loads(filter_args[0])
|
|
88
|
+
except json.decoder.JSONDecodeError:
|
|
89
|
+
raise ValidationError("Invalid filter format (see https://github.com/thomaxxl/safrs/wiki)")
|
|
90
|
+
|
|
91
|
+
if not isinstance(filters, list):
|
|
92
|
+
filters = [filters]
|
|
93
|
+
|
|
94
|
+
expressions = []
|
|
95
|
+
query = cls._s_query
|
|
96
|
+
|
|
97
|
+
for filt in filters:
|
|
98
|
+
if not isinstance(filt, dict):
|
|
99
|
+
safrs.log.warning(f"Invalid filter '{filt}'")
|
|
100
|
+
continue
|
|
101
|
+
attr_name = filt.get("name")
|
|
102
|
+
attr_val = filt.get("val")
|
|
103
|
+
if attr_name != "id" and attr_name not in cls._s_jsonapi_attrs:
|
|
104
|
+
raise ValidationError(f'Invalid filter "{filt}", unknown attribute "{attr_name}"')
|
|
105
|
+
|
|
106
|
+
op_name = filt.get("op", "").strip("_")
|
|
107
|
+
attr = cls._s_jsonapi_attrs[attr_name] if attr_name != "id" else cls.id
|
|
108
|
+
if op_name in ["in", "notin"]:
|
|
109
|
+
op = getattr(attr, op_name + "_")
|
|
110
|
+
query = query.filter(op(attr_val))
|
|
111
|
+
elif op_name in ["like", "ilike", "match", "notilike"] and hasattr(attr, "like"):
|
|
112
|
+
# => attr is Column or InstrumentedAttribute
|
|
113
|
+
like = getattr(attr, op_name)
|
|
114
|
+
expressions.append(like(attr_val))
|
|
115
|
+
elif not hasattr(operator, op_name):
|
|
116
|
+
raise ValidationError(f'Invalid filter "{filt}", unknown operator "{op_name}"')
|
|
117
|
+
else:
|
|
118
|
+
op = getattr(operator, op_name)
|
|
119
|
+
expressions.append(op(attr, attr_val))
|
|
120
|
+
|
|
121
|
+
if len(filters) > 1:
|
|
122
|
+
return query.filter(operator.and_(*expressions))
|
|
123
|
+
else:
|
|
124
|
+
return query.filter(*expressions)
|
|
125
|
+
|
|
126
|
+
|
|
61
127
|
class TestBase(Base):
|
|
62
128
|
__abstract__ = True
|
|
63
129
|
def __init__(self, *args, **kwargs):
|