ApiLogicServer 14.3.25__py3-none-any.whl → 14.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. api_logic_server_cli/api_logic_server.py +4 -14
  2. api_logic_server_cli/api_logic_server_info.yaml +3 -3
  3. api_logic_server_cli/cli.py +16 -7
  4. api_logic_server_cli/create_from_model/__pycache__/create_db_from_model.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/__pycache__/ont_create.cpython-312.pyc +0 -0
  7. api_logic_server_cli/create_from_model/create_db_from_model.py +2 -0
  8. api_logic_server_cli/create_from_model/ont_build.py +19 -14
  9. api_logic_server_cli/create_from_model/ont_create.py +5 -5
  10. api_logic_server_cli/create_from_model/safrs-react-admin-npm-build/static/.DS_Store +0 -0
  11. api_logic_server_cli/database/nw-gold-fix.sql +62 -0
  12. api_logic_server_cli/database/nw-gold.sqlite +0 -0
  13. api_logic_server_cli/{prototypes/manager/webgenai → fragments}/docker-compose.yml +1 -1
  14. api_logic_server_cli/genai/genai.py +42 -11
  15. api_logic_server_cli/genai/genai_graphics.py +252 -38
  16. api_logic_server_cli/genai/genai_svcs.py +20 -12
  17. api_logic_server_cli/manager.py +19 -10
  18. api_logic_server_cli/prototypes/.DS_Store +0 -0
  19. api_logic_server_cli/prototypes/base/.DS_Store +0 -0
  20. api_logic_server_cli/prototypes/base/.vscode/launch.json +19 -0
  21. api_logic_server_cli/prototypes/base/api/expose_api_models.py +3 -1
  22. api_logic_server_cli/prototypes/base/api_logic_server_run.py +5 -2
  23. api_logic_server_cli/prototypes/base/config/activate_logicbank.py +1 -0
  24. api_logic_server_cli/prototypes/base/config/config.py +57 -14
  25. api_logic_server_cli/prototypes/base/config/logging.yml +1 -0
  26. api_logic_server_cli/prototypes/base/config/server_setup.py +33 -1
  27. api_logic_server_cli/prototypes/base/database/test_data/readme.md +3 -1
  28. api_logic_server_cli/prototypes/base/devops/docker-standard-image/docker-compose-standard-image.yml +7 -2
  29. api_logic_server_cli/prototypes/base/docs/training/logic_bank_api.prompt +314 -0
  30. api_logic_server_cli/prototypes/base/docs/training/logic_example.py +41 -0
  31. api_logic_server_cli/prototypes/base/integration/kafka/kafka_producer.py +7 -3
  32. api_logic_server_cli/prototypes/base/integration/system/FlaskKafka.py +5 -1
  33. api_logic_server_cli/prototypes/base/ui/templates/bar_chart.jinja +64 -0
  34. api_logic_server_cli/prototypes/genai_demo/ui/admin/admin.yaml +1 -1
  35. api_logic_server_cli/prototypes/manager/README.md +26 -4
  36. api_logic_server_cli/prototypes/manager/system/genai/.DS_Store +0 -0
  37. api_logic_server_cli/prototypes/manager/system/genai/examples/.DS_Store +0 -0
  38. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/.DS_Store +0 -0
  39. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo.prompt +0 -10
  40. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo.response_example +32 -10
  41. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_docs_logic/docs/002_create_db_models.prompt +4 -4
  42. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_docs_logic/docs/003_create_db_models.response +77 -47
  43. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_informal.prompt +1 -1
  44. api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/dashboard_services.jinja +83 -0
  45. api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/graphics_dashboard_WIP.py +34 -0
  46. api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/{graphics_services.py → graphics_services_api_xxx.py} +0 -9
  47. api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/graphics_services_db.jinja +46 -0
  48. api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/graphics_services_db_each_method.jinja +36 -0
  49. api_logic_server_cli/prototypes/manager/system/genai/prompt_inserts/graphics.prompt +7 -3
  50. api_logic_server_cli/prototypes/manager/system/genai/prompt_inserts/response_format.prompt +8 -1
  51. api_logic_server_cli/prototypes/manager/system/install-ApiLogicServer-dev/install-ApiLogicServer-dev.ps1 +100 -0
  52. api_logic_server_cli/prototypes/manager/system/install-ApiLogicServer-dev/install-ApiLogicServer-dev.sh +116 -0
  53. api_logic_server_cli/prototypes/manager/system/install-ApiLogicServer-dev/readme.md +7 -0
  54. api_logic_server_cli/prototypes/manager/system/style-guide.yaml +2 -2
  55. api_logic_server_cli/prototypes/manager/webgenai/README.md +6 -0
  56. api_logic_server_cli/prototypes/nw/docs/graphics/count_orders_by_category.prompt +1 -0
  57. api_logic_server_cli/prototypes/nw/docs/graphics/order_count_by_month.prompt +1 -0
  58. api_logic_server_cli/prototypes/nw/docs/graphics/request copy.json +892 -0
  59. api_logic_server_cli/prototypes/nw/docs/graphics/request.json +6 -0
  60. api_logic_server_cli/prototypes/nw/docs/graphics/response.json +17 -0
  61. api_logic_server_cli/prototypes/nw/docs/graphics/response.yaml +59 -0
  62. api_logic_server_cli/prototypes/nw/docs/graphics/sales_by_category.prompt +1 -0
  63. api_logic_server_cli/prototypes/nw/ui/admin/home.js +5 -4
  64. api_logic_server_cli/prototypes/nw/ui/app_model_custom.yaml +851 -1082
  65. api_logic_server_cli/prototypes/nw_no_cust/docs/graphics/count_orders_by_category.prompt +1 -0
  66. api_logic_server_cli/prototypes/nw_no_cust/docs/graphics/sales_by_employee.prompt +1 -0
  67. api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/1_langchain_loader.py +19 -0
  68. api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/2_gpt_mcp_prompt.txt +19 -0
  69. api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/3_executor_test_agent.py +38 -0
  70. api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/README.md +17 -0
  71. api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/resources/curl.txt +4 -0
  72. api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/resources/nw_swagger_3.yaml +16660 -0
  73. api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/run_executor.py +23 -0
  74. api_logic_server_cli/prototypes/ont_app/ontimize_seed/nginx/nginx.conf +2 -2
  75. api_logic_server_cli/prototypes/ont_app/ontimize_seed/package.json +6 -6
  76. api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/app/app.config.ts +2 -1
  77. api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/environments/environment.prod.ts +5 -5
  78. api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/environments/environment.ts +5 -5
  79. api_logic_server_cli/prototypes/ont_app/templates/app_config.jinja +1 -1
  80. api_logic_server_cli/prototypes/ont_app/templates/detail_template.html +1 -1
  81. api_logic_server_cli/prototypes/ont_app/templates/new_template.html +16 -16
  82. apilogicserver-14.4.0.dist-info/METADATA +76 -0
  83. {apilogicserver-14.3.25.dist-info → apilogicserver-14.4.0.dist-info}/RECORD +87 -56
  84. {apilogicserver-14.3.25.dist-info → apilogicserver-14.4.0.dist-info}/WHEEL +1 -1
  85. api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/service_template_jsonapi_rpc.jinja +0 -37
  86. api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/service_template_unused.jinja +0 -38
  87. apilogicserver-14.3.25.dist-info/METADATA +0 -167
  88. {apilogicserver-14.3.25.dist-info → apilogicserver-14.4.0.dist-info}/entry_points.txt +0 -0
  89. {apilogicserver-14.3.25.dist-info → apilogicserver-14.4.0.dist-info}/licenses/LICENSE +0 -0
  90. {apilogicserver-14.3.25.dist-info → apilogicserver-14.4.0.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
  import importlib
7
7
  from api_logic_server_cli.genai.genai_utils import call_chatgpt
8
8
  import requests
9
- import os
9
+ import os, time
10
10
  import datetime
11
11
  import create_from_model.api_logic_server_utils as utils
12
12
  import time
@@ -29,49 +29,101 @@ K_data_model_prompt = "Use SQLAlchemy to create"
29
29
  log = logging.getLogger(__name__)
30
30
 
31
31
  class GenAIGraphics(object):
32
- """
33
- Adds Graphics to **existing** projects (genai project or als project):
34
- * adds `api/api_discovery` file(s) to project
35
- * adds html to `home.js` (? currently just creating a 1-off in api/api_discovery)
32
+ """ 4/3/2025
33
+ Adds Graphics to projects (genai project or als project):
34
+ * adds `database/database_discovery` file to project (methods on database.models classes)
35
+ * adds `api/api_discovery` file to project (dashboard services - calls db methods, above)
36
+ * adds `docs/graphics` prompt files to **wg** project (graphics prompt files)
36
37
 
37
38
  Invoked from:
38
39
  1. **New GenAI Project:** for newly created project (e,g, mgr system/genai/examples/genai_demo/genai_demo.prompt)
39
- * `--using` is None ==> Docs folder already has WGResponse.graphics[]
40
- 2. **Existing Project:** CLI/genai-graphics existing project, using *docs/graphics* eg
41
- * `--using` ==> Call ChatGPT for WGResponse.graphics `<project>/docs/graphics/*.prompt`
40
+ * `--using` is None ==> Docs folder already has WGResponse.graphics[]
41
+ * see api_logic_server_cli/genai/genai_svcs.py#insert_logic_into_created_project
42
+ 2. **Existing Project:** `als genai-graphics [--using]` # existing project
43
+ * `--using` ==> Call ChatGPT for WGResponse.graphics, default = `<project>/docs/graphics/*.prompt`
42
44
  * note: dbml not rebuilt after rebuild-from-db
43
45
  3. **Existing WG Project:** in-place (do not create new project with new test data)
44
46
  * Same as #1, but requires WG UI change ('in place', 'graphics' button, ...) to use genai_graphics cmd
45
47
 
48
+ Testing:
49
+ * BLT to create manager
50
+ * Test from source: launch.json (in group 3) has `Add Graphics to blt/samples/nw...`
51
+ * Note: uses `<mgr>/system/genai/graphics_templates`
52
+ * Don't forget to copy these back to `api_logic_server_cli/prototypes/manager/system/genai/graphics_templates`
53
+ * Optionally: update `bypass_for_debug` to True to skip ChatGPT call
46
54
 
47
- **Issue:** what is the persistence model for graphics? (eg, in docs/graphics, or docs/response.json, wg database??)
48
- * if existing wg project, is docs/response.json updated?
49
-
55
+ Persistence model for graphics: docs/response.json (also in docs/graphics/response.json, for als customization)
56
+ * Requirements:
57
+ 1. Fail-safe: do not let WG projects fail due to graphics - but alert user, just once
58
+ 2. Iterable: do not lose graphics on WG iteration
59
+ * Running design: 4/12
60
+ * Fail-safe: implemented in dashboard_service.py -- for each <graphic.name> query:
61
+ 1. if exists(docs/graphics/<graphics.name>.err), bypass the query
62
+ 2. wrap each dashboard_service query in a try/except block
63
+ 3. if exception,
64
+ * return "graphics failed" to iFrame so user can see it
65
+ * create docs/graphics/<graphics>.err to inhibit future calls
66
+ * For wg (--using == None):
67
+ * iterations build on docs/response.json, so graphics are preserved
68
+ * todo: verify add-rules
69
+ * this code creates docs/graphics/<graphics.name>.prompt
70
+ * preserves graphics when opening project in als mode
71
+ * ALS developers manage their own docs/graphics
72
+
50
73
  Open Issues
51
- * How to integrate with als/wg home.js?
52
- * How to enforce licensing?
53
- * How to choose graph vs chart?
74
+ * How to enforce licensing? eg, create stubbed api/api_discovery/dashboard_services.py
75
+ * How can user delete or alter the graphics? Activate the Project Summary Graphics button [replace]
76
+ * Graphics for wg projects (not showing)
77
+ * No Graphics (just shows {} )
54
78
 
55
79
  """
56
80
 
57
- def __init__(self, project: Project, using: str, genai_version: str):
81
+ def __init__(self, project: Project, using: str, genai_version: str, replace_with: str):
58
82
  """
59
83
  Add graphics to existing projects - [see docs](https://apilogicserver.github.io/Docs/WebGenAI-CLI/#add-graphics-to-existing-projects)
60
84
 
61
- see key_module_map() for key methods
85
+ Called to inject graphics into existing project, by:
86
+ 1. genai#insert_logic for NEW WG projects (--replace_with = '!new-wg', using already-built docs dir)
87
+ 2. cli for EXISTING projects (add from docs/graphics, or update per replace_with)
62
88
 
89
+ Args:
90
+ project (Project): Project object
91
+ using (str): path to graphics prompt files (set by genai#insert_logic, or None, for existing project)
92
+ replace_with (str): (request type): '!using' (default), '!new-wg', '!delete', '!retry`, or '!request'
93
+ genai_version (str): GenAI version to use
63
94
  """
64
95
 
65
96
  self.project = project
66
- self.project.genai_using = using
97
+ self.using = using
67
98
  self.manager_path = genai_svcs.get_manager_path()
68
-
69
- if using is None: # New GenAI Project: use docs/response.json
99
+ self.start_time = time.time()
100
+ self.replace_with = replace_with
101
+ ''' '!using', '!new-wg', '!delete', '!retry`, or '!request Graph Sales... '''
102
+ graphics_response_path = self.project.project_directory_path.joinpath('docs/graphics/response.json') # assume existing project
103
+ if replace_with == '!new-wg': # if new webgenai, response already prepared here
70
104
  graphics_response_path = self.project.project_directory_path.joinpath('docs/response.json')
71
- else: # Existing (any) Project - use graphics files -> ChatGPT
72
- graphics_response_path = self.project.project_directory_path.joinpath('docs/graphics/response.json')
105
+
106
+ log.info(f"\nGenAIGraphics start...")
107
+ log.info(f"... args:")
108
+ log.info(f"..... self.replace_with: {self.replace_with}")
109
+ log.info(f"..... self.using: {self.using}")
110
+ log.info(f"..... graphics_response_path: {graphics_response_path}")
111
+ log.info(f"..... self.project.project_directory_actual: {self.project.project_directory_actual}")
112
+ log.info(f"..... self.project.project_directory_path: {str(self.project.project_directory_path)}")
113
+
114
+ if self.replace_with != '!new-wg' and self.replace_with != '!using' : # update existing genai project
115
+ replaced_graphics = self.graphics_replace_with_in_existing_project()
116
+ if self.replace_with == '!delete': # we are done (else create docs/graphics prompts for processing below)
117
+ log.info(f"... update existing genai project - delete graphics")
118
+ return
119
+ log.info(f"... update genai existing project - from docs/graphics prompts with {replaced_graphics}")
120
+
121
+ if replace_with == '!new-wg':
122
+ log.info(f"... NEW WG project - already built: docs/002_create_db_models.prompt")
123
+ else:
124
+ log.info(f"... EXISTING project - process prompts in docs/graphics")
73
125
  if bypass_for_debug := False:
74
- pass
126
+ pass # uses already-built docs/graphics/response.json
75
127
  else:
76
128
  prompt = genai_svcs.read_and_expand_prompt(self.manager_path.joinpath('system/genai/prompt_inserts/graphics_request.prompt'))
77
129
  prompt_lines = prompt.split('\n') # ChatGPT instructions
@@ -87,74 +139,236 @@ class GenAIGraphics(object):
87
139
  using = self.project.project_directory_path.joinpath('docs/graphics'),
88
140
  api_version=genai_version)
89
141
 
90
- self.process_graphics_response(graphics_response_path)
142
+ self.create_data_class_methods(graphics_response_path)
143
+ self.create_graphics_dashboard_service(graphics_response_path)
144
+ self.create_genai_graphics_prompts(graphics_response_path)
145
+ log.info(f"\ngenai-graphics completed in [{str(int(time.time() - self.start_time))} secs] \n")
91
146
  pass
92
147
 
93
- def process_graphics_response(self, graphics_response_path: Path):
94
- """ Process graphics response from ChatGPT graphics_response_path """
148
+ def create_data_class_methods(self, graphics_response_path: Path):
149
+ """ Process graphics response from ChatGPT docs/graphics/response.json
150
+ * 'graphics' attributes map directly (by name) to <mgr>/system/genai/graphics_templates
151
+ """
95
152
 
96
- graphic_services_header_path = self.manager_path.joinpath('system/genai/graphics_templates/graphics_services.py')
97
- graphics_services_path = self.project.project_directory_path.joinpath('api/api_discovery/graphics_services.py')
98
- shutil.copy(graphic_services_header_path, graphics_services_path)
153
+ shutil.copy(self.manager_path.joinpath('system/genai/graphics_templates/graphics_services_db.jinja'),
154
+ self.project.project_directory_path.joinpath('database/database_discovery/graphics_services.py')) # all the db-class methods are created in this file
99
155
 
100
156
  # open and read the graphics_response_path json file
101
157
  assert graphics_response_path.exists(), f'Graphics response file not found: {graphics_response_path}'
102
158
  with open(graphics_response_path, 'r') as file:
103
159
  graphics_response = json.load(file)
104
- log.info(f'Graphics response loaded from {graphics_response_path}')
160
+ log.info(f'... create_data_class_methods - from {graphics_response_path}')
105
161
  graphics = graphics_response['graphics']
106
162
  for each_graphic in graphics: # add each service to api/api_discovery
107
163
  self.fix_sqlalchemy_query(each_graphic)
108
164
  env = Environment(loader=FileSystemLoader(self.manager_path.joinpath('system/genai/graphics_templates')))
109
165
 
110
- template = env.get_template('service_template_jsonapi_rpc.jinja')
166
+ template = env.get_template('graphics_services_db_each_method.jinja')
111
167
  rendered_result = template.render( **each_graphic )
112
- with open(self.project.project_directory_path.joinpath(f'api/api_discovery/graphics_services.py'), 'a') as out_file:
168
+ with open(self.project.project_directory_path.joinpath(f'database/database_discovery/graphics_services.py'), 'a') as out_file:
113
169
  out_file.write(rendered_result)
114
170
 
171
+ log.info(f'..... added db class method: {each_graphic['name']} to database_discovery')
172
+ pass
173
+
174
+
175
+ """ note it needs the /1 - what is that about?
176
+ curl -X 'GET' \
177
+ 'http://localhost:5656/api/Category/sales_by_category' \
178
+ -H 'accept: application/vnd.api+json' \
179
+ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MzY5MTc3OCwianRpIjoiNDBlYzZkNGMtMzk4My00OGEwLTgxMjQtYzQwY2RmYWFiZWRhIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InUxIiwibmJmIjoxNzQzNjkxNzc4LCJleHAiOjE3NDM3MDUwOTh9.WLDxdkp3PIsgUqR0t9-ymQDR0eOAECdQsgS_3YTqAQ0'
180
+ """
181
+
182
+ def create_graphics_dashboard_service(self, graphics_response_path: Path):
183
+ """ Process graphics response from ChatGPT graphics_response_path """
184
+
185
+ # open and read the graphics_response_path json file
186
+ assert graphics_response_path.exists(), f'Graphics response file not found: {graphics_response_path}'
187
+ with open(graphics_response_path, 'r') as file:
188
+ graphics_response = json.load(file)
189
+ if 'graphics' not in graphics_response:
190
+ log.error(f'No graphics found in {graphics_response_path}')
191
+ return
192
+ graphics = graphics_response['graphics']
193
+
194
+ env = Environment(loader=FileSystemLoader(self.manager_path.joinpath('system/genai/graphics_templates')))
195
+ iframe_templates= []
196
+ has_iframe = False
197
+ iframe_links = []
198
+ dashboards = []
199
+ cnt = 0
200
+ template = env.get_template('dashboard_services.jinja')
201
+ for each_graphic in graphics: # add each service to api/api_discovery
202
+ cnt += 1
203
+ server = '{server}'
204
+ iframe = f'iframe_{cnt} = iframe_template.format(url=f"{server}chart_graphics/{each_graphic['name']}")\n'
205
+ iframe_templates.append(iframe)
206
+ link = "{"+ f'iframe_{cnt}' + "}"
207
+ iframe_links.append(f'{link}')
208
+ sqlalchemy_query = each_graphic['class_x_axis']
209
+
210
+ # create the dashboard service query (skip if .err file exists, create .err file if query fails)
211
+ # typical failure: xxx
212
+ db = f"""
213
+ previously_failed = Path('docs/graphics/{each_graphic['name']}.err').exists()
214
+ if previously_failed:
215
+ pass # query has previously failed, so skip it
216
+ else:
217
+ try:
218
+ results = models.{sqlalchemy_query}.{each_graphic['name']}(None)
219
+ color = 'rgba(75, 192, 192, 0.2)'
220
+ dashboard{cnt} = template.render(result=results, color=color)
221
+ dashboard_result['{each_graphic['name']}']= dashboard{cnt}
222
+ except Exception as e:
223
+ msg = f"GenAI query creation error on models.{sqlalchemy_query}.{each_graphic['name']}: " + str(e)
224
+ dashboard_result['{each_graphic['name']}'] = msg
225
+ app_logger.error(msg)
226
+ with open('docs/graphics//{each_graphic['name']}.err', 'w') as err_file:
227
+ err_file.write(msg) # this logs the error to prevent future calls
228
+
229
+ """
230
+ dashboards.append(db)
231
+
232
+ rendered_result = template.render(iframe_templates=iframe_templates, iframe_links=" ".join(iframe_links), has_iframe=cnt > 0 , dashboards= dashboards)
233
+ with open(self.project.project_directory_path.joinpath(f'api/api_discovery/dashboard_services.py'), 'w') as out_file:
234
+ out_file.write(rendered_result)
235
+ log.info(f'... create_graphics_dashboard_service - created api/api_discovery/dashboard_services.py')
236
+
237
+ for each_graphic in graphics:
238
+ self.fix_sqlalchemy_query(each_graphic)
115
239
  template = env.get_template('html_template.jinja')
116
240
  rendered_result = template.render( **each_graphic )
117
241
  with open(self.project.project_directory_path.joinpath(f'api/api_discovery/{each_graphic['name']}.html'), 'w') as out_file:
118
242
  out_file.write(rendered_result)
119
243
 
120
244
  with open(self.project.project_directory_path.joinpath(f'api/api_discovery/{each_graphic['name']}.sql'), 'w') as out_file:
121
- out_file.write(each_graphic['sql_query'])
245
+ sql_query = "System Error: missing sql_query - check WGResult format"
246
+ if 'sql_query' in each_graphic:
247
+ sql_query = each_graphic['sql_query']
248
+ out_file.write(sql_query)
122
249
 
123
- log.info(f'.. added service: {each_graphic['name']} to api_discovery')
250
+ log.info(f'..... added dashboard query: {each_graphic['name']} to api_discovery')
251
+
124
252
  pass
125
253
 
254
+ def create_genai_graphics_prompts(self, graphics_response_path: Path):
255
+ """ if genai project, create graphics prompt file: docs/graphics/wg_graphics.prompt
256
+ """
257
+ if self.using is not None:
258
+ return # it's an als request, not a genai project (todo: confirm this works on iterations)
259
+
260
+ # open and read the graphics_response_path json file
261
+ assert graphics_response_path.exists(), f'Graphics response file not found: {graphics_response_path}'
262
+ with open(graphics_response_path, 'r') as file:
263
+ graphics_response = json.load(file)
264
+ log.info(f'... create_genai_graphics_prompts - from {graphics_response_path}')
265
+ graphics = graphics_response['graphics']
266
+ graphics_prompt = ''
267
+ graphics_prompt_count = 0
268
+ for each_graphic in graphics: # add each prompt to docs/graphics
269
+ graphics_prompt_count += 1
270
+ graphics_prompt += each_graphic['prompt'] + '\n'
271
+ with open(self.project.project_directory_path.joinpath(f'docs/graphics/wg_graphics.prompt'), 'w') as out_file:
272
+ out_file.write(graphics_prompt)
273
+ log.info(f'..... added docs/graphics/wg_graphics.prompt')
274
+ pass
275
+
276
+ def graphics_replace_with_in_existing_project(self) -> str:
277
+ """
278
+ Delete graphics for wg project (als projects - just delete the docs/graphics files)
279
+ 1. Update docs/*.prompt to remove lines starting with Graphics
280
+ * Prevents reappearance on iteration
281
+ 2. Presume (!) not necessary to delete the graphics[] in docs/*.response files
282
+ 3. If delete, rename the api/api_discovery/dashboard_services.py file so it won't be called
283
+
284
+ This does not rebuild / create a new project - operates on current project.
285
+
286
+ If self.replace_with is retry or request, build docs/graphics/wg_graphics.prompt
287
+ """
288
+
289
+ # for all docs/*.prompt files, alter lines where the first characters are 'Graph' (case insensitive)
290
+ docs_dir = self.project.project_directory_path.joinpath('docs')
291
+ replaced_count = 0
292
+ replaced_prompts = ''
293
+ log.info(f"... graphics_replace_with_in_existing_project - self.replace_with: {self.replace_with}")
294
+
295
+ for prompt_file in docs_dir.glob('*.prompt'):
296
+ with open(prompt_file, 'r') as file:
297
+ lines = file.readlines()
298
+ line_number = 0
299
+ with open(prompt_file, 'w') as file:
300
+ for line in lines:
301
+ line_number += 1
302
+ if not line.strip().lower().startswith('graph '):
303
+ file.write(line)
304
+ else:
305
+ replaced_prompts += line
306
+ replaced_count += 1
307
+ if self.replace_with == '!retry': # we are just doing retry - don't replace, use existing wg_graphics
308
+ file.write(line)
309
+ continue
310
+ elif self.replace_with.startswith('!request'): # replacing - replace ==> docs and doc/graphics
311
+ if replaced_count == 1:
312
+ file.write(self.replace_with + '\n')
313
+ graphics_prompt = self.replace_with[8:]
314
+ with open(self.project.project_directory_path.joinpath(f'docs/graphics/wg_graphics.prompt'), 'w') as out_file:
315
+ out_file.write(self.replace_with)
316
+ else:
317
+ assert self.replace_with == '!delete', f"genai_graphics - expected !delete, got: {self.replace_with}"
318
+ pass # removing line
319
+
320
+ log.info(f"..... completed - processed {replaced_count} graph line(s).")
321
+
322
+ # Stop graphics: rename the old dashboard_services.py file to dashboard_services.pyZ
323
+ dashboard_service_path = self.project.project_directory_path.joinpath('api/api_discovery/dashboard_services.py')
324
+ if dashboard_service_path.exists():
325
+ renamed_path = dashboard_service_path.with_suffix('.pyZ')
326
+ dashboard_service_path.rename(renamed_path)
327
+ log.info(f"..... Renamed existing dashboard_services.py to {renamed_path}")
328
+ else:
329
+ log.info(f'.. Note: {dashboard_service_path} not found')
330
+ return replaced_prompts
331
+
126
332
  def fix_sqlalchemy_query(self, graphic: Dict):
127
333
  """ Fix the SQLAlchemy query for the graphic """
128
334
  graphic['sqlalchemy_query'] = graphic['sqlalchemy_query'].replace('\\n', '\n')
129
335
  graphic['sqlalchemy_query'] = graphic['sqlalchemy_query'].replace('\"', '"')
130
- pass
336
+ graphic['sqlalchemy_query'] = graphic['sqlalchemy_query'].replace('.isnot', '.is_not')
131
337
 
338
+ # this part is for readability, not 'fixing'
339
+ graphic['sqlalchemy_query'] = graphic['sqlalchemy_query'].replace('session.query', '(session.query')
340
+ graphic['sqlalchemy_query'] = graphic['sqlalchemy_query'] + ')'
341
+ graphic['sqlalchemy_query'] = graphic['sqlalchemy_query'].replace(').', ')\n .')
342
+ pass
132
343
 
133
344
  def append_data_model(self) -> List[str]:
134
345
  """ Get the data model
135
346
 
136
347
  Returns:
137
- list: logic_files
348
+ list[str]: the data model lines
138
349
  """
139
350
 
140
351
  data_model_lines = []
352
+ data_model_lines.extend(['Here is the data model - please use it to create the graphics:'])
141
353
  data_model_path = self.project.project_directory_path.joinpath('database/models.py')
142
354
  assert data_model_path.exists(), f"Data model file not found: {data_model_path}"
143
355
  with open(data_model_path, 'r') as file:
144
356
  prompt_lines = file.readlines()
145
357
  data_model_lines.extend(prompt_lines)
358
+ data_model_lines.extend(['End of data model'])
146
359
  return data_model_lines
147
360
 
148
361
  def append_graphics_files(self) -> List[str]:
149
- """ Get graphics files (typically from project)
362
+ """ Get graphics files (typically from project/docs/graphics)
363
+ * 1 file per graphic
150
364
 
151
365
  Returns:
152
366
  list: logic_files
153
367
  """
154
368
 
155
369
  graphics_lines = []
156
- if Path(self.project.genai_using).is_dir(): # conversation from directory
157
- for each_file in sorted(Path(self.project.genai_using).iterdir()):
370
+ if Path(self.using).is_dir(): # conversation from directory
371
+ for each_file in sorted(Path(self.using).iterdir()):
158
372
  if each_file.is_file() and each_file.suffix == '.prompt':
159
373
  # read lines from each_file, and append to prompt
160
374
  with open(each_file, 'r') as file:
@@ -42,11 +42,18 @@ class Model(BaseModel):
42
42
  description: str
43
43
  name: str
44
44
 
45
- class Graph(BaseModel):
46
- sqlalchemy_query: str # sqlalchemy group by result = { "result": [ ("name", "value) ] }
45
+ class Graphic(BaseModel):
46
+ sqlalchemy_query: str # sqlalchemy query using group by, returns result = { "result": [ ("name", "value") ] }
47
47
  sql_query: str # sql query using group by, returns result = { "result": [ ("name", "value") ] }
48
48
  classes_used: str # comma-delimited list of classes used in sqlalchemy_query
49
- name: str # suggested Python name for sqlalchemy_query
49
+ class_x_axis: str # name of class for x axis
50
+ name: str # suggested Python name for sqlalchemy_query - unique
51
+ prompt: str # prompt used to create the graphic
52
+ title: str # expanded name
53
+ xAxis: str # caption for x axis
54
+ yAxis: str # caption for y axis
55
+ dashboard: bool # whether appears on home page
56
+ graph_type: str # Bar, Line, Pie
50
57
  html_code: str # create a java script app to show a bar chart from sqlalchemy_query result
51
58
 
52
59
  class TestDataRow(BaseModel):
@@ -57,7 +64,7 @@ class WGResult(BaseModel): # must match system/genai/prompt_inserts/response_fo
57
64
  # response: str # result
58
65
  models : List[Model] # list of sqlalchemy classes in the response
59
66
  rules : List[Rule] # list rule declarations
60
- graphics: List[Graph] # list of graphs
67
+ graphics: List[Graphic] # list of graphs
61
68
  test_data: str
62
69
  test_data_rows: List[TestDataRow] # list of test data rows
63
70
  test_data_sqlite: str # test data as sqlite INSERT statements
@@ -737,10 +744,10 @@ def get_create_prompt__with_inserts(arg_prompt_inserts: str='', raw_prompt: str=
737
744
  pre_post = file.read() # eg, Use SQLAlchemy to create a sqlite database named system/genai/temp/create_db_models.sqlite, with
738
745
  prompt_result = pre_post.replace('{{prompt}}', raw_prompt)
739
746
  if for_iteration:
740
- # Update the prior response - be sure not to lose classes and test data already created.
741
- prompt_result = 'Update the prior response - be sure not to lose classes and test data already created.' \
742
- + '\n\n' + prompt_result
743
- log.debug(f'.. iteration inserted: Update the prior response')
747
+ # Update the prior response - be sure not to lose classes, attributes, rules and test data already created.
748
+ iteration_prompt = read_and_expand_prompt(get_manager_path().joinpath(f'system/genai/prompt_inserts/iteration.prompt'))
749
+ prompt_result = iteration_prompt + '\n\n' + prompt_result
750
+ log.debug(f'.. iteration inserted: Update the prior response, using prompt_inserts/iteration.prompt')
744
751
  #log.debug(f'.... iteration prompt result: {prompt_result}')
745
752
 
746
753
  prompt_lines = prompt_result.split('\n')
@@ -766,10 +773,11 @@ def get_create_prompt__with_inserts(arg_prompt_inserts: str='', raw_prompt: str=
766
773
  do_logic = False
767
774
  prompt_line_number += 1
768
775
 
769
- response_format_file_name = get_manager_path().joinpath(f'system/genai/prompt_inserts/response_format.prompt')
770
- with open(response_format_file_name, 'r') as file:
771
- response_format = file.readlines()
772
- prompt_lines.extend(response_format)
776
+ if format_not_requested := False: # FIXME - double format definition on create; others??
777
+ response_format_file_name = get_manager_path().joinpath(f'system/genai/prompt_inserts/response_format.prompt')
778
+ with open(response_format_file_name, 'r') as file:
779
+ response_format = file.readlines()
780
+ prompt_lines.extend(response_format)
773
781
 
774
782
  prompt_result = "\n".join(prompt_lines) # back to a string
775
783
  pass
@@ -12,7 +12,7 @@ import api_logic_server_cli.api_logic_server as PR
12
12
 
13
13
  def create_manager(clean: bool, open_with: str, api_logic_server_path: Path,
14
14
  volume: str = "", open_manager: bool = True, samples: bool = True):
15
- """Implements als start to create manager - called from api_logic_server_cli/cli.py
15
+ """Implements `als start` to create manager - called from api_logic_server_cli/cli.py
16
16
 
17
17
  create Manager at os.getcwd(), including:
18
18
 
@@ -27,11 +27,11 @@ def create_manager(clean: bool, open_with: str, api_logic_server_path: Path,
27
27
  Bit tricky to find find cli in subdirectories of the lib path for manager run launches
28
28
 
29
29
  Args:
30
- clean (bool): _description_
31
- open_with (str): _description_
30
+ clean (bool): Overlay existing manager (projects and web_genai retained)
31
+ open_with (str): IDE to use
32
32
  api_logic_server_path (Path): _description_
33
33
  volume (str, optional): _description_. Defaults to "".
34
- open_manager (bool, optional): _description_. Defaults to True.
34
+ open_manager (bool, optional): Whether to open IDE at Manager. Defaults to True.
35
35
  """
36
36
 
37
37
  log = logging.getLogger(__name__)
@@ -68,8 +68,9 @@ def create_manager(clean: bool, open_with: str, api_logic_server_path: Path,
68
68
  docker_volume = volume + '/'
69
69
 
70
70
  to_dir = Path(os.getcwd())
71
+ """ location for creating (cleaning) the Manager """
71
72
  path = Path(__file__)
72
- from_dir = api_logic_server_path.joinpath('prototypes/manager')
73
+ from_dir_proto_mgr = api_logic_server_path.joinpath('prototypes/manager')
73
74
  to_dir_str = str(to_dir)
74
75
  to_dir_check = Path(to_dir).joinpath('venv')
75
76
  if not Path(to_dir).joinpath('venv').exists():
@@ -85,12 +86,12 @@ def create_manager(clean: bool, open_with: str, api_logic_server_path: Path,
85
86
  if to_dir_check.exists() and not clean:
86
87
  manager_exists = True
87
88
  if env_path.exists():
88
- log.info(f" Using manager at: {to_dir}\n\n")
89
+ log.info(f" Using existing manager (update .env APILOGICSERVER_AUTO_OPEN, only) at: {to_dir}\n\n")
89
90
  else:
90
91
  log.info(f" Refreshing .env in manager at: {to_dir}\n\n")
91
- copied_env = shutil.copy(src=from_dir.joinpath('settings.txt'), dst=to_dir.joinpath('.env'))
92
+ copied_env = shutil.copy(src=from_dir_proto_mgr.joinpath('settings.txt'), dst=to_dir.joinpath('.env'))
92
93
  os.remove(to_dir.joinpath('settings.txt'))
93
- else:
94
+ else: # new Manager, or clean existing manager
94
95
  mgr_save_level = log.level
95
96
  codegen_logger = logging.getLogger('sqlacodegen_wrapper.sqlacodegen.sqlacodegen.codegen')
96
97
  codegen_logger_save_level= codegen_logger.level
@@ -99,8 +100,16 @@ def create_manager(clean: bool, open_with: str, api_logic_server_path: Path,
99
100
  if to_dir_check.exists():
100
101
  log.info(f" Cleaning manager at: {to_dir}\n\n")
101
102
 
102
- copied_path = shutil.copytree(src=from_dir, dst=to_dir, dirs_exist_ok=True) # issue: not permitted
103
- copied_env = shutil.copy(src=from_dir.joinpath('settings.txt'), dst=to_dir.joinpath('.env'))
103
+ # create the manager system files and shell scripts (samples created below).
104
+ copied_path = shutil.copytree(src=from_dir_proto_mgr, dst=to_dir, dirs_exist_ok=True, ) # issue: not permitted
105
+ copied_env = shutil.copy(src=from_dir_proto_mgr.joinpath('settings.txt'), dst=to_dir.joinpath('.env'))
106
+ web_genai_docker = to_dir.joinpath('webgenai/docker-compose.yml')
107
+ if web_genai_docker.exists():
108
+ log.debug(' .. WebGenAI docker_compose unaltered (license preserved)')
109
+ else:
110
+ log.debug(' .. WebGenAI docker_compose created')
111
+ copied_env = shutil.copy(src=api_logic_server_path.joinpath('fragments/docker-compose.yml'),
112
+ dst=web_genai_docker)
104
113
  os.remove(to_dir.joinpath('settings.txt'))
105
114
  log.debug(f" .. created manager\n")
106
115
 
@@ -22,6 +22,25 @@
22
22
  "console": "internalConsole",
23
23
  "internalConsoleOptions": "openOnSessionStart"
24
24
  },
25
+ {
26
+ "name": "ApiLogicServer DEBUG",
27
+ "type": "debugpy",
28
+ "cwd": "${workspaceFolder}",
29
+ "env": {
30
+ "PYTHONPATH": "",
31
+ "EXPERIMENT": "",
32
+ "PYTHONHASHSEED": "0",
33
+ "APILOGICPROJECT_LOGGING_CONFIG": "config/logging.yml",
34
+ "APILOGICPROJECT_STOP_OK": "True",
35
+ "APILOGICPROJECT_DEBUG": "False"},
36
+ "request": "launch",
37
+ "program": "api_logic_server_run.py",
38
+ "redirectOutput": true,
39
+ "justMyCode": false,
40
+ "args": ["--flask_host=localhost", "--port=5656", "--swagger_port=5656", "--swagger_host=localhost", "--verbose=False"],
41
+ "console": "internalConsole",
42
+ "internalConsoleOptions": "openOnSessionStart"
43
+ },
25
44
  {
26
45
  "name": " - API Logic Server - VERBOSE",
27
46
  "type": "debugpy",
@@ -4,6 +4,8 @@ import importlib
4
4
  import pathlib
5
5
  import logging as logging
6
6
  import flask_sqlalchemy
7
+ from config.config import Args
8
+ from config import config
7
9
 
8
10
  # use absolute path import for easier multi-{app,model,db} support
9
11
  database = __import__('database')
@@ -40,7 +42,7 @@ def expose_models(api, method_decorators = []):
40
42
  """
41
43
 
42
44
  debug_inspect_list = inspect.getmembers(database.models)
43
- pass
45
+
44
46
  # Get all the subclasses of the Base class and expose them in the api
45
47
  for name, obj in inspect.getmembers(database.models):
46
48
  if inspect.isclass(obj) and issubclass(obj, database.models.SAFRSBaseX) and obj is not database.models.SAFRSBaseX:
@@ -31,7 +31,7 @@ import os, logging, logging.config, sys, yaml # failure here means venv probabl
31
31
  from flask_sqlalchemy import SQLAlchemy
32
32
  import json
33
33
  from pathlib import Path
34
- from config.config import Args
34
+ from config.config import Args # sets up logging
35
35
  from config import server_setup
36
36
 
37
37
  current_path = os.path.abspath(os.path.dirname(__file__))
@@ -68,8 +68,11 @@ import ui.admin.admin_loader as AdminLoader
68
68
  from security.system.authentication import configure_auth
69
69
  import oracledb
70
70
 
71
+ if os.getenv("EXPERIMENT") == '+':
72
+ app_logger = logging.getLogger("api_logic_server_app")
73
+ else:
74
+ app_logger = server_setup.logging_setup()
71
75
 
72
- app_logger = server_setup.logging_setup()
73
76
 
74
77
 
75
78
  # ==================================
@@ -40,6 +40,7 @@ def activate_logicbank(session, constraint_handler):
40
40
  app_logger.exception(e)
41
41
  if not os.environ.get("WG_PROJECT") == "True":
42
42
  # Continue if inside WebGenAI
43
+ # see: https://apilogicserver.github.io/Docs/WebGenAI-CLI/#wg_rules-and-ide-rules
43
44
  raise e
44
45
  logic_logger.setLevel(logic_logger_level)
45
46