ApiLogicServer 14.3.20__py3-none-any.whl → 14.3.25__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 -3
- api_logic_server_cli/api_logic_server_info.yaml +3 -3
- api_logic_server_cli/cli.py +38 -0
- api_logic_server_cli/database/nw-gold.sqlite +0 -0
- api_logic_server_cli/genai/genai.py +2 -1
- api_logic_server_cli/genai/genai_graphics.py +165 -0
- api_logic_server_cli/genai/genai_logic_builder.py +2 -2
- api_logic_server_cli/genai/genai_svcs.py +8 -0
- api_logic_server_cli/prototypes/base/config/config.py +58 -30
- api_logic_server_cli/prototypes/base/docs/graphics/readme.md +12 -0
- api_logic_server_cli/prototypes/base/security/authentication_provider/keycloak/auth_provider.py +1 -1
- api_logic_server_cli/prototypes/base/security/declare_security.py +4 -0
- api_logic_server_cli/prototypes/base/ui/admin/admin_loader.py +3 -1
- api_logic_server_cli/prototypes/manager/README.md +30 -1
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo.prompt +2 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo.response_example +68 -60
- api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/graphics_services.py +41 -0
- api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/html_template.jinja +76 -0
- api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/index.html +19 -0
- api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/sales_by_region.jinja +63 -0
- api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/service_template_jsonapi_rpc.jinja +37 -0
- api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/service_template_unused.jinja +38 -0
- api_logic_server_cli/prototypes/manager/system/genai/prompt_inserts/graphics.prompt +18 -0
- api_logic_server_cli/prototypes/manager/system/genai/prompt_inserts/graphics_request.prompt +5 -0
- api_logic_server_cli/prototypes/manager/system/genai/prompt_inserts/response_format.prompt +8 -0
- api_logic_server_cli/prototypes/manager/system/genai/prompt_inserts/sqlite_inserts.prompt +2 -0
- api_logic_server_cli/prototypes/manager/webgenai/docker-compose.yml +27 -0
- api_logic_server_cli/prototypes/nw_no_cust/docs/graphics/request copy.json +892 -0
- api_logic_server_cli/prototypes/nw_no_cust/docs/graphics/request.json +6 -0
- api_logic_server_cli/prototypes/nw_no_cust/docs/graphics/response.json +17 -0
- api_logic_server_cli/prototypes/nw_no_cust/docs/graphics/response.yaml +59 -0
- api_logic_server_cli/prototypes/nw_no_cust/docs/graphics/sales_by_category.prompt +1 -0
- {apilogicserver-14.3.20.dist-info → apilogicserver-14.3.25.dist-info}/METADATA +1 -1
- {apilogicserver-14.3.20.dist-info → apilogicserver-14.3.25.dist-info}/RECORD +38 -26
- {apilogicserver-14.3.20.dist-info → apilogicserver-14.3.25.dist-info}/WHEEL +1 -1
- api_logic_server_cli/prototypes/manager/system/genai/prompt_inserts/zsqlite_inserts_iterations.prompt +0 -29
- api_logic_server_cli/prototypes/manager/webgenai/docker-compose-webg.yml +0 -33
- api_logic_server_cli/prototypes/manager/webgenai/webg_config/license.json +0 -6
- api_logic_server_cli/prototypes/manager/webgenai/webg_config/web_genai.txt +0 -13
- {apilogicserver-14.3.20.dist-info → apilogicserver-14.3.25.dist-info}/entry_points.txt +0 -0
- {apilogicserver-14.3.20.dist-info → apilogicserver-14.3.25.dist-info}/licenses/LICENSE +0 -0
- {apilogicserver-14.3.20.dist-info → apilogicserver-14.3.25.dist-info}/top_level.txt +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
'''
|
|
4
4
|
ApiLogicServer CLI: given a database url, create [and run] customizable ApiLogicProject.
|
|
5
5
|
* Basically clones prototype project (api_logic_server_cli/prototypes/base), and creates:
|
|
6
|
-
* database/models.py for SQLAlchemy
|
|
6
|
+
* database/models.py for SQLAlchemy,` using modified sqlacodegen & safrs metadata
|
|
7
7
|
* ui/admin/admin.yaml for the Admin App - using introspected models.py
|
|
8
8
|
* api/expose_api_models.py for a safrs api - using introspected models.py
|
|
9
9
|
* Special provisions for NW Sample, to show customizations.
|
|
@@ -12,10 +12,11 @@ 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.03.
|
|
15
|
+
__version__ = "14.03.25"
|
|
16
16
|
recent_changes = \
|
|
17
17
|
f'\n\nRecent Changes:\n' +\
|
|
18
|
-
"\t03/
|
|
18
|
+
"\t03/30/2024 - 14.03.25: WebGenAI fixes for Kafka and Keycloak \n"\
|
|
19
|
+
"\t03/19/2024 - 14.03.20: licensed webgenai docker, [87] sra fix for home.js \n"\
|
|
19
20
|
"\t02/26/2024 - 14.03.14: [85: reserved words], genai_demo fixes \n"\
|
|
20
21
|
"\t02/16/2024 - 14.03.12: Docker w/ std container, mgr assistant for local WebG \n"\
|
|
21
22
|
"\t02/13/2024 - 14.03.11: fixes [78: Keycloak, 79: boolean defaulting, 80: Send If missing attribute] \n"\
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
last_created_date: March
|
|
2
|
-
last_created_project_name:
|
|
3
|
-
last_created_version: 14.03.
|
|
1
|
+
last_created_date: March 26, 2025 13:40:07
|
|
2
|
+
last_created_project_name: genai_demo
|
|
3
|
+
last_created_version: 14.03.22
|
api_logic_server_cli/cli.py
CHANGED
|
@@ -738,6 +738,44 @@ def genai_logic(ctx, using, genai_version: str, retries: int, suggest: click.BOO
|
|
|
738
738
|
log.info("")
|
|
739
739
|
|
|
740
740
|
|
|
741
|
+
@main.command("genai-graphics", cls=HideDunderCommand)
|
|
742
|
+
@click.option('--using',
|
|
743
|
+
default=f'docs/graphics',
|
|
744
|
+
help="File or dir")
|
|
745
|
+
@click.option('--genai-version', 'genai_version',
|
|
746
|
+
default='gpt-4o',
|
|
747
|
+
help="Eg, gpt-3.5-turbo, gpt-4o")
|
|
748
|
+
@click.pass_context
|
|
749
|
+
def genai_graphics(ctx, using, genai_version: str):
|
|
750
|
+
"""
|
|
751
|
+
Adds (or suggests) logic to current project.
|
|
752
|
+
"""
|
|
753
|
+
global command
|
|
754
|
+
project_dir = resolve_blank_project_name('')
|
|
755
|
+
project_name = Path(project_dir).name
|
|
756
|
+
project = PR.ProjectRun(command="add_security",
|
|
757
|
+
project_name=project_name,
|
|
758
|
+
db_url="",
|
|
759
|
+
execute=False
|
|
760
|
+
)
|
|
761
|
+
project.project_directory, project.api_name, project.merge_into_prototype = \
|
|
762
|
+
create_utils.get_project_directory_and_api_name(project)
|
|
763
|
+
project.project_directory_actual = os.path.abspath(os.getcwd()) # make path absolute, not relative (no /../)
|
|
764
|
+
project.project_directory_path = Path(project.project_directory_actual)
|
|
765
|
+
models_py_path = project.project_directory_path.joinpath('database/models.py')
|
|
766
|
+
project.abs_db_url, project.nw_db_status, project.model_file_name = \
|
|
767
|
+
create_utils.get_abs_db_url("0. Using Sample DB", project, is_auth=True)
|
|
768
|
+
|
|
769
|
+
if not models_py_path.exists():
|
|
770
|
+
log.info(f'... Error - does not appear to be a project: {str(project.project_directory_path)}')
|
|
771
|
+
log.info(f'... Typical usage - cd into project, use --project_name=. \n')
|
|
772
|
+
exit (1)
|
|
773
|
+
from api_logic_server_cli.genai.genai_graphics import GenAIGraphics
|
|
774
|
+
genai_graphics = GenAIGraphics(using=using, project=project, genai_version=genai_version)
|
|
775
|
+
pass
|
|
776
|
+
log.info("")
|
|
777
|
+
|
|
778
|
+
|
|
741
779
|
@main.command("genai-create", cls=HideDunderCommand)
|
|
742
780
|
@click.option('--project-name', 'project_name',
|
|
743
781
|
default=f'{last_created_project_name}',
|
|
Binary file
|
|
@@ -27,6 +27,7 @@ from api_logic_server_cli.genai.genai_svcs import K_LogicBankOff
|
|
|
27
27
|
from api_logic_server_cli.genai.genai_svcs import K_LogicBankTraining
|
|
28
28
|
from api_logic_server_cli.genai.genai_svcs import fix_and_write_model_file as fix_and_write_model_file_svcs
|
|
29
29
|
import api_logic_server_cli.genai.genai_svcs as genai_svcs
|
|
30
|
+
from api_logic_server_cli.genai.genai_graphics import GenAIGraphics
|
|
30
31
|
|
|
31
32
|
log = logging.getLogger(__name__)
|
|
32
33
|
|
|
@@ -575,7 +576,7 @@ class GenAI(object):
|
|
|
575
576
|
except: # intentional try/catch/bury - it's just docs, so don't fail
|
|
576
577
|
import traceback
|
|
577
578
|
log.error(f"\n\nERROR creating genai project docs: {docs_dir}\n\n{traceback.format_exc()}")
|
|
578
|
-
|
|
579
|
+
genai_graphics = GenAIGraphics(project=self.project, using=None, genai_version=self.project.genai_version)
|
|
579
580
|
|
|
580
581
|
def save_prompt_messages_to_system_genai_temp_project(self):
|
|
581
582
|
"""
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
from typing import Dict, List
|
|
3
|
+
from api_logic_server_cli.cli_args_project import Project
|
|
4
|
+
import logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import importlib
|
|
7
|
+
from api_logic_server_cli.genai.genai_utils import call_chatgpt
|
|
8
|
+
import requests
|
|
9
|
+
import os
|
|
10
|
+
import datetime
|
|
11
|
+
import create_from_model.api_logic_server_utils as utils
|
|
12
|
+
import time
|
|
13
|
+
from openai import OpenAI
|
|
14
|
+
from api_logic_server_cli.genai.genai_svcs import WGResult
|
|
15
|
+
from api_logic_server_cli.genai.genai_svcs import Rule
|
|
16
|
+
import api_logic_server_cli.genai.genai_svcs as genai_svcs
|
|
17
|
+
import json
|
|
18
|
+
from typing import List, Dict
|
|
19
|
+
from pydantic import BaseModel
|
|
20
|
+
from dotmap import DotMap
|
|
21
|
+
from natsort import natsorted
|
|
22
|
+
import glob
|
|
23
|
+
import create_from_model.api_logic_server_utils as create_utils
|
|
24
|
+
from jinja2 import Environment, FileSystemLoader
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
K_data_model_prompt = "Use SQLAlchemy to create"
|
|
28
|
+
|
|
29
|
+
log = logging.getLogger(__name__)
|
|
30
|
+
|
|
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)
|
|
36
|
+
|
|
37
|
+
Invoked from:
|
|
38
|
+
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`
|
|
42
|
+
* note: dbml not rebuilt after rebuild-from-db
|
|
43
|
+
3. **Existing WG Project:** in-place (do not create new project with new test data)
|
|
44
|
+
* Same as #1, but requires WG UI change ('in place', 'graphics' button, ...) to use genai_graphics cmd
|
|
45
|
+
|
|
46
|
+
|
|
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
|
+
|
|
50
|
+
Open Issues
|
|
51
|
+
* How to integrate with als/wg home.js?
|
|
52
|
+
* How to enforce licensing?
|
|
53
|
+
* How to choose graph vs chart?
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, project: Project, using: str, genai_version: str):
|
|
58
|
+
"""
|
|
59
|
+
Add graphics to existing projects - [see docs](https://apilogicserver.github.io/Docs/WebGenAI-CLI/#add-graphics-to-existing-projects)
|
|
60
|
+
|
|
61
|
+
see key_module_map() for key methods
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
self.project = project
|
|
66
|
+
self.project.genai_using = using
|
|
67
|
+
self.manager_path = genai_svcs.get_manager_path()
|
|
68
|
+
|
|
69
|
+
if using is None: # New GenAI Project: use docs/response.json
|
|
70
|
+
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')
|
|
73
|
+
if bypass_for_debug := False:
|
|
74
|
+
pass
|
|
75
|
+
else:
|
|
76
|
+
prompt = genai_svcs.read_and_expand_prompt(self.manager_path.joinpath('system/genai/prompt_inserts/graphics_request.prompt'))
|
|
77
|
+
prompt_lines = prompt.split('\n') # ChatGPT instructions
|
|
78
|
+
prompt_lines.extend(self.append_data_model()) # add data model
|
|
79
|
+
prompt_lines.extend(self.append_graphics_files()) # and the users's requests from graphics files
|
|
80
|
+
prompt_str = "\n".join(prompt_lines)
|
|
81
|
+
|
|
82
|
+
prompt_messages : List[ Dict[str, str] ] = [] # prompt/response conversation to be sent to ChatGPT
|
|
83
|
+
prompt = genai_svcs.get_prompt_you_are()
|
|
84
|
+
prompt["content"] = prompt_str
|
|
85
|
+
prompt_messages.append( prompt )
|
|
86
|
+
genai_svcs.call_chatgpt(messages = prompt_messages,
|
|
87
|
+
using = self.project.project_directory_path.joinpath('docs/graphics'),
|
|
88
|
+
api_version=genai_version)
|
|
89
|
+
|
|
90
|
+
self.process_graphics_response(graphics_response_path)
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
def process_graphics_response(self, graphics_response_path: Path):
|
|
94
|
+
""" Process graphics response from ChatGPT graphics_response_path """
|
|
95
|
+
|
|
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)
|
|
99
|
+
|
|
100
|
+
# open and read the graphics_response_path json file
|
|
101
|
+
assert graphics_response_path.exists(), f'Graphics response file not found: {graphics_response_path}'
|
|
102
|
+
with open(graphics_response_path, 'r') as file:
|
|
103
|
+
graphics_response = json.load(file)
|
|
104
|
+
log.info(f'Graphics response loaded from {graphics_response_path}')
|
|
105
|
+
graphics = graphics_response['graphics']
|
|
106
|
+
for each_graphic in graphics: # add each service to api/api_discovery
|
|
107
|
+
self.fix_sqlalchemy_query(each_graphic)
|
|
108
|
+
env = Environment(loader=FileSystemLoader(self.manager_path.joinpath('system/genai/graphics_templates')))
|
|
109
|
+
|
|
110
|
+
template = env.get_template('service_template_jsonapi_rpc.jinja')
|
|
111
|
+
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:
|
|
113
|
+
out_file.write(rendered_result)
|
|
114
|
+
|
|
115
|
+
template = env.get_template('html_template.jinja')
|
|
116
|
+
rendered_result = template.render( **each_graphic )
|
|
117
|
+
with open(self.project.project_directory_path.joinpath(f'api/api_discovery/{each_graphic['name']}.html'), 'w') as out_file:
|
|
118
|
+
out_file.write(rendered_result)
|
|
119
|
+
|
|
120
|
+
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'])
|
|
122
|
+
|
|
123
|
+
log.info(f'.. added service: {each_graphic['name']} to api_discovery')
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
def fix_sqlalchemy_query(self, graphic: Dict):
|
|
127
|
+
""" Fix the SQLAlchemy query for the graphic """
|
|
128
|
+
graphic['sqlalchemy_query'] = graphic['sqlalchemy_query'].replace('\\n', '\n')
|
|
129
|
+
graphic['sqlalchemy_query'] = graphic['sqlalchemy_query'].replace('\"', '"')
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def append_data_model(self) -> List[str]:
|
|
134
|
+
""" Get the data model
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
list: logic_files
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
data_model_lines = []
|
|
141
|
+
data_model_path = self.project.project_directory_path.joinpath('database/models.py')
|
|
142
|
+
assert data_model_path.exists(), f"Data model file not found: {data_model_path}"
|
|
143
|
+
with open(data_model_path, 'r') as file:
|
|
144
|
+
prompt_lines = file.readlines()
|
|
145
|
+
data_model_lines.extend(prompt_lines)
|
|
146
|
+
return data_model_lines
|
|
147
|
+
|
|
148
|
+
def append_graphics_files(self) -> List[str]:
|
|
149
|
+
""" Get graphics files (typically from project)
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
list: logic_files
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
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()):
|
|
158
|
+
if each_file.is_file() and each_file.suffix == '.prompt':
|
|
159
|
+
# read lines from each_file, and append to prompt
|
|
160
|
+
with open(each_file, 'r') as file:
|
|
161
|
+
prompt_lines = file.readlines()
|
|
162
|
+
graphics_lines.extend(prompt_lines)
|
|
163
|
+
return graphics_lines
|
|
164
|
+
|
|
165
|
+
|
|
@@ -27,7 +27,7 @@ log = logging.getLogger(__name__)
|
|
|
27
27
|
|
|
28
28
|
class GenAILogic(object):
|
|
29
29
|
"""
|
|
30
|
-
Called by cli for *existing* project
|
|
30
|
+
Called by cli for *existing* project (must be wg project with docs models)
|
|
31
31
|
|
|
32
32
|
* **Create logic** from *logic files* eg `<project>/docs/logic/check_credit.prompt`
|
|
33
33
|
|
|
@@ -113,7 +113,7 @@ class GenAILogic(object):
|
|
|
113
113
|
return learning_requests # TODO - what if no learning requests?
|
|
114
114
|
|
|
115
115
|
def get_learnings_and_data_model(self) -> List[Dict[str, str]]:
|
|
116
|
-
""" Get prompts from the docs dir (so GPT knows model, learnings)
|
|
116
|
+
""" Get prompts from the docs dir (so GPT knows model, learnings) -- **not** from database/models.py
|
|
117
117
|
|
|
118
118
|
Most often, adding logic to new project, which looks like:
|
|
119
119
|
|
|
@@ -42,6 +42,13 @@ 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) ] }
|
|
47
|
+
sql_query: str # sql query using group by, returns result = { "result": [ ("name", "value") ] }
|
|
48
|
+
classes_used: str # comma-delimited list of classes used in sqlalchemy_query
|
|
49
|
+
name: str # suggested Python name for sqlalchemy_query
|
|
50
|
+
html_code: str # create a java script app to show a bar chart from sqlalchemy_query result
|
|
51
|
+
|
|
45
52
|
class TestDataRow(BaseModel):
|
|
46
53
|
test_data_row_variable: str # the Python test data row variable
|
|
47
54
|
code: str # Python code to create a test data row instance
|
|
@@ -50,6 +57,7 @@ class WGResult(BaseModel): # must match system/genai/prompt_inserts/response_fo
|
|
|
50
57
|
# response: str # result
|
|
51
58
|
models : List[Model] # list of sqlalchemy classes in the response
|
|
52
59
|
rules : List[Rule] # list rule declarations
|
|
60
|
+
graphics: List[Graph] # list of graphs
|
|
53
61
|
test_data: str
|
|
54
62
|
test_data_rows: List[TestDataRow] # list of test data rows
|
|
55
63
|
test_data_sqlite: str # test data as sqlite INSERT statements
|
|
@@ -116,15 +116,16 @@ class Config:
|
|
|
116
116
|
# als add-auth --provider-type=sql --db-url=
|
|
117
117
|
# als add-auth --provider-type=keycloak --db-url=localhost
|
|
118
118
|
# als add-auth --provider-type=keycloak --db-url=http://10.0.0.77:8080
|
|
119
|
-
kc_base = '
|
|
119
|
+
kc_base = os.getenv('KEYCLOAK_BASE','https://localhost:8080')
|
|
120
|
+
#kc_base = 'http://localhost:8080'
|
|
120
121
|
''' keycloak location '''
|
|
121
|
-
KEYCLOAK_REALM = 'kcals'
|
|
122
|
-
KEYCLOAK_BASE = f'{kc_base}
|
|
123
|
-
KEYCLOAK_BASE_URL = f'{kc_base}'
|
|
124
|
-
KEYCLOAK_CLIENT_ID = 'alsclient'
|
|
122
|
+
KEYCLOAK_REALM = os.getenv('KEYCLOAK_REALM','kcals')
|
|
123
|
+
KEYCLOAK_BASE = os.getenv('KEYCLOAK_BASE',f'{kc_base}')
|
|
124
|
+
KEYCLOAK_BASE_URL = os.getenv('KEYCLOAK_BASE_URL',f'{kc_base}/realms/{KEYCLOAK_REALM}')
|
|
125
|
+
KEYCLOAK_CLIENT_ID = os.getenv('KEYCLOAK_CLIENT_ID','alsclient')
|
|
125
126
|
''' keycloak client id '''
|
|
126
127
|
|
|
127
|
-
SECURITY_ENABLED = False
|
|
128
|
+
SECURITY_ENABLED = os.getenv("SECURITY_ENABLED",False)
|
|
128
129
|
SECURITY_PROVIDER = None
|
|
129
130
|
if os.getenv('SECURITY_ENABLED'): # e.g. export SECURITY_ENABLED=true
|
|
130
131
|
security_export = os.getenv('SECURITY_ENABLED') # type: ignore # type: str
|
|
@@ -141,6 +142,8 @@ class Config:
|
|
|
141
142
|
app_logger.debug(f'config.py - security enabled')
|
|
142
143
|
else:
|
|
143
144
|
app_logger.info(f'config.py - security disabled')
|
|
145
|
+
|
|
146
|
+
app_logger.info(f'SECURITY_PROVIDER={SECURITY_PROVIDER}')
|
|
144
147
|
|
|
145
148
|
# Begin Multi-Database URLs (from ApiLogicServer add-db...)
|
|
146
149
|
auth_db_path = str(project_path.joinpath('database/authentication_db.sqlite'))
|
|
@@ -164,11 +167,22 @@ class Config:
|
|
|
164
167
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
|
165
168
|
PROPAGATE_EXCEPTIONS = False
|
|
166
169
|
|
|
167
|
-
KAFKA_PRODUCER =
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
KAFKA_PRODUCER = None
|
|
171
|
+
KAFKA_CONSUMER = None
|
|
172
|
+
KAFKA_CONSUMER_GROUP = None
|
|
173
|
+
KAFKA_SERVER = None
|
|
174
|
+
#KAFKA_SERVER = os.getenv('KAFKA_SERVER','localhost:9092') # if running locally default
|
|
175
|
+
if KAFKA_SERVER:
|
|
176
|
+
app_logger.info(f'config.py - KAFKA_SERVER: {KAFKA_SERVER}')
|
|
177
|
+
KAFKA_PRODUCER = os.getenv('KAFKA_PRODUCER',{"bootstrap.servers": f"{KAFKA_SERVER}"}) # , "client.id": "aaa.b.c.d"}'
|
|
178
|
+
KAFKA_CONSUMER_GROUP = os.getenv('KAFKA_CONSUMER_GROUP','als-default-group1')
|
|
179
|
+
KAFKA_CONSUMER = os.getenv('KAFKA_CONSUMER', {"bootstrap.servers": f"{KAFKA_SERVER}", "group.id": f"{KAFKA_CONSUMER_GROUP}", "enable.auto.commit": "false", "auto.offset.reset": "earliest"})
|
|
180
|
+
else:
|
|
181
|
+
app_logger.info(f'config.py - KAFKA_SERVER: {KAFKA_SERVER} - not set, no kafka producer/consumer')
|
|
182
|
+
print(f'config.py - KAFKA_PRODUCER: {KAFKA_PRODUCER}')
|
|
183
|
+
print(f'config.py - KAFKA_CONSUMER: {KAFKA_CONSUMER}')
|
|
184
|
+
print(f'config.py - KAFKA_CONSUMER_GROUP: {KAFKA_CONSUMER_GROUP}')
|
|
185
|
+
print(f'config.py - KAFKA_SERVER: {KAFKA_SERVER}')
|
|
172
186
|
# N8N Webhook Args (for testing)
|
|
173
187
|
# see https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.webhook#path
|
|
174
188
|
wh_scheme = "http"
|
|
@@ -245,6 +259,7 @@ class Args():
|
|
|
245
259
|
self.http_scheme = Config.CREATED_HTTP_SCHEME
|
|
246
260
|
self.kafka_producer = Config.KAFKA_PRODUCER
|
|
247
261
|
self.kafka_consumer = Config.KAFKA_CONSUMER
|
|
262
|
+
self.kafka_consumer_group = Config.KAFKA_CONSUMER_GROUP
|
|
248
263
|
self.n8n_producer = Config.N8N_PRODUCER
|
|
249
264
|
self.keycloak_base = Config.KEYCLOAK_BASE
|
|
250
265
|
self.keycloak_realm = Config.KEYCLOAK_REALM
|
|
@@ -463,14 +478,11 @@ class Args():
|
|
|
463
478
|
@property
|
|
464
479
|
def kafka_producer(self) -> dict:
|
|
465
480
|
""" kafka connect string """
|
|
466
|
-
if "KAFKA_PRODUCER" in self.flask_app.config:
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
else:
|
|
472
|
-
value = json.loads(self.flask_app.config["KAFKA_PRODUCER"])
|
|
473
|
-
return value
|
|
481
|
+
if "KAFKA_PRODUCER" in self.flask_app.config and self.flask_app.config["KAFKA_PRODUCER"] is not None:
|
|
482
|
+
value = self.flask_app.config["KAFKA_PRODUCER"]
|
|
483
|
+
if not isinstance(value, dict):
|
|
484
|
+
value = json.loads(self.flask_app.config["KAFKA_PRODUCER"])
|
|
485
|
+
return value
|
|
474
486
|
return None
|
|
475
487
|
|
|
476
488
|
@kafka_producer.setter
|
|
@@ -480,23 +492,30 @@ class Args():
|
|
|
480
492
|
@property
|
|
481
493
|
def kafka_consumer(self) -> dict:
|
|
482
494
|
""" kafka enable consumer """
|
|
483
|
-
if "KAFKA_CONSUMER" in self.flask_app.config:
|
|
484
|
-
|
|
485
|
-
|
|
495
|
+
if "KAFKA_CONSUMER" in self.flask_app.config and self.flask_app.config["KAFKA_CONSUMER"] is not None:
|
|
496
|
+
value = self.flask_app.config["KAFKA_CONSUMER"]
|
|
497
|
+
if not isinstance(value, dict):
|
|
498
|
+
value = json.loads(self.flask_app.config["KAFKA_CONSUMER"])
|
|
499
|
+
return value
|
|
486
500
|
return None
|
|
487
501
|
|
|
488
502
|
@kafka_consumer.setter
|
|
489
503
|
def kafka_consumer(self, a: str):
|
|
490
504
|
self.flask_app.config["KAFKA_CONSUMER"] = a
|
|
491
505
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
506
|
+
|
|
507
|
+
@property
|
|
508
|
+
def kafka_consumer_group(self) -> dict:
|
|
509
|
+
""" kafka enable consumer group """
|
|
510
|
+
if "KAFKA_CONSUMER_GROUP" in self.flask_app.config:
|
|
511
|
+
if self.flask_app.config["KAFKA_CONSUMER_GROUP"] is not None:
|
|
512
|
+
return self.flask_app.config["KAFKA_CONSUMER_GROUP"]
|
|
513
|
+
return None
|
|
514
|
+
|
|
515
|
+
@kafka_consumer_group.setter
|
|
516
|
+
def kafka_consumer_group(self, a: str):
|
|
517
|
+
self.flask_app.config["KAFKA_CONSUMER_GROUP"] = a
|
|
518
|
+
|
|
500
519
|
@property
|
|
501
520
|
def n8n_producer(self) -> dict:
|
|
502
521
|
""" n8n connect string """
|
|
@@ -515,6 +534,15 @@ class Args():
|
|
|
515
534
|
self.flask_app.config["N8N_PRODUCER"] = a
|
|
516
535
|
|
|
517
536
|
|
|
537
|
+
def __str__(self) -> str:
|
|
538
|
+
rtn = f'.. flask_host: {self.flask_host}, port: {self.port}, \n'\
|
|
539
|
+
f'.. swagger_host: {self.swagger_host}, swagger_port: {self.swagger_port}, \n'\
|
|
540
|
+
f'.. client_uri: {self.client_uri}, \n'\
|
|
541
|
+
f'.. http_scheme: {self.http_scheme}, api_prefix: {self.api_prefix}, \n'\
|
|
542
|
+
f'.. | verbose: {self.verbose}, create_and_run: {self.create_and_run}'
|
|
543
|
+
return rtn
|
|
544
|
+
|
|
545
|
+
|
|
518
546
|
def get_cli_args(self, args: 'Args', dunder_name: str):
|
|
519
547
|
"""
|
|
520
548
|
returns tuple of start args:
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
### Add Natural Language Logic to Your Project
|
|
2
|
+
|
|
3
|
+
You can add Natural Language logic files to this directory, e.g.:
|
|
4
|
+
|
|
5
|
+
* `.sales_by_category.prompt`: Graph Sales by Category
|
|
6
|
+
|
|
7
|
+
Then, use GenAI to create executable logic in your `api/api_discovery` directory, e.g.,
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
als genai-graphics
|
|
11
|
+
```
|
|
12
|
+
1. Consider renaming your graphics files afterward (`valid_names.z-prompt`)so they are skipped on future runs
|
api_logic_server_cli/prototypes/base/security/authentication_provider/keycloak/auth_provider.py
CHANGED
|
@@ -78,7 +78,7 @@ class Authentication_Provider(Abstract_Authentication_Provider):
|
|
|
78
78
|
from flask import jsonify, request
|
|
79
79
|
from config.config import Args # circular import error if at top
|
|
80
80
|
|
|
81
|
-
jwks_uri = Args.instance.
|
|
81
|
+
jwks_uri = Args.instance.keycloak_base_url + '/protocol/openid-connect/certs'
|
|
82
82
|
for i in range(100):
|
|
83
83
|
# we retry a couple of times in case there are connection problems
|
|
84
84
|
try:
|
|
@@ -34,6 +34,8 @@ class Roles():
|
|
|
34
34
|
admin = "CS_ADMIN"
|
|
35
35
|
public="public" # p1/p (no roles, but gets public)
|
|
36
36
|
sa="sa"
|
|
37
|
+
default_roles_kcals = "default-roles-kcals"
|
|
38
|
+
uma_authorization = "uma_authorization"
|
|
37
39
|
|
|
38
40
|
DefaultRolePermission(to_role=Roles.sa, can_read=True, can_update=True, can_insert=True, can_delete=True)
|
|
39
41
|
DefaultRolePermission(to_role=Roles.tenant, can_read=True, can_delete=True)
|
|
@@ -43,3 +45,5 @@ DefaultRolePermission(to_role=Roles.teller, can_read=True, can_insert=True,can_u
|
|
|
43
45
|
DefaultRolePermission(to_role=Roles.customer, can_read=True, can_insert=True,can_update=True, can_delete=False)
|
|
44
46
|
DefaultRolePermission(to_role=Roles.read_only, can_read=True, can_insert=False,can_update=False, can_delete=False)
|
|
45
47
|
DefaultRolePermission(to_role=Roles.public, can_read=True, can_insert=False,can_update=False, can_delete=False)
|
|
48
|
+
DefaultRolePermission(to_role=Roles.default_roles_kcals, can_read=True, can_insert=True,can_update=True, can_delete=False)
|
|
49
|
+
DefaultRolePermission(to_role=Roles.uma_authorization, can_read=True, can_insert=True,can_update=True, can_delete=False)
|
|
@@ -140,7 +140,7 @@ def admin_events(flask_app: Flask, args: Args, validation_error: ValidationError
|
|
|
140
140
|
if "keycloak" in provider_name:
|
|
141
141
|
s = (f'\n'
|
|
142
142
|
f' keycloak:\n'
|
|
143
|
-
f' url: {args.
|
|
143
|
+
f' url: {args.keycloak_base}\n'
|
|
144
144
|
f' realm: {args.keycloak_realm}\n'
|
|
145
145
|
f' clientId: {args.keycloak_client_id}\n'
|
|
146
146
|
)
|
|
@@ -148,6 +148,8 @@ def admin_events(flask_app: Flask, args: Args, validation_error: ValidationError
|
|
|
148
148
|
elif "sql" in provider_name:
|
|
149
149
|
sql_auth_config = f'\n endpoint: {args.http_scheme}://{args.swagger_host}:{args.swagger_port}/{args.api_prefix[1:]}/auth/login\n'
|
|
150
150
|
content = content.replace("'{system-default}'", sql_auth_config)
|
|
151
|
+
elif getattr(Config.SECURITY_PROVIDER, 'auth_config', None):
|
|
152
|
+
content = content.replace("'{system-default}'", Config.SECURITY_PROVIDER.auth_config)
|
|
151
153
|
else:
|
|
152
154
|
sys.exit(f"ERROR[admin_loader]: unknown security type: {Config.SECURITY_PROVIDER}")
|
|
153
155
|
|
|
@@ -98,7 +98,7 @@ Then, try your own databases [(db-url examples here)](https://apilogicserver.git
|
|
|
98
98
|
|
|
99
99
|
<br>You can do this with or without signup:
|
|
100
100
|
|
|
101
|
-
1. If you have signed up, this will create and open a project called `genai_demo` from `genai_demo.prompt` (available in left Explorer pane):
|
|
101
|
+
1. If you have signed up (see *To obtain a ChatGPT API Key*, below), this will create and open a project called `genai_demo` from `genai_demo.prompt` (available in left Explorer pane):
|
|
102
102
|
|
|
103
103
|
```bash
|
|
104
104
|
als genai --using=system/genai/examples/genai_demo/genai_demo.prompt --project-name=genai_demo
|
|
@@ -122,6 +122,20 @@ Verify it's operating properly:
|
|
|
122
122
|
|
|
123
123
|
</br>
|
|
124
124
|
|
|
125
|
+
|
|
126
|
+
<details markdown>
|
|
127
|
+
|
|
128
|
+
<summary> To obtain a ChatGPT API Key</summary>
|
|
129
|
+
|
|
130
|
+
<br>GenAI-Logic uses OpenAI, which requires an Open API Key:
|
|
131
|
+
|
|
132
|
+
1. Obtain one from [here](https://platform.openai.com/account/api-keys) or [here](https://platform.openai.com/api-keys)
|
|
133
|
+
2. Authorize payments [here](https://platform.openai.com/settings/organization/billing/overview)
|
|
134
|
+
|
|
135
|
+
</details>
|
|
136
|
+
|
|
137
|
+
</br>
|
|
138
|
+
|
|
125
139
|
<details markdown>
|
|
126
140
|
|
|
127
141
|
<summary> What Just Happened? Next Steps...</summary>
|
|
@@ -544,6 +558,21 @@ ApiLogicServer create --project-name=samples/nw_sample_nocust --db-url=nw
|
|
|
544
558
|
|
|
545
559
|
|
|
546
560
|
|
|
561
|
+
## Explore WebGenAI
|
|
562
|
+
|
|
563
|
+
In addition to the CLI examples above, you can use [WebGenAI](https://apilogicserver.github.io/Docs/WebGenAI/) - a web interface for creating projects from prompts. You can install WebGenAI on a server, so that created projects are easy to review with colleagues.
|
|
564
|
+
|
|
565
|
+
To try WebGenAI:
|
|
566
|
+
|
|
567
|
+
```bash
|
|
568
|
+
cd webgenai
|
|
569
|
+
docker compose up
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
You will be directed to the registration process. You will also require a ChatGPT API Key as described above.
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
|
|
547
576
|
|
|
548
577
|
|
|
549
578
|
## Appendix: Quick Basic Demo
|
|
@@ -14,6 +14,8 @@ Use case: Check Credit
|
|
|
14
14
|
Use case: App Integration
|
|
15
15
|
1. Send the Order to Kafka topic 'order_shipping' if the date_shipped is not None.
|
|
16
16
|
|
|
17
|
+
Graph sales by month.
|
|
18
|
+
|
|
17
19
|
Ensure each customer and product has a unique name.
|
|
18
20
|
|
|
19
21
|
Ensure each Item quantity is not null.
|