ApiLogicServer 14.2.20__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.20.dist-info → ApiLogicServer-14.3.0.dist-info}/METADATA +2 -2
- {ApiLogicServer-14.2.20.dist-info → ApiLogicServer-14.3.0.dist-info}/RECORD +65 -47
- api_logic_server_cli/api_logic_server.py +4 -1
- api_logic_server_cli/api_logic_server_info.yaml +3 -3
- api_logic_server_cli/create_from_model/__pycache__/api_logic_server_utils.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/__pycache__/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/genai.py +21 -8
- api_logic_server_cli/genai/genai_logic_builder.py +14 -11
- api_logic_server_cli/genai/genai_svcs.py +102 -7
- 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/database/bind_dbs.py +1 -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 +22 -13
- api_logic_server_cli/prototypes/genai_demo/logic/declare_logic.py +29 -21
- api_logic_server_cli/prototypes/manager/README.md +18 -3
- 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/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.20.dist-info → ApiLogicServer-14.3.0.dist-info}/LICENSE +0 -0
- {ApiLogicServer-14.2.20.dist-info → ApiLogicServer-14.3.0.dist-info}/WHEEL +0 -0
- {ApiLogicServer-14.2.20.dist-info → ApiLogicServer-14.3.0.dist-info}/entry_points.txt +0 -0
- {ApiLogicServer-14.2.20.dist-info → ApiLogicServer-14.3.0.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
|
@@ -15,7 +15,7 @@ def bind_dbs(flask_app):
|
|
|
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
|
+
'landing_page' : flask_app.config['SQLALCHEMY_DATABASE_URI_LANDING']
|
|
19
19
|
}) # make multiple databases available to SQLAlchemy
|
|
20
20
|
|
|
21
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
|
+
|
|
@@ -23,21 +23,30 @@ def discover_logic():
|
|
|
23
23
|
spec.loader.exec_module(each_logic_file) # runs "bare" module code (e.g., initialization)
|
|
24
24
|
each_logic_file.declare_logic() # invoke create function
|
|
25
25
|
|
|
26
|
-
if False and Path(__file__).parent.parent.parent.joinpath("docs/project_is_genai_demo.txt").exists():
|
|
27
|
-
|
|
26
|
+
# if False and Path(__file__).parent.parent.parent.joinpath("docs/project_is_genai_demo.txt").exists():
|
|
27
|
+
# return # for genai_demo, logic is in logic/declare_logic.py (so ignore logic_discovery)
|
|
28
28
|
|
|
29
29
|
wg_logic_path = Path(__file__).parent.parent.joinpath("wg_rules")
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
logic.append(
|
|
38
|
-
|
|
39
|
-
spec.loader.exec_module(
|
|
40
|
-
|
|
30
|
+
if wg_logic_path.exists():
|
|
31
|
+
run_local = os.environ.get("WG_PROJECT") is None # eg, running export locally
|
|
32
|
+
# run_local = False # for debug
|
|
33
|
+
if run_local:
|
|
34
|
+
wg_export_logic_path = Path(__file__).parent.parent.parent.joinpath("logic/wg_rules/active_rules_export.py")
|
|
35
|
+
if wg_export_logic_path.is_file():
|
|
36
|
+
spec = importlib.util.spec_from_file_location("module.name", wg_export_logic_path)
|
|
37
|
+
logic.append(str(wg_export_logic_path))
|
|
38
|
+
wg_export_logic_file = importlib.util.module_from_spec(spec)
|
|
39
|
+
spec.loader.exec_module(wg_export_logic_file) # runs "bare" module code (e.g., initialization)
|
|
40
|
+
wg_export_logic_file.declare_logic() # invoke create function
|
|
41
|
+
else:
|
|
42
|
+
for root, dirs, files in os.walk(wg_logic_path):
|
|
43
|
+
for file in files:
|
|
44
|
+
if file.endswith(".py") and 'active_rules_export.py' != file:
|
|
45
|
+
spec = importlib.util.spec_from_file_location("module.name", wg_logic_path.joinpath(file))
|
|
46
|
+
logic.append(file)
|
|
47
|
+
each_logic_file = importlib.util.module_from_spec(spec)
|
|
48
|
+
spec.loader.exec_module(each_logic_file) # runs "bare" module code (e.g., initialization)
|
|
49
|
+
each_logic_file.init_rule() # invoke create function
|
|
41
50
|
|
|
42
51
|
app_logger.info(f"..discovered logic: {logic}")
|
|
43
52
|
return
|
|
@@ -20,19 +20,16 @@ def declare_logic():
|
|
|
20
20
|
''' Declarative multi-table derivations and constraints, extensible with Python.
|
|
21
21
|
|
|
22
22
|
Brief background: see readme_declare_logic.md
|
|
23
|
-
|
|
24
|
-
Your Code Goes Here - Use code completion (Rule.) to declare rules
|
|
25
23
|
|
|
26
|
-
GenAI:
|
|
24
|
+
GenAI: the following prompt was sent to GenAI-Logic, which translated it into the code below:
|
|
27
25
|
|
|
28
|
-
Use
|
|
26
|
+
Use case: Check Credit
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
5. Store the Items.UnitPrice as a copy from Product.UnitPrice
|
|
28
|
+
1. The Customer's balance is less than the credit limit
|
|
29
|
+
2. The Customer's balance is the sum of the Order amount_total where date_shipped is null
|
|
30
|
+
3. The Order's amount_total is the sum of the Item amount
|
|
31
|
+
4. The Item amount is the quantity * unit_price
|
|
32
|
+
5. The Item unit_price is copied from the Product unit_price
|
|
36
33
|
'''
|
|
37
34
|
|
|
38
35
|
from logic_bank.logic_bank import Rule
|
|
@@ -40,39 +37,50 @@ def declare_logic():
|
|
|
40
37
|
from logic.logic_discovery.auto_discovery import discover_logic
|
|
41
38
|
discover_logic()
|
|
42
39
|
|
|
43
|
-
# Logic from GenAI: (or, use your IDE w/ code completion)
|
|
40
|
+
# Logic from GenAI: (or, use your IDE w/ code completion)
|
|
41
|
+
|
|
42
|
+
# Ensures the customer's balance is less than the credit limit.
|
|
43
|
+
Rule.constraint(validate=Customer,
|
|
44
|
+
as_condition=lambda row: row.Balance <= row.CreditLimit,
|
|
45
|
+
error_msg="Customer balance ({row.Balance}) exceeds credit limit ({row.CreditLimit})")
|
|
46
|
+
|
|
47
|
+
# Computes the customer's balance as the sum of unshipped orders.
|
|
44
48
|
Rule.sum(derive=Customer.Balance, as_sum_of=Order.AmountTotal, where=lambda row: row.ShipDate is None)
|
|
49
|
+
|
|
50
|
+
# Computes the total amount of an order as the sum of item amounts.
|
|
45
51
|
Rule.sum(derive=Order.AmountTotal, as_sum_of=Item.Amount)
|
|
52
|
+
|
|
53
|
+
# Calculates the item amount as the quantity times unit price. (original rule, prior to iteration)
|
|
46
54
|
# Rule.formula(derive=Item.amount, as_expression=lambda row: row.quantity * row.unit_price)
|
|
55
|
+
|
|
56
|
+
# Copies the unit price from the parent product to the item.
|
|
47
57
|
Rule.copy(derive=Item.UnitPrice, from_parent=Product.UnitPrice)
|
|
48
|
-
Rule.constraint(validate=Customer,
|
|
49
|
-
as_condition=lambda row: row.Balance <= row.CreditLimit,
|
|
50
|
-
error_msg="Customer balance ({row.Balance}) exceeds credit limit ({row.CreditLimit})")
|
|
51
58
|
|
|
52
59
|
# End Logic from GenAI
|
|
53
60
|
|
|
61
|
+
|
|
62
|
+
#als: Demonstrate that logic == Rules + Python (for extensibility)
|
|
63
|
+
|
|
64
|
+
# 4. Items.Amount = Quantity * UnitPrice, altered with IDE for CarbonNeutral discount
|
|
54
65
|
def derive_amount(row: Item, old_row: Item, logic_row: LogicRow):
|
|
55
66
|
amount = row.Quantity * row.UnitPrice
|
|
56
67
|
if row.Product.CarbonNeutral == True and row.Quantity >= 10:
|
|
57
68
|
amount = amount * Decimal(0.9) # breakpoint here
|
|
58
69
|
return amount
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
Rule.formula(derive=Item.Amount, calling=derive_amount)
|
|
62
|
-
|
|
63
|
-
#als: Demonstrate that logic == Rules + Python (for extensibility)
|
|
70
|
+
# now register function; note logic ordering is automatic
|
|
71
|
+
Rule.formula(derive=Item.Amount, calling=derive_amount)
|
|
64
72
|
|
|
65
73
|
def send_order_to_shipping(row: Order, old_row: Order, logic_row: LogicRow):
|
|
66
74
|
""" #als: Send Kafka message formatted by OrderShipping RowDictMapper
|
|
67
75
|
|
|
68
76
|
Format row per shipping requirements, and send (e.g., a message)
|
|
69
77
|
|
|
70
|
-
NB: the after_flush event makes Order.Id avaible.
|
|
78
|
+
NB: the after_flush event makes Order.Id avaible.
|
|
71
79
|
|
|
72
80
|
Args:
|
|
73
81
|
row (Order): inserted Order
|
|
74
82
|
old_row (Order): n/a
|
|
75
|
-
logic_row (LogicRow): bundles curr/old row, with ins/upd/dlt
|
|
83
|
+
logic_row (LogicRow): bundles curr/old row, with ins/upd/dlt (etc) state
|
|
76
84
|
"""
|
|
77
85
|
if logic_row.is_inserted():
|
|
78
86
|
kafka_producer.send_kafka_message(logic_row=logic_row,
|
|
@@ -149,9 +149,22 @@ Verify it's operating properly:
|
|
|
149
149
|
|
|
150
150
|
<details markdown>
|
|
151
151
|
|
|
152
|
-
<summary> You can iterate the data model
|
|
152
|
+
<summary> You can iterate the logic and data model</summary>
|
|
153
153
|
|
|
154
|
-
<br>
|
|
154
|
+
<br>Logic iterations are particuarly useful. For example, here we take the basic check-credit logic, and add:
|
|
155
|
+
|
|
156
|
+
> Provide a 10% discount when buying more than 10 carbon neutral products.<br><br>The Item carbon neutral is copied from the Product carbon neutral
|
|
157
|
+
|
|
158
|
+
Explore [genai_demo_iteration_discount](system/genai/examples/genai_demo/genai_demo_iteration_discount). This will add carbon_neutral to the data model, and update the logic to provide the discount:
|
|
159
|
+
|
|
160
|
+
```bash title='Iterate Business Logic'
|
|
161
|
+
# Iterate with data model and logic
|
|
162
|
+
als genai --project-name='genai_demo_with_logic' --using=system/genai/examples/genai_demo/genai_demo_iteration
|
|
163
|
+
# open Docs/db.dbml
|
|
164
|
+
```
|
|
165
|
+
<br>
|
|
166
|
+
|
|
167
|
+
You can add new columns/tables, while keeping the prior model intact:
|
|
155
168
|
|
|
156
169
|
```bash title='Iterate Without Logic'
|
|
157
170
|
# Step 1 - create without logic
|
|
@@ -164,7 +177,9 @@ als genai --project-name='genai_demo_no_logic' --using=system/genai/examples/gen
|
|
|
164
177
|
als genai --project-name='genai_demo_with_logic' --using=system/genai/examples/genai_demo/genai_demo_iteration
|
|
165
178
|
# open Docs/db.dbml
|
|
166
179
|
```
|
|
167
|
-
|
|
180
|
+
|
|
181
|
+
Explore [genai_demo_iteration](system/genai/examples/genai_demo/genai_demo_iteration) - observe the `--using` is a *directory* of prompts. These include the prompts from the first example, plus an *iteration prompt* (`004_iteration_renames_logic.prompt`) to rename tables and add logic.
|
|
182
|
+
|
|
168
183
|
|
|
169
184
|
</details>
|
|
170
185
|
</br>
|