ApiLogicServer 14.2.2__py3-none-any.whl → 14.3.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.
- {ApiLogicServer-14.2.2.dist-info → ApiLogicServer-14.3.0.dist-info}/METADATA +2 -2
- {ApiLogicServer-14.2.2.dist-info → ApiLogicServer-14.3.0.dist-info}/RECORD +73 -54
- api_logic_server_cli/api_logic_server.py +47 -10
- api_logic_server_cli/api_logic_server_info.yaml +3 -3
- api_logic_server_cli/cli.py +9 -3
- api_logic_server_cli/create_from_model/__pycache__/api_logic_server_utils.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/__pycache__/ont_build.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/__pycache__/ont_create.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/api_logic_server_utils.py +4 -0
- api_logic_server_cli/create_from_model/ont_build.py +53 -19
- api_logic_server_cli/create_from_model/ont_create.py +14 -5
- api_logic_server_cli/fragments/declare_logic.py +72 -0
- api_logic_server_cli/{prototypes/manager/system/genai/create_db_models_inserts/logic_discovery_prefix.py → fragments/declare_logic_begin.py} +2 -1
- api_logic_server_cli/fragments/declare_logic_end.py +52 -0
- api_logic_server_cli/genai/client.py +24 -0
- api_logic_server_cli/genai/genai.py +37 -17
- api_logic_server_cli/genai/genai_logic_builder.py +21 -35
- api_logic_server_cli/genai/genai_svcs.py +109 -13
- api_logic_server_cli/genai/genai_utils.py +0 -1
- api_logic_server_cli/model_migrator/model_migrator_start.py +1 -1
- api_logic_server_cli/model_migrator/reposreader.py +9 -1
- api_logic_server_cli/model_migrator/rule_obj.py +24 -6
- api_logic_server_cli/prototypes/base/api/api_discovery/ontimize_api.py +4 -1
- api_logic_server_cli/prototypes/base/config/activate_logicbank.py +8 -4
- api_logic_server_cli/prototypes/base/config/config.py +10 -6
- api_logic_server_cli/prototypes/base/database/bind_dbs.py +2 -1
- api_logic_server_cli/prototypes/base/database/test_data/readme.md +5 -5
- api_logic_server_cli/prototypes/base/logic/declare_logic.py +8 -3
- api_logic_server_cli/prototypes/base/logic/load_verify_rules.py +216 -0
- api_logic_server_cli/prototypes/base/logic/logic_discovery/auto_discovery.py +23 -11
- api_logic_server_cli/prototypes/genai_demo/database/models.py +11 -55
- api_logic_server_cli/prototypes/genai_demo/logic/declare_logic.py +29 -21
- api_logic_server_cli/prototypes/manager/.vscode/launch.json +3 -3
- api_logic_server_cli/prototypes/manager/README.md +25 -10
- api_logic_server_cli/prototypes/manager/system/genai/create_db_models_inserts/create_db_models_imports.py +1 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo.response_example +19 -18
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/.DS_Store +0 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/000_you_are.prompt +1 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/001_logic_training.prompt +314 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/002_create_db_models.prompt +150 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/003_create_db_models.response +134 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/004_iteratio_logic.prompt +131 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/005_create_db_models.response-example +141 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/create_db_models.py +105 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/db.dbml +70 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/readme.md +6 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/response.json +178 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/wg_dev_merge/base_genai_demo_no_logic/logic/declare_logic.py +0 -1
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/wg_dev_merge/dev_demo_no_logic_fixed/logic/declare_logic.py +0 -1
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/wg_dev_merge/wg_demo_no_logic_fixed/genai/examples/genai_demo/wg_dev_merge/base_genai_demo_no_logic/logic/declare_logic.py +0 -1
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/wg_dev_merge/wg_demo_no_logic_fixed/genai/examples/genai_demo/wg_dev_merge/dev_demo_no_logic_fixed/logic/declare_logic.py +0 -1
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/wg_dev_merge/wg_demo_no_logic_fixed/genai/examples/genai_demo/wg_dev_merge/wg_genai_demo_no_logic_fixed_from_CLI/logic/declare_logic.py +0 -1
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/wg_dev_merge/wg_demo_no_logic_fixed/logic/declare_logic.py +0 -1
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/wg_dev_merge/wg_demo_no_logic_fixed/system/genai/examples/genai_demo/wg_dev_merge/base_genai_demo_no_logic/logic/declare_logic.py +0 -1
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/wg_dev_merge/wg_demo_no_logic_fixed/system/genai/examples/genai_demo/wg_dev_merge/dev_demo_no_logic_fixed/logic/declare_logic.py +0 -1
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/wg_dev_merge/wg_demo_no_logic_fixed/system/genai/examples/genai_demo/wg_dev_merge/wg_genai_demo_no_logic_fixed_from_CLI/logic/declare_logic.py +0 -1
- api_logic_server_cli/prototypes/manager/system/genai/examples/time_tracking_billing/002_create_db_models.prompt +194 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/time_tracking_billing/003_create_db_models.response +298 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/time_tracking_billing/db.sqlite +0 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/time_tracking_billing/readme.md +8 -0
- api_logic_server_cli/prototypes/manager/system/genai/learning_requests/logic_bank_api.prompt +14 -10
- api_logic_server_cli/prototypes/manager/system/genai/prompt_inserts/iteration.prompt +2 -1
- api_logic_server_cli/prototypes/nw_no_cust/venv_setup/system_note.txt +1 -1
- api_logic_server_cli/prototypes/ont_app/templates/home_tree_template.html +9 -0
- api_logic_server_cli/prototypes/ont_app/templates/tree_routing.jinja +32 -0
- api_logic_server_cli/sqlacodegen_wrapper/sqlacodegen/sqlacodegen/__pycache__/codegen.cpython-312.pyc +0 -0
- api_logic_server_cli/sqlacodegen_wrapper/sqlacodegen/sqlacodegen/codegen.py +2 -1
- api_logic_server_cli/tools/mini_skel/logic/load_verify_rules.py +1 -1
- api_logic_server_cli/tools/mini_skel/run.py +1 -0
- api_logic_server_cli/model_migrator/system/custom_endpoint.py +0 -545
- api_logic_server_cli/prototypes/base/database/test_data/z_test_data_rows.py +0 -98
- {ApiLogicServer-14.2.2.dist-info → ApiLogicServer-14.3.0.dist-info}/LICENSE +0 -0
- {ApiLogicServer-14.2.2.dist-info → ApiLogicServer-14.3.0.dist-info}/WHEEL +0 -0
- {ApiLogicServer-14.2.2.dist-info → ApiLogicServer-14.3.0.dist-info}/entry_points.txt +0 -0
- {ApiLogicServer-14.2.2.dist-info → ApiLogicServer-14.3.0.dist-info}/top_level.txt +0 -0
|
@@ -21,6 +21,8 @@ import ast
|
|
|
21
21
|
import astor
|
|
22
22
|
import yaml
|
|
23
23
|
|
|
24
|
+
from genai.client import get_ai_client
|
|
25
|
+
|
|
24
26
|
K_LogicBankOff = "LBX"
|
|
25
27
|
''' LBX Disable Logic (for demos) '''
|
|
26
28
|
K_LogicBankTraining = "Here is the simplified API for LogicBank"
|
|
@@ -65,20 +67,103 @@ try: # this is just for WebGenAI
|
|
|
65
67
|
# Add the file handler to the logger
|
|
66
68
|
log.addHandler(file_handler)
|
|
67
69
|
log.setLevel(logging.DEBUG)
|
|
68
|
-
|
|
70
|
+
|
|
69
71
|
except Exception as exc:
|
|
70
72
|
pass # this is just for WebGenAI, ok to ignore error
|
|
71
73
|
|
|
72
|
-
def
|
|
73
|
-
"""returns code snippet for rules from rule
|
|
74
|
+
def get_code_update_logic_file(rule_list: List[DotMap], logic_file_path: Path = None) -> str:
|
|
75
|
+
"""returns code snippet for rules from rule, updates rules if logic_file_path provided
|
|
76
|
+
|
|
77
|
+
* see avoid_collisions_on_rule
|
|
74
78
|
|
|
75
79
|
Args:
|
|
76
80
|
rule_list (List[DotMap]): list of rules from ChatGPT in DotMap format
|
|
81
|
+
logic_file_path (Path): if provided, update default rule file, with provisions for model named `Rule`
|
|
77
82
|
|
|
78
83
|
Returns:
|
|
79
84
|
str: the rule code
|
|
80
85
|
"""
|
|
81
86
|
|
|
87
|
+
import re
|
|
88
|
+
|
|
89
|
+
patterns = [ re.compile(r"derive=(\w+).*$"),
|
|
90
|
+
re.compile(r"from_parent=(\w+).*$"),
|
|
91
|
+
re.compile(r"Rule\.constraint\(validate=(\w+).*$"),
|
|
92
|
+
re.compile(r"as_sum_of=(\w+).*$"),
|
|
93
|
+
re.compile(r"as_count_of=(\w+).*$")
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
def get_imports(each_line: str, imports: set):
|
|
97
|
+
"""updates imports by extracting the class names to import
|
|
98
|
+
|
|
99
|
+
eg, Rule.constraint(validate=Customer) -> Customer
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
each_line (str): the ChatGPT code
|
|
103
|
+
imports (set): the set of imported classes (updated in place)
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
for each_pattern in patterns:
|
|
107
|
+
match = each_pattern.search(each_line)
|
|
108
|
+
if match:
|
|
109
|
+
the_class_name = match.group(1)
|
|
110
|
+
log.debug(f'.. found class: {the_class_name} in: {each_line}')
|
|
111
|
+
imports.add(the_class_name)
|
|
112
|
+
else:
|
|
113
|
+
pass
|
|
114
|
+
# log.debug(f'.. no classes found in: {each_line}')
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
def insert_logic_into_file_and_avoid_collisions_on_rule(translated_logic: str, imports: set, logic_file_path: Path = None) -> None:
|
|
118
|
+
""" Update logic file with rule code (if provided), with collision avoidance on models named Rule
|
|
119
|
+
|
|
120
|
+
If there's a model named `Rule`, that collides with LogicBank.Rule. So, change LogicBank.Rule refs:
|
|
121
|
+
|
|
122
|
+
1. `from logic_bank.logic_bank import Rule`
|
|
123
|
+
* to: from logic_bank.logic_bank import Rule as LogicBankRule
|
|
124
|
+
2. `Rule.early_row_event_all_classes(early_row_event_all_classes=handle_all)`
|
|
125
|
+
* to: LogicBankRule.early_row_event_all_classes
|
|
126
|
+
* not used in logic_files, so project is None
|
|
127
|
+
3. Users' logic (e.g, Rule.constraint) --> LogicBank.Rule.constraint
|
|
128
|
+
|
|
129
|
+
Beware of these cases:
|
|
130
|
+
1. als create - this is not used (but logic/declare_logic.py must exist)
|
|
131
|
+
2. als genai - uses this, with discovery stuff & Rule.early_event at the end
|
|
132
|
+
3. als genai-logic - logic/logic_discovery files, no discovery stuff at end
|
|
133
|
+
4. webG logic - operates differently, to create logic/wg_rules files (for diagnostics)
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
rule_list (List[DotMap]): list of rules from ChatGPT in DotMap format
|
|
137
|
+
project (Project): a Project object (unless creating a logic file)
|
|
138
|
+
imports (set): the set of imported classes
|
|
139
|
+
"""
|
|
140
|
+
insert_logic = "\n # Logic from GenAI: (or, use your IDE w/ code completion)\n"
|
|
141
|
+
insert_logic += translated_logic
|
|
142
|
+
insert_logic += "\n # End Logic from GenAI\n\n"
|
|
143
|
+
|
|
144
|
+
utils.insert_lines_at(lines=insert_logic,
|
|
145
|
+
file_name=logic_file_path,
|
|
146
|
+
at='discover_logic()',
|
|
147
|
+
after=True)
|
|
148
|
+
|
|
149
|
+
if 'Rule' not in imports:
|
|
150
|
+
return
|
|
151
|
+
if logic_file_path is None: # this needs review for WebGenAI
|
|
152
|
+
log.debug(f'.. .. WebGenAI - avoid_collisions_on_rule: {logic_file_path}')
|
|
153
|
+
return
|
|
154
|
+
# find and replace `Rule` in declare_logic.py:
|
|
155
|
+
utils.replace_string_in_file(search_for='from logic_bank.logic_bank import Rule',
|
|
156
|
+
replace_with='from logic_bank.logic_bank import Rule as LogicBankRule',
|
|
157
|
+
in_file=logic_file_path)
|
|
158
|
+
utils.replace_string_in_file(search_for='Rule.early_row_event_all_classes',
|
|
159
|
+
replace_with='LogicBankRule.early_row_event_all_classes',
|
|
160
|
+
in_file=logic_file_path)
|
|
161
|
+
utils.replace_string_in_file(search_for=' Rule.',
|
|
162
|
+
replace_with=' LogicBankRule.',
|
|
163
|
+
in_file=logic_file_path)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
82
167
|
def remove_logic_halluncinations(each_line: str) -> str:
|
|
83
168
|
"""remove hallucinations from logic
|
|
84
169
|
|
|
@@ -117,7 +202,9 @@ def get_code(rule_list: List[DotMap]) -> str:
|
|
|
117
202
|
log.debug(f'.. removed hallucination: {each_line}')
|
|
118
203
|
return return_line
|
|
119
204
|
|
|
205
|
+
|
|
120
206
|
translated_logic = ""
|
|
207
|
+
imports = set()
|
|
121
208
|
for each_rule in rule_list:
|
|
122
209
|
comment_line = each_rule.description
|
|
123
210
|
translated_logic += f'\n # {comment_line}\n'
|
|
@@ -127,11 +214,19 @@ def get_code(rule_list: List[DotMap]) -> str:
|
|
|
127
214
|
for each_line in code_lines:
|
|
128
215
|
if 'declare_logic.py' not in each_line:
|
|
129
216
|
each_repaired_line = remove_logic_halluncinations(each_line=each_line)
|
|
217
|
+
get_imports(each_line = each_repaired_line, imports=imports)
|
|
130
218
|
if not each_repaired_line.startswith(' '): # sometimes in indents, sometimes not
|
|
131
219
|
each_repaired_line = ' ' + each_repaired_line
|
|
132
220
|
if 'def declare_logic' not in each_repaired_line:
|
|
133
|
-
translated_logic += each_repaired_line + '\n'
|
|
134
|
-
|
|
221
|
+
translated_logic += each_repaired_line + '\n'
|
|
222
|
+
|
|
223
|
+
# from database.models import Customer, Order, Item, Product
|
|
224
|
+
return_translated_logic = ' from database.models import ' + ', '.join(imports) + '\n' + translated_logic
|
|
225
|
+
insert_logic_into_file_and_avoid_collisions_on_rule(
|
|
226
|
+
imports=imports,
|
|
227
|
+
translated_logic=return_translated_logic,
|
|
228
|
+
logic_file_path=logic_file_path)
|
|
229
|
+
return return_translated_logic
|
|
135
230
|
|
|
136
231
|
def rebuild_test_data_for_project(response: str = 'docs/response.json',
|
|
137
232
|
project: Project = None,
|
|
@@ -166,7 +261,6 @@ def rebuild_test_data_for_project(response: str = 'docs/response.json',
|
|
|
166
261
|
existing_models = rebuild_project
|
|
167
262
|
del existing_models['rules']
|
|
168
263
|
del existing_models['test_data']
|
|
169
|
-
del existing_models['test_data_rows']
|
|
170
264
|
del existing_models['test_data_sqlite']
|
|
171
265
|
rebuild_request.append({"role": "user", "content": json.dumps(existing_models)})
|
|
172
266
|
with open(get_manager_path().joinpath('system/genai/prompt_inserts/rebuild_test_data.prompt'), 'r') as file:
|
|
@@ -434,6 +528,8 @@ def fix_and_write_model_file(response_dict: DotMap, save_dir: str, post_error:
|
|
|
434
528
|
check_for_row_name = False
|
|
435
529
|
if 'Base.metadata.create_all(engine)' in each_fixed_line:
|
|
436
530
|
each_fixed_line = each_fixed_line.replace('Base.metadata.create_all(engine)', '# Base.metadata.create_all(engine)')
|
|
531
|
+
if ',00' in each_fixed_line:
|
|
532
|
+
each_fixed_line = each_fixed_line.replace(',00', ',0')
|
|
437
533
|
return each_fixed_line
|
|
438
534
|
|
|
439
535
|
row_names = list()
|
|
@@ -759,22 +855,22 @@ def call_chatgpt(messages: List[Dict[str, str]], api_version: str, using: str) -
|
|
|
759
855
|
using (str): str to save response.json (relative to cwd)
|
|
760
856
|
Returns:
|
|
761
857
|
str: response from ChatGPT
|
|
762
|
-
"""
|
|
858
|
+
"""
|
|
763
859
|
try:
|
|
764
860
|
start_time = time.time()
|
|
765
|
-
db_key = os.getenv("APILOGICSERVER_CHATGPT_APIKEY")
|
|
766
|
-
client = OpenAI(api_key=os.getenv("APILOGICSERVER_CHATGPT_APIKEY"))
|
|
767
861
|
model = api_version
|
|
768
862
|
if model == "": # default from CLI is '', meaning fall back to env variable or system default...
|
|
769
863
|
model = os.getenv("APILOGICSERVER_CHATGPT_MODEL")
|
|
770
|
-
if model is None or model == "*":
|
|
771
|
-
model = "gpt-4o-2024-08-06"
|
|
864
|
+
if model is None or model == "*": # system default chatgpt model
|
|
865
|
+
model = "gpt-4o-2024-08-06" # 33 sec
|
|
866
|
+
# model = "o3-mini" # 130 sec
|
|
772
867
|
with open(Path(using).joinpath('request.json'), "w") as request_file: # save for debug
|
|
773
868
|
json.dump(messages, request_file, indent=4)
|
|
774
869
|
log.info(f'.. saved request: {using}/request.json')
|
|
775
|
-
|
|
870
|
+
client = get_ai_client()
|
|
776
871
|
completion = client.beta.chat.completions.parse(
|
|
777
|
-
messages=messages,
|
|
872
|
+
messages=messages,
|
|
873
|
+
response_format=WGResult,
|
|
778
874
|
# temperature=self.project.genai_temperature, values .1 and .7 made students / charges fail
|
|
779
875
|
model=model # for own model, use "ft:gpt-4o-2024-08-06:personal:logicbank:ARY904vS"
|
|
780
876
|
)
|
|
@@ -98,7 +98,6 @@ class GenAIUtils:
|
|
|
98
98
|
api_version = f"{self.genai_version}"
|
|
99
99
|
start_time = time.time()
|
|
100
100
|
db_key = os.getenv("APILOGICSERVER_CHATGPT_APIKEY", "")
|
|
101
|
-
client = OpenAI(api_key=db_key)
|
|
102
101
|
model = api_version if api_version else os.getenv("APILOGICSERVER_CHATGPT_MODEL", "gpt-4o-2024-08-06")
|
|
103
102
|
self.resolved_model = model
|
|
104
103
|
|
|
@@ -56,7 +56,7 @@ class ModelMigrator(object):
|
|
|
56
56
|
# need repos location and project api name (teamspaces/api/{lac_project_name})
|
|
57
57
|
running_at = Path(__file__)
|
|
58
58
|
repos_location = f"{running_at.parent}{os.sep}CALiveAPICreator.repository"
|
|
59
|
-
lac_project_name = "
|
|
59
|
+
lac_project_name = "repos" # pass as args.lac_project_name TODO
|
|
60
60
|
repo_reader.start(repos_location, self.project_directory, lac_project_name, table_to_class )
|
|
61
61
|
log.debug('.. ModelMigrator.run() - done')
|
|
62
62
|
|
|
@@ -404,7 +404,7 @@ class ModelMigrationService(object):
|
|
|
404
404
|
j = json.loads(d)
|
|
405
405
|
isActive = j["isActive"]
|
|
406
406
|
if isActive:
|
|
407
|
-
func_type = j
|
|
407
|
+
func_type = j.get("functionType","Java")
|
|
408
408
|
if func_type == "rowLevel":
|
|
409
409
|
comments = j["comments"]
|
|
410
410
|
if comments != "":
|
|
@@ -427,6 +427,14 @@ class ModelMigrationService(object):
|
|
|
427
427
|
self.add_content("'''")
|
|
428
428
|
self.add_content("")
|
|
429
429
|
lac_func.append(name)
|
|
430
|
+
elif func_type == "Java":
|
|
431
|
+
name = j["name"]
|
|
432
|
+
self.add_content("'''")
|
|
433
|
+
self.add_content(f"#Java Function: {j['methodName']}")
|
|
434
|
+
self.add_content(f"#Java Function: {j['className']}")
|
|
435
|
+
self.add_content("'''")
|
|
436
|
+
self.add_content("")
|
|
437
|
+
lac_func.append(name)
|
|
430
438
|
return lac_func
|
|
431
439
|
|
|
432
440
|
def functionList(self, thisPath: str):
|
|
@@ -102,25 +102,43 @@ class RuleObj:
|
|
|
102
102
|
title =""
|
|
103
103
|
if j.title is not None:
|
|
104
104
|
title = j.title
|
|
105
|
-
funName = "fn_" + name.split(".")[0]
|
|
105
|
+
#funName = "fn_" + name.split(".")[0]
|
|
106
|
+
entityLower = entity.lower()
|
|
107
|
+
funName = f"fn_{entityLower}_{ruleType}_{name}"
|
|
106
108
|
comments = j.comments
|
|
107
109
|
appliesTo = ""
|
|
108
110
|
if j.appliesTo is not None:
|
|
109
111
|
appliesTo = j.appliesTo
|
|
110
112
|
|
|
111
113
|
# Define a function to use in the rule
|
|
112
|
-
ruleJSObj = None if self.jsObj is None else fixup(self.jsObj)
|
|
114
|
+
ruleJSObj = None if self.jsObj is None else fixup(self.jsObj) if self.jsObj is None else ""
|
|
113
115
|
tab = "\t\t"
|
|
114
116
|
self.add_content(f"\t# RuleType: {ruleType}")
|
|
115
117
|
self.add_content(f"\t# Title: {title}")
|
|
116
118
|
self.add_content(f"\t# Name: {name}")
|
|
117
119
|
self.add_content(f"\t# Entity: {entity}")
|
|
120
|
+
|
|
121
|
+
codeType = j.get("codeType", None)
|
|
122
|
+
if codeType == "Java":
|
|
123
|
+
className = j.get("className", None)
|
|
124
|
+
methodName = j.get("methodName", None)
|
|
125
|
+
self.add_content(f"\t# CodeType: {codeType}")
|
|
126
|
+
self.add_content(f"\t# ClassName: {className}")
|
|
127
|
+
self.add_content(f"\t# MethodName: {methodName}")
|
|
128
|
+
if name == "cache":
|
|
129
|
+
funName = f"fn_{methodName}"
|
|
130
|
+
|
|
131
|
+
|
|
118
132
|
self.add_content(f"\t# Comments: {comments}")
|
|
119
133
|
self.add_content("")
|
|
134
|
+
if codeType == "Java":
|
|
135
|
+
self.add_content(f"\tdef {funName}(row: models.{entity}, old_row: models.{entity}, logic_row: LogicRow):")
|
|
136
|
+
self.add_content(f"\t\t# Call Java Code: {className}.{methodName}(row, old_row, logic_row)")
|
|
137
|
+
self.add_content("\t\tpass")
|
|
138
|
+
|
|
139
|
+
|
|
120
140
|
if ruleJSObj is not None:
|
|
121
|
-
|
|
122
|
-
funName = f"\tfn_{entityLower}_{ruleType}_{name}"
|
|
123
|
-
if len(ruleJSObj) < 80 and ruleType == "formula":
|
|
141
|
+
if len(ruleJSObj) < 80 and ruleType == "formula" and codeType == "JavaScript":
|
|
124
142
|
pass
|
|
125
143
|
else:
|
|
126
144
|
self.add_content(f"\tdef {funName}(row: models.{entity}, old_row: models.{entity}, logic_row: LogicRow):")
|
|
@@ -149,7 +167,7 @@ class RuleObj:
|
|
|
149
167
|
if ruleJSObj is not None and len(ruleJSObj) > 80:
|
|
150
168
|
self.add_content(f"{tab}calling={funName})")
|
|
151
169
|
else:
|
|
152
|
-
ruleJSObj = ruleJSObj.replace("return","lambda row: ")
|
|
170
|
+
ruleJSObj = ruleJSObj.replace("return","lambda row: ") if ruleJSObj is not None else ""
|
|
153
171
|
self.add_content(f"{tab}as_expression={ruleJSObj})")
|
|
154
172
|
case "count":
|
|
155
173
|
attr = j.attribute
|
|
@@ -231,7 +231,10 @@ def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_deco
|
|
|
231
231
|
session.flush()
|
|
232
232
|
except Exception as ex:
|
|
233
233
|
session.rollback()
|
|
234
|
-
|
|
234
|
+
msg = f"{ex.message if hasattr(ex, 'message') else ex}"
|
|
235
|
+
return jsonify(
|
|
236
|
+
{"code": 1, "message": f"{msg}", "data": [], "sqlTypes": None}
|
|
237
|
+
)
|
|
235
238
|
|
|
236
239
|
return jsonify({"code":0,"message":f"{method}:True","data":result,"sqlTypes":None}) #{f"{method}":True})
|
|
237
240
|
|
|
@@ -19,23 +19,27 @@ def activate_logicbank(session, constraint_handler):
|
|
|
19
19
|
|
|
20
20
|
app_logger.info("LogicBank Activation - declare_logic.py")
|
|
21
21
|
aggregate_defaults = os.environ.get("AGGREGATE_DEFAULTS") == "True"
|
|
22
|
+
all_defaults = os.environ.get("ALL_DEFAULTS") == "True"
|
|
22
23
|
try: # hover activate for info
|
|
23
24
|
LogicBank.activate(session=session,
|
|
24
25
|
activator=declare_logic.declare_logic,
|
|
25
26
|
constraint_event=constraint_handler,
|
|
26
|
-
aggregate_defaults=aggregate_defaults
|
|
27
|
+
aggregate_defaults=aggregate_defaults,
|
|
28
|
+
all_defaults=all_defaults)
|
|
27
29
|
except LBActivateException as e:
|
|
28
30
|
app_logger.error("\nLogic Bank Activation Error -- see https://apilogicserver.github.io/Docs/WebGenAI-CLI/#recovery-options")
|
|
29
31
|
if e.invalid_rules: logic_logger.error(f"Invalid Rules: {e.invalid_rules}")
|
|
30
32
|
if e.missing_attributes: logic_logger.error(f"Missing Attrs (try als genai-utils --fixup): {e.missing_attributes}")
|
|
31
33
|
app_logger.error("\n")
|
|
32
|
-
if not os.environ.get("VERIFY_RULES") == "True":
|
|
33
|
-
# WG Rule Verification, continue if VERIFY_RULES is True
|
|
34
|
+
if not os.environ.get("VERIFY_RULES") == "True" and not os.environ.get("WG_PROJECT") == "True":
|
|
35
|
+
# WG Rule Verification, continue if VERIFY_RULES is True or inside WebGenAI
|
|
34
36
|
raise e
|
|
35
37
|
|
|
36
38
|
except Exception as e:
|
|
37
39
|
app_logger.error(f"Logic Bank Activation Error: {e}")
|
|
38
40
|
app_logger.exception(e)
|
|
39
|
-
|
|
41
|
+
if not os.environ.get("WG_PROJECT") == "True":
|
|
42
|
+
# Continue if inside WebGenAI
|
|
43
|
+
raise e
|
|
40
44
|
logic_logger.setLevel(logic_logger_level)
|
|
41
45
|
|
|
@@ -46,6 +46,7 @@ class OptLocking(ExtendedEnum):
|
|
|
46
46
|
|
|
47
47
|
basedir = path.abspath(path.dirname(__file__))
|
|
48
48
|
load_dotenv(path.join(basedir, "default.env"))
|
|
49
|
+
project_path = Path(__file__).parent.parent
|
|
49
50
|
app_logger = logging.getLogger('api_logic_server_app')
|
|
50
51
|
|
|
51
52
|
def is_docker() -> bool:
|
|
@@ -89,11 +90,10 @@ class Config:
|
|
|
89
90
|
FLASK_APP = environ.get("FLASK_APP")
|
|
90
91
|
FLASK_ENV = environ.get("FLASK_ENV")
|
|
91
92
|
DEBUG = environ.get("DEBUG")
|
|
92
|
-
|
|
93
|
-
running_at = Path(__file__)
|
|
94
|
-
project_abs_dir = running_at.parent.absolute()
|
|
93
|
+
|
|
95
94
|
|
|
96
95
|
# Database
|
|
96
|
+
db_path = str(project_path.joinpath('database/db.sqlite'))
|
|
97
97
|
SQLALCHEMY_DATABASE_URI : typing.Optional[str] = f"replace_db_url"
|
|
98
98
|
# override SQLALCHEMY_DATABASE_URI here as required
|
|
99
99
|
|
|
@@ -143,9 +143,8 @@ class Config:
|
|
|
143
143
|
app_logger.info(f'config.py - security disabled')
|
|
144
144
|
|
|
145
145
|
# Begin Multi-Database URLs (from ApiLogicServer add-db...)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
SQLALCHEMY_DATABASE_URI_AUTHENTICATION = 'sqlite:///../database/authentication_db.sqlite'
|
|
146
|
+
auth_db_path = str(project_path.joinpath('database/authentication_db.sqlite'))
|
|
147
|
+
SQLALCHEMY_DATABASE_URI_AUTHENTICATION = f'sqlite:///{auth_db_path}'
|
|
149
148
|
app_logger.info(f'config.py - SQLALCHEMY_DATABASE_URI_AUTHENTICATION: {SQLALCHEMY_DATABASE_URI_AUTHENTICATION}\n')
|
|
150
149
|
|
|
151
150
|
# as desired, use env variable: export SQLALCHEMY_DATABASE_URI='sqlite:////Users/val/dev/servers/docker_api_logic_project/database/db.sqliteXX'
|
|
@@ -153,6 +152,11 @@ class Config:
|
|
|
153
152
|
SQLALCHEMY_DATABASE_URI_AUTHENTICATION = os.getenv('SQLALCHEMY_DATABASE_URI_AUTHENTICATION') # type: ignore # type: str
|
|
154
153
|
app_logger.debug(f'.. overridden from env variable: SQLALCHEMY_DATABASE_URI_AUTHENTICATION')
|
|
155
154
|
|
|
155
|
+
# Single Page App (SPA) Landing Page Database
|
|
156
|
+
landing_db_path = project_path.joinpath('database/db_spa.sqlite')
|
|
157
|
+
SQLALCHEMY_DATABASE_URI_LANDING = f'sqlite:///{landing_db_path}'
|
|
158
|
+
if landing_db_path.exists():
|
|
159
|
+
app_logger.info(f'config.py - SQLALCHEMY_DATABASE_URI_LANDING: {SQLALCHEMY_DATABASE_URI_LANDING}\n')
|
|
156
160
|
|
|
157
161
|
# End Multi-Database URLs (from ApiLogicServer add-db...)
|
|
158
162
|
|
|
@@ -14,7 +14,8 @@ def bind_dbs(flask_app):
|
|
|
14
14
|
""" called by api_logic_server_run to open/bind each additional database"""
|
|
15
15
|
|
|
16
16
|
flask_app.config.update(SQLALCHEMY_BINDS = {
|
|
17
|
-
|
|
17
|
+
'authentication': flask_app.config['SQLALCHEMY_DATABASE_URI_AUTHENTICATION'],
|
|
18
|
+
'landing_page' : flask_app.config['SQLALCHEMY_DATABASE_URI_LANDING']
|
|
18
19
|
}) # make multiple databases available to SQLAlchemy
|
|
19
20
|
|
|
20
21
|
return
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
ChatGPT sometimes fails to build proper test data that matches the derivation rules.
|
|
2
2
|
|
|
3
|
-
You can rebuild the test data, using Logic Bank rules for proper derivations.
|
|
3
|
+
You can rebuild the test data, using Logic Bank rules for proper derivations, to rebuild your `database/db.sqlite` (make a copy first to preserve your existing data).
|
|
4
4
|
|
|
5
|
-
Envisioned support will create a new db.sqlite, with test data that reflects derivations.
|
|
6
|
-
Review, and copy to your database/db.sqlite.
|
|
7
5
|
|
|
8
6
|
```
|
|
9
7
|
als genai-utils --rebuild-test-data
|
|
10
|
-
```
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
You can explore the generated `database/test_data/test_data_code.py` to control test data generation.
|
|
@@ -5,9 +5,9 @@ from logic_bank.extensions.rule_extensions import RuleExtension
|
|
|
5
5
|
from logic_bank.logic_bank import Rule
|
|
6
6
|
from logic_bank.logic_bank import DeclareRule
|
|
7
7
|
import database.models as models
|
|
8
|
-
from database.models import *
|
|
9
8
|
import api.system.opt_locking.opt_locking as opt_locking
|
|
10
9
|
from security.system.authorization import Grant, Security
|
|
10
|
+
from logic.load_verify_rules import load_verify_rules
|
|
11
11
|
import logging
|
|
12
12
|
|
|
13
13
|
app_logger = logging.getLogger(__name__)
|
|
@@ -22,8 +22,13 @@ def declare_logic():
|
|
|
22
22
|
Your Code Goes Here - Use code completion (Rule.) to declare rules
|
|
23
23
|
'''
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
if os.environ.get("WG_PROJECT"):
|
|
26
|
+
# Inside WG: Load rules from docs/expprt/export.json
|
|
27
|
+
load_verify_rules()
|
|
28
|
+
else:
|
|
29
|
+
# Outside WG: load declare_logic function
|
|
30
|
+
from logic.logic_discovery.auto_discovery import discover_logic
|
|
31
|
+
discover_logic()
|
|
27
32
|
|
|
28
33
|
def handle_all(logic_row: LogicRow): # #als: TIME / DATE STAMPING, OPTIMISTIC LOCKING
|
|
29
34
|
"""
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This code loads and verifies rules from export.json and activates them if they pass verification
|
|
3
|
+
# It is WebGenAI specific, used only when env var WG_PROJECT is set
|
|
4
|
+
#
|
|
5
|
+
import ast
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import safrs
|
|
11
|
+
import subprocess
|
|
12
|
+
from importlib import import_module
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from werkzeug.utils import secure_filename
|
|
15
|
+
from database.models import *
|
|
16
|
+
from logic_bank.logic_bank import DeclareRule, Rule, LogicBank
|
|
17
|
+
from colorama import Fore, Style, init
|
|
18
|
+
from logic_bank.logic_bank import RuleBank
|
|
19
|
+
from logic_bank.rule_bank.rule_bank_setup import find_referenced_attributes
|
|
20
|
+
import tempfile
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
app_logger = logging.getLogger(__name__)
|
|
24
|
+
declare_logic_message = "ALERT: *** No Rules Yet ***" # printed in api_logic_server.py
|
|
25
|
+
|
|
26
|
+
rule_import_template = """
|
|
27
|
+
from logic_bank.logic_bank import Rule
|
|
28
|
+
from database.models import *
|
|
29
|
+
|
|
30
|
+
def init_rule():
|
|
31
|
+
{rule_code}
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
MANAGER_PATH = "/opt/webgenai/database/manager.py"
|
|
35
|
+
EXPORT_JSON_PATH = os.environ.get("EXPORT_JSON_PATH", "./docs/export/export.json")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def set_rule_status(rule_id, status):
|
|
39
|
+
"""
|
|
40
|
+
Call the manager.py script to set the status of a rule
|
|
41
|
+
|
|
42
|
+
(if the status is "active", the manager will remove the rule error)
|
|
43
|
+
"""
|
|
44
|
+
if not Path(MANAGER_PATH).exists():
|
|
45
|
+
app_logger.info(f"No manager, can't set rule {rule_id} status {status}")
|
|
46
|
+
return
|
|
47
|
+
subprocess.run([
|
|
48
|
+
"python", MANAGER_PATH,
|
|
49
|
+
"-R", rule_id,
|
|
50
|
+
"--rule-status", status],
|
|
51
|
+
cwd="/opt/webgenai")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def set_rule_error(rule_id, error):
|
|
55
|
+
"""
|
|
56
|
+
Call the manager.py script to set the error of a rule
|
|
57
|
+
"""
|
|
58
|
+
if not Path(MANAGER_PATH).exists():
|
|
59
|
+
app_logger.warning(f"No manager, can't set rule {rule_id} error {error}")
|
|
60
|
+
return
|
|
61
|
+
subprocess.check_output([
|
|
62
|
+
"python", MANAGER_PATH,
|
|
63
|
+
"-R", rule_id,
|
|
64
|
+
"--rule-error", error],
|
|
65
|
+
cwd="/opt/webgenai")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def check_rule_code_syntax(rule_code):
|
|
69
|
+
"""
|
|
70
|
+
Check the syntax of the rule code
|
|
71
|
+
"""
|
|
72
|
+
try:
|
|
73
|
+
ast.parse(rule_code)
|
|
74
|
+
return rule_code
|
|
75
|
+
except Exception as exc:
|
|
76
|
+
log.warning(f"Syntax error in rule code '{rule_code}': {exc}")
|
|
77
|
+
|
|
78
|
+
rule_code = rule_code.replace("\\\\", "\\")
|
|
79
|
+
try:
|
|
80
|
+
ast.parse(rule_code)
|
|
81
|
+
return rule_code
|
|
82
|
+
except Exception as exc:
|
|
83
|
+
log.warning(f"Syntax error in rule code '{rule_code}': {exc}")
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_exported_rules(rule_code_dir):
|
|
88
|
+
"""
|
|
89
|
+
Read the exported rules from export.json and write the code to the
|
|
90
|
+
rule_code_dir
|
|
91
|
+
"""
|
|
92
|
+
export_file = Path(EXPORT_JSON_PATH)
|
|
93
|
+
if not export_file.exists():
|
|
94
|
+
app_logger.info(f"{export_file.resolve()} does not exist")
|
|
95
|
+
return []
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
with open(export_file) as f:
|
|
99
|
+
export = json.load(f)
|
|
100
|
+
rules = export.get("rules", [])
|
|
101
|
+
except Exception as exc:
|
|
102
|
+
app_logger.warning(f"Failed to load rules from {export_file}: {exc}")
|
|
103
|
+
return []
|
|
104
|
+
|
|
105
|
+
for rule in rules:
|
|
106
|
+
if rule["status"] == "rejected":
|
|
107
|
+
continue
|
|
108
|
+
rule_file = rule_code_dir / f"{secure_filename(rule['name']).replace('.','_')}.py"
|
|
109
|
+
try:
|
|
110
|
+
# write current rule to rule_file
|
|
111
|
+
# (we can't use eval, because logicbank uses inspect)
|
|
112
|
+
rule_code_str = check_rule_code_syntax(rule["code"])
|
|
113
|
+
if not rule_code_str:
|
|
114
|
+
continue
|
|
115
|
+
with open(rule_file, "w") as temp_file:
|
|
116
|
+
rule_code = "\n".join([f" {code}" for code in rule_code_str.split("\n")])
|
|
117
|
+
temp_file.write(rule_import_template.format(rule_code=rule_code))
|
|
118
|
+
temp_file_path = temp_file.name
|
|
119
|
+
# module_name used to import current rule
|
|
120
|
+
module_name = Path(temp_file_path).stem
|
|
121
|
+
rule["module_name"] = module_name
|
|
122
|
+
app_logger.info(f"{rule['id']} rule file: {rule_file}")
|
|
123
|
+
except Exception as exc:
|
|
124
|
+
app_logger.exception(exc)
|
|
125
|
+
app_logger.warning(f"Failed to write rule code to {rule_file}: {exc}")
|
|
126
|
+
|
|
127
|
+
return rules
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def verify_rules(rule_code_dir, rule_type="accepted"):
|
|
131
|
+
"""
|
|
132
|
+
Verify the rules from export.json and activate them if they pass verification
|
|
133
|
+
|
|
134
|
+
Write the rule code to a temporary file and import it as a module
|
|
135
|
+
"""
|
|
136
|
+
rules = get_exported_rules(rule_code_dir)
|
|
137
|
+
|
|
138
|
+
for rule in rules:
|
|
139
|
+
if not rule["status"] == rule_type:
|
|
140
|
+
continue
|
|
141
|
+
module_name = rule["module_name"]
|
|
142
|
+
app_logger.info(f"\n{Fore.BLUE}Verifying rule: {module_name} - {rule['id']}{Style.RESET_ALL}")
|
|
143
|
+
try:
|
|
144
|
+
rule_module = import_module(module_name)
|
|
145
|
+
rule_module.init_rule()
|
|
146
|
+
LogicBank.activate(session=safrs.DB.session, activator=rule_module.init_rule)
|
|
147
|
+
if rule["status"] != "active":
|
|
148
|
+
set_rule_status(rule["id"], "active")
|
|
149
|
+
app_logger.info(f"\n{Fore.GREEN}Activated rule {rule['id']}{Style.RESET_ALL}")
|
|
150
|
+
|
|
151
|
+
except Exception as exc:
|
|
152
|
+
app_logger.exception(exc)
|
|
153
|
+
set_rule_error(rule["id"], f"{type(exc).__name__}: {exc}")
|
|
154
|
+
app_logger.warning(f"{Fore.RED}Failed to verify {rule_type} rule code\n{rule['code']}\n{Fore.YELLOW}{type(exc).__name__}: {exc}{Style.RESET_ALL}")
|
|
155
|
+
app_logger.debug(f"{rule}")
|
|
156
|
+
rule["status"] = "accepted"
|
|
157
|
+
rule["error"] = f"{type(exc).__name__}: {exc}"
|
|
158
|
+
|
|
159
|
+
return rules
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def load_active_rules(rule_code_dir, rules=None):
|
|
163
|
+
"""
|
|
164
|
+
Load the active rules from export.json
|
|
165
|
+
"""
|
|
166
|
+
if not rules:
|
|
167
|
+
rules = get_exported_rules(rule_code_dir)
|
|
168
|
+
for rule in rules:
|
|
169
|
+
module_name = rule.get("module_name", None)
|
|
170
|
+
if not rule["status"] == "active" or module_name is None:
|
|
171
|
+
continue
|
|
172
|
+
app_logger.info(f"{Fore.GREEN}Loading Rule Module {module_name} {rule['id']} {Style.RESET_ALL}")
|
|
173
|
+
rule_module = import_module(module_name)
|
|
174
|
+
try:
|
|
175
|
+
rule_module.init_rule()
|
|
176
|
+
except Exception as exc:
|
|
177
|
+
app_logger.exception(exc)
|
|
178
|
+
app_logger.warning(f"{Fore.RED}Failed to load active rule {rule['id']} {rule['code']} {Style.RESET_ALL}")
|
|
179
|
+
set_rule_error(rule["id"], f"{type(exc).__name__}: {exc}")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def get_project_id():
|
|
183
|
+
if os.environ.get("PROJECT_ID"):
|
|
184
|
+
return os.environ.get("PROJECT_ID")
|
|
185
|
+
|
|
186
|
+
return Path(os.getcwd()).name
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def load_verify_rules():
|
|
190
|
+
|
|
191
|
+
# Add FileHandler to root_logger
|
|
192
|
+
log_file = Path(os.getenv("LOG_DIR",tempfile.mkdtemp())) / "load_verify_rules.log"
|
|
193
|
+
file_handler = logging.FileHandler(log_file)
|
|
194
|
+
root_logger = logging.getLogger()
|
|
195
|
+
root_logger.addHandler(file_handler)
|
|
196
|
+
|
|
197
|
+
rule_code_dir = Path("./logic/wg_rules") # in the project root
|
|
198
|
+
rule_code_dir.mkdir(parents=True, exist_ok=True)
|
|
199
|
+
sys.path.append(f"{rule_code_dir}")
|
|
200
|
+
|
|
201
|
+
app_logger.info(f"Loading rules from {rule_code_dir.resolve()}")
|
|
202
|
+
|
|
203
|
+
rules = []
|
|
204
|
+
|
|
205
|
+
if os.environ.get("VERIFY_RULES") == "True":
|
|
206
|
+
rules = verify_rules(rule_code_dir, rule_type="active")
|
|
207
|
+
verify_rules(rule_code_dir, rule_type="accepted")
|
|
208
|
+
else:
|
|
209
|
+
try:
|
|
210
|
+
load_active_rules(rule_code_dir, rules)
|
|
211
|
+
except Exception as exc:
|
|
212
|
+
app_logger.exception(exc)
|
|
213
|
+
app_logger.warning(f"{Fore.RED}Failed to load active exported rules: {exc}{Style.RESET_ALL}")
|
|
214
|
+
|
|
215
|
+
root_logger.removeHandler(file_handler)
|
|
216
|
+
|