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.
Files changed (50) hide show
  1. api_logic_server_cli/add_cust/add_cust.py +7 -21
  2. api_logic_server_cli/api_logic_server.py +4 -2
  3. api_logic_server_cli/api_logic_server_info.yaml +2 -2
  4. api_logic_server_cli/create_from_model/__pycache__/dbml.cpython-312.pyc +0 -0
  5. api_logic_server_cli/create_from_model/__pycache__/ont_build.cpython-312.pyc +0 -0
  6. api_logic_server_cli/create_from_model/dbml.py +3 -0
  7. api_logic_server_cli/prototypes/basic_demo/customizations/api/api_discovery/{mcp_server_executor.py → mcp_discovery.py} +1 -43
  8. api_logic_server_cli/prototypes/basic_demo/customizations/config/server_setup.py +388 -0
  9. api_logic_server_cli/prototypes/basic_demo/customizations/database/system/SAFRSBaseX.py +139 -0
  10. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/.DS_Store +0 -0
  11. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/README_mcp.md +3 -1
  12. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/mcp_client_executor.py +144 -131
  13. api_logic_server_cli/prototypes/basic_demo/customizations/logic/declare_logic.py +22 -2
  14. api_logic_server_cli/prototypes/basic_demo/iteration/logic/declare_logic.py +1 -1
  15. api_logic_server_cli/prototypes/nw/logic/declare_logic.py +2 -2
  16. api_logic_server_cli/prototypes/nw_no_cust/.obsidian/app.json +1 -0
  17. api_logic_server_cli/prototypes/nw_no_cust/.obsidian/appearance.json +1 -0
  18. api_logic_server_cli/prototypes/nw_no_cust/.obsidian/core-plugins.json +31 -0
  19. api_logic_server_cli/prototypes/nw_no_cust/.obsidian/workspace.json +166 -0
  20. apilogicserver-14.5.4.dist-info/METADATA +168 -0
  21. {apilogicserver-14.5.0.dist-info → apilogicserver-14.5.4.dist-info}/RECORD +25 -43
  22. {apilogicserver-14.5.0.dist-info → apilogicserver-14.5.4.dist-info}/WHEEL +1 -1
  23. api_logic_server_cli/prototypes/basic_demo/customizations/api/api_discovery/proper_update_def.json +0 -71
  24. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/1_langchain_loader.py +0 -71
  25. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/2_gpt_mcp_prompt.txt +0 -19
  26. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/multi_mcp_flow/multi_mcp_flow.png +0 -0
  27. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/multi_mcp_flow/multi_mcp_orchestration.yaml +0 -49
  28. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/multi_mcp_flow/wny mcp flows.png +0 -0
  29. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/natlang_to_api.py +0 -73
  30. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/curl.txt +0 -5
  31. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/images/MCP Overview.png +0 -0
  32. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/images/MCP_Arch.png +0 -0
  33. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/images/MCP_Overview_Executor.png +0 -0
  34. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/invoke_llm/1 - prompt_messages_array.json +0 -10
  35. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/invoke_llm/2 - completion_tool_context.json +0 -12
  36. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/llm_schema.txt +0 -38
  37. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/nw_swagger_2.yaml +0 -17393
  38. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/nw_swagger_3.yaml +0 -16660
  39. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/nw_swagger_3_relaxed.yaml +0 -109
  40. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/proxy_server.py +0 -51
  41. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/proxy_serverZ.py +0 -72
  42. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/validate_jsonapi.py +0 -64
  43. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/run_executor.py +0 -23
  44. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/swagger_converter.py +0 -65
  45. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/z_old/3_executor_test_agent.py +0 -52
  46. api_logic_server_cli/prototypes/manager/README_X.md +0 -663
  47. apilogicserver-14.5.0.dist-info/METADATA +0 -76
  48. {apilogicserver-14.5.0.dist-info → apilogicserver-14.5.4.dist-info}/entry_points.txt +0 -0
  49. {apilogicserver-14.5.0.dist-info → apilogicserver-14.5.4.dist-info}/licenses/LICENSE +0 -0
  50. {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 # can't use project.is_genai_demo because this is not the create command...
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
- add_genai_customizations(project=project, do_security=False)
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.00" # last public release: 14.04.00
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 15, 2025 12:52:13
1
+ last_created_date: May 20, 2025 15:00:15
2
2
  last_created_project_name: ../../../servers/basic_demo
3
- last_created_version: 14.04.08
3
+ last_created_version: 14.05.03
@@ -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
+