ApiLogicServer 14.2.20__py3-none-any.whl → 14.3.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. {ApiLogicServer-14.2.20.dist-info → ApiLogicServer-14.3.7.dist-info}/METADATA +2 -2
  2. {ApiLogicServer-14.2.20.dist-info → ApiLogicServer-14.3.7.dist-info}/RECORD +90 -69
  3. api_logic_server_cli/api_logic_server.py +5 -1
  4. api_logic_server_cli/api_logic_server_info.yaml +3 -3
  5. api_logic_server_cli/cli.py +5 -2
  6. api_logic_server_cli/create_from_model/__pycache__/api_logic_server_utils.cpython-312.pyc +0 -0
  7. api_logic_server_cli/create_from_model/__pycache__/ont_build.cpython-312.pyc +0 -0
  8. api_logic_server_cli/create_from_model/__pycache__/ont_create.cpython-312.pyc +0 -0
  9. api_logic_server_cli/create_from_model/api_logic_server_utils.py +4 -0
  10. api_logic_server_cli/create_from_model/ont_build.py +53 -19
  11. api_logic_server_cli/create_from_model/ont_create.py +14 -5
  12. api_logic_server_cli/fragments/declare_logic.py +72 -0
  13. api_logic_server_cli/{prototypes/manager/system/genai/create_db_models_inserts/logic_discovery_prefix.py → fragments/declare_logic_begin.py} +2 -1
  14. api_logic_server_cli/fragments/declare_logic_end.py +52 -0
  15. api_logic_server_cli/genai/genai.py +25 -8
  16. api_logic_server_cli/genai/genai_logic_builder.py +14 -11
  17. api_logic_server_cli/genai/genai_svcs.py +104 -7
  18. api_logic_server_cli/manager.py +20 -16
  19. api_logic_server_cli/model_migrator/model_migrator_start.py +1 -1
  20. api_logic_server_cli/model_migrator/reposreader.py +9 -1
  21. api_logic_server_cli/model_migrator/rule_obj.py +24 -6
  22. api_logic_server_cli/prototypes/base/api/api_discovery/ontimize_api.py +4 -1
  23. api_logic_server_cli/prototypes/base/api/system/expression_parser.py +10 -4
  24. api_logic_server_cli/prototypes/base/config/activate_logicbank.py +8 -4
  25. api_logic_server_cli/prototypes/base/database/bind_dbs.py +1 -1
  26. api_logic_server_cli/prototypes/base/database/test_data/readme.md +5 -5
  27. api_logic_server_cli/prototypes/base/integration/kafka/kafka_producer.py +32 -8
  28. api_logic_server_cli/prototypes/base/integration/system/RowDictMapper.py +33 -16
  29. api_logic_server_cli/prototypes/base/logic/declare_logic.py +9 -3
  30. api_logic_server_cli/prototypes/base/logic/load_verify_rules.py +217 -0
  31. api_logic_server_cli/prototypes/base/logic/logic_discovery/auto_discovery.py +22 -13
  32. api_logic_server_cli/prototypes/genai_demo/api/customize_api.py +9 -11
  33. api_logic_server_cli/prototypes/genai_demo/database/.DS_Store +0 -0
  34. api_logic_server_cli/prototypes/genai_demo/database/db.sqlite +0 -0
  35. api_logic_server_cli/prototypes/genai_demo/database/models.py +52 -42
  36. api_logic_server_cli/prototypes/genai_demo/integration/row_dict_maps/OrderB2B.py +4 -6
  37. api_logic_server_cli/prototypes/genai_demo/integration/row_dict_maps/__pycache__/OrderB2B.cpython-312.pyc +0 -0
  38. api_logic_server_cli/prototypes/genai_demo/integration/row_dict_maps/row_dict_maps_readme.md +3 -0
  39. api_logic_server_cli/prototypes/genai_demo/logic/__pycache__/declare_logic.cpython-312.pyc +0 -0
  40. api_logic_server_cli/prototypes/genai_demo/logic/__pycache__/load_verify_rules.cpython-312.pyc +0 -0
  41. api_logic_server_cli/prototypes/genai_demo/logic/declare_logic.py +58 -62
  42. api_logic_server_cli/prototypes/genai_demo/logic/load_verify_rules.py +216 -0
  43. api_logic_server_cli/prototypes/genai_demo/logic/logic_discovery/__pycache__/__init__.cpython-312.pyc +0 -0
  44. api_logic_server_cli/prototypes/genai_demo/logic/logic_discovery/__pycache__/auto_discovery.cpython-312.pyc +0 -0
  45. api_logic_server_cli/prototypes/genai_demo/logic/logic_discovery/__pycache__/error_testing.cpython-312.pyc +0 -0
  46. api_logic_server_cli/prototypes/genai_demo/logic/logic_discovery/auto_discovery.py +52 -0
  47. api_logic_server_cli/prototypes/genai_demo/logic/readme_declare_logic.md +172 -0
  48. api_logic_server_cli/prototypes/genai_demo/security/__pycache__/declare_security.cpython-312.pyc +0 -0
  49. api_logic_server_cli/prototypes/genai_demo/ui/admin/admin.yaml +86 -53
  50. api_logic_server_cli/prototypes/manager/.vscode/launch.json +1 -1
  51. api_logic_server_cli/prototypes/manager/README.md +19 -4
  52. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo.prompt +4 -1
  53. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo.response_example +34 -26
  54. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_informal.prompt +3 -0
  55. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/.DS_Store +0 -0
  56. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/000_you_are.prompt +1 -0
  57. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/001_logic_training.prompt +314 -0
  58. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/002_create_db_models.prompt +150 -0
  59. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/003_create_db_models.response +134 -0
  60. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/004_iteratio_logic.prompt +131 -0
  61. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/005_create_db_models.response-example +141 -0
  62. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/create_db_models.py +105 -0
  63. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/db.dbml +70 -0
  64. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/readme.md +6 -0
  65. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/response.json +178 -0
  66. 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
  67. 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
  68. 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
  69. 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
  70. 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
  71. 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
  72. 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
  73. 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
  74. 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
  75. api_logic_server_cli/prototypes/manager/system/genai/examples/time_tracking_billing/002_create_db_models.prompt +194 -0
  76. api_logic_server_cli/prototypes/manager/system/genai/examples/time_tracking_billing/003_create_db_models.response +298 -0
  77. api_logic_server_cli/prototypes/{genai_demo/database/chatgpt/sample_ai.sqlite → manager/system/genai/examples/time_tracking_billing/db.sqlite} +0 -0
  78. api_logic_server_cli/prototypes/manager/system/genai/examples/time_tracking_billing/readme.md +61 -0
  79. api_logic_server_cli/prototypes/manager/system/genai/learning_requests/logic_bank_api.prompt +29 -11
  80. api_logic_server_cli/prototypes/manager/system/genai/prompt_inserts/iteration.prompt +2 -1
  81. api_logic_server_cli/prototypes/nw_no_cust/venv_setup/system_note.txt +1 -1
  82. api_logic_server_cli/prototypes/ont_app/templates/home_tree_template.html +9 -0
  83. api_logic_server_cli/prototypes/ont_app/templates/tree_routing.jinja +32 -0
  84. api_logic_server_cli/sqlacodegen_wrapper/sqlacodegen/sqlacodegen/__pycache__/codegen.cpython-312.pyc +0 -0
  85. api_logic_server_cli/sqlacodegen_wrapper/sqlacodegen/sqlacodegen/codegen.py +4 -2
  86. api_logic_server_cli/tools/mini_skel/logic/load_verify_rules.py +1 -1
  87. api_logic_server_cli/model_migrator/system/custom_endpoint.py +0 -545
  88. api_logic_server_cli/prototypes/base/database/test_data/z_test_data_rows.py +0 -98
  89. api_logic_server_cli/prototypes/genai_demo/database/chatgpt/__pycache__/copilot_models.cpython-312.pyc +0 -0
  90. api_logic_server_cli/prototypes/genai_demo/database/chatgpt/__pycache__/sample_ai_models.cpython-312.pyc +0 -0
  91. api_logic_server_cli/prototypes/genai_demo/database/chatgpt/sample_ai.chatgpt +0 -16
  92. api_logic_server_cli/prototypes/genai_demo/database/chatgpt/sample_ai.sql +0 -66
  93. api_logic_server_cli/prototypes/genai_demo/database/chatgpt/sample_ai_items.sqlite +0 -0
  94. api_logic_server_cli/prototypes/genai_demo/database/chatgpt/sample_ai_models.py +0 -156
  95. api_logic_server_cli/prototypes/genai_demo/database/chatgpt/sample_ai_models.sqlite +0 -0
  96. api_logic_server_cli/prototypes/genai_demo/logic/cocktail-napkin.jpg +0 -0
  97. {ApiLogicServer-14.2.20.dist-info → ApiLogicServer-14.3.7.dist-info}/LICENSE +0 -0
  98. {ApiLogicServer-14.2.20.dist-info → ApiLogicServer-14.3.7.dist-info}/WHEEL +0 -0
  99. {ApiLogicServer-14.2.20.dist-info → ApiLogicServer-14.3.7.dist-info}/entry_points.txt +0 -0
  100. {ApiLogicServer-14.2.20.dist-info → ApiLogicServer-14.3.7.dist-info}/top_level.txt +0 -0
@@ -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
+
@@ -0,0 +1,52 @@
1
+ import importlib
2
+ from pathlib import Path
3
+ import logging
4
+
5
+ app_logger = logging.getLogger(__name__)
6
+
7
+ def discover_logic():
8
+ """
9
+ Discover additional logic in this directory
10
+ """
11
+ import os
12
+ logic = []
13
+ logic_path = Path(__file__).parent
14
+ for root, dirs, files in os.walk(logic_path):
15
+ for file in files:
16
+ if file.endswith(".py"):
17
+ spec = importlib.util.spec_from_file_location("module.name", logic_path.joinpath(file))
18
+ if file.endswith("auto_discovery.py"):
19
+ pass
20
+ else:
21
+ logic.append(file)
22
+ each_logic_file = importlib.util.module_from_spec(spec)
23
+ spec.loader.exec_module(each_logic_file) # runs "bare" module code (e.g., initialization)
24
+ each_logic_file.declare_logic() # invoke create function
25
+
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
+
29
+ wg_logic_path = Path(__file__).parent.parent.joinpath("wg_rules")
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
50
+
51
+ app_logger.info(f"..discovered logic: {logic}")
52
+ return
@@ -0,0 +1,172 @@
1
+ This describes how to use Logic; for more information, [see here](https://apilogicserver.github.io/Docs/Logic-Why).
2
+
3
+ > Key Takeway - Logic: Multi-table Derivation and Constraint Rules, Extensible with Python
4
+ <br>Rules are:
5
+ <br>1. **Declared** in your IDE - 40X more concise
6
+ <br>2. **Activated** on server start
7
+ <br>3. **Executed** - *automatically* - on updates (using SQLAlchemy events)
8
+ <br>4. **Debugged** in your IDE, and with the console log
9
+ <br>For more on rules, [click here](https://apilogicserver.github.io/Docs/Logic-Why/).
10
+
11
+ &nbsp;
12
+
13
+ ## Examples
14
+ Examples from tutorial project:
15
+ * Examples drawn from [tutorial project](https://github.com/ApiLogicServer/demo/blob/main/logic/declare_logic.py)
16
+ * Use Shift + "." to view in project mode
17
+
18
+ You can [find the rules here](https://apilogicserver.github.io/Docs/Logic). Below, we explore the syntax of 3 typical rules.
19
+
20
+ &nbsp;
21
+
22
+ ### 1. Multi-Table Derivations
23
+
24
+ This declares the Customer.Balance as the sum of the unshipped Order.AmountTotal:
25
+
26
+ ```python
27
+ Rule.sum(derive=models.Customer.Balance,
28
+ as_sum_of=models.Order.AmountTotal,
29
+ where=lambda row: row.ShippedDate is None)
30
+ ```
31
+ It means the rule engine **watches** for these changes:
32
+ * Order inserted/deleted, or
33
+ * AmountTotal or ShippedDate or CustomerID changes
34
+
35
+ Iff changes are detected, the engine **reacts** by *adjusting* the Customer.Balance. SQLs are [optimized](#declarative-logic-important-notes).
36
+
37
+ This would **chain** to check the Customers' Constraint rule, described below.
38
+
39
+ &nbsp;
40
+
41
+ ### 2. Constraints: lambda or function
42
+
43
+ Constraints are multi-field conditions which must be true for transactions to succeed (else an exception is raised). You can express the condition as a lambda or a function:
44
+
45
+ **As a lambda:**
46
+ ```python
47
+ Rule.constraint(validate=models.Customer,
48
+ as_condition=lambda row: row.Balance <= row.CreditLimit, # parent references are supported
49
+ error_msg="balance ({row.Balance}) exceeds credit ({row.CreditLimit})")
50
+ ```
51
+
52
+ **Or, as a function:**
53
+ ```python
54
+ def check_balance(row: models.Customer, old_row: models.Customer, logic_row: LogicRow):
55
+ if logic_row.ins_upd_dlt != "dlt": # see also: logic_row.old_row
56
+ return row.Balance <= row.CreditLimit
57
+ else:
58
+ return True
59
+
60
+ Rule.constraint(validate=models.Customer,
61
+ calling=check_balance,
62
+ error_msg=f"balance ({row.Balance}) exceeds credit ({row.CreditLimit})")
63
+ ```
64
+
65
+ &nbsp;
66
+
67
+ ### 3. Row Events: Extensible with Python
68
+
69
+ Events are procedural Python code, providing extensibility for declarative rules:
70
+ ```python
71
+ def congratulate_sales_rep(row: models.Order, old_row: models.Order, logic_row: LogicRow):
72
+ pass # event code here - sending email, messages, etc.
73
+
74
+ Rule.commit_row_event(on_class=models.Order, calling=congratulate_sales_rep)
75
+ ```
76
+ Note there are multiple kinds of events, so you can control whether they run before or after rule execution. For more information, [see here](https://apilogicserver.github.io/Docs/Logic-Type-Constraint).
77
+
78
+ &nbsp;
79
+
80
+ ## LogicRow: old_row, verb, etc
81
+
82
+ A key argument to functions is `logic_row`:
83
+
84
+ * **Wraps row and old_row,** plus methods for insert, update and delete - rule enforcement
85
+
86
+ * **Additional instance variables:** ins_upd_dlt, nest_level, session, etc.
87
+
88
+ * **Helper Methods:** are_attributes_changed, set_same_named_attributes, get_parent_logic_row(role_name), get_derived_attributes, log, is_inserted, etc
89
+
90
+ Here is an example:
91
+
92
+ ```python
93
+ """
94
+ STATE TRANSITION LOGIC, using old_row
95
+ """
96
+ def raise_over_20_percent(row: models.Employee, old_row: models.Employee, logic_row: LogicRow):
97
+ if logic_row.ins_upd_dlt == "upd" and row.Salary > old_row.Salary:
98
+ return row.Salary >= Decimal('1.20') * old_row.Salary
99
+ else:
100
+ return True
101
+
102
+ Rule.constraint(validate=models.Employee,
103
+ calling=raise_over_20_percent,
104
+ error_msg="{row.LastName} needs a more meaningful raise")
105
+ ```
106
+
107
+ Note the `log` method, which enables you to write row/old_row into the log with a short message:
108
+
109
+ ```python
110
+ logic_row.log("no manager for this order's salesrep")
111
+ ```
112
+
113
+ &nbsp;
114
+
115
+ ## Declarative Logic: Important Notes
116
+
117
+ Logic *declarative*, which differs from conventional *procedural* logic:
118
+
119
+ 1. **Automatic Invocation:** you don't call the rules; they execute in response to updates (via SQLAlchemy events).
120
+
121
+ 2. **Automatic Ordering:** you don't order the rules; execution order is based on system-discovered depencencies.
122
+
123
+ 3. **Automatic Optimizations:** logic is optimized to reduce SQLs.
124
+
125
+ * Rule execution is *pruned* if dependent attributes are not altered
126
+ * SQL is optimized, e.g., `sum` rules operate by *adjustment*, not expensive SQL `select sum`
127
+
128
+ These simplify maintenance / iteration: you can be sure new logic is always called, in the correct order.
129
+
130
+ &nbsp;
131
+
132
+ ## Debugging
133
+
134
+ Debug rules using **system-generated logic log** and your **IDE debugger**; for more information, [see here](https://apilogicserver.github.io/Docs/Logic-Use).
135
+
136
+ &nbsp;
137
+
138
+ ### Using the debugger
139
+
140
+ Use the debugger as shown below. Note you can stop in lambda functions.
141
+
142
+ ![Logic Debugger](https://apilogicserver.github.io/Docs/images/logic/logic-debug.png)
143
+
144
+ &nbsp;
145
+
146
+ ### Logic Log
147
+
148
+ Logging is performed using standard Python logging, with a logger named `logic_logger`. Use `info` for tracing, and `debug` for additional information (e.g., all declared rules are logged).
149
+
150
+ In addition, the system logs all rules that fire, to aid in debugging. Referring the the screen shot above:
151
+
152
+ * Each line represents a rule execution, showing row state (old/new values), and the _{reason}_ that caused the update (e.g., client, sum adjustment)
153
+ * Log indention shows multi-table chaining
154
+
155
+ &nbsp;
156
+
157
+ ## How Logic works
158
+
159
+ *Activation* occurs in `api_logic_server_run.py`:
160
+ ```python
161
+ LogicBank.activate(session=session, activator=declare_logic, constraint_event=constraint_handler)
162
+ ```
163
+
164
+ This installs the rule engine as a SQLAlchemy event listener (`before_flush`). So, Logic *runs* automatically, in response to transaction commits (typically via the API).
165
+
166
+ Rules plug into SQLAlchemy events, and execute as follows:
167
+
168
+ | Logic Phase | Why It Matters |
169
+ |:-----------------------------|:---------------------|
170
+ | **Watch** for changes at the attribute level | Performance - Automatic Attribute-level Pruning |
171
+ | **React** by recomputing value | Ensures Reuse - Invocation is automatic<br>Derivations are optimized (e.g. *adjustment updates* - not aggregate queries) |
172
+ | **Chain** to other referencing data | Simplifies Maintenance - ordering is automatic<br>Multi-table logic automation |
@@ -1,109 +1,142 @@
1
1
  about:
2
- date: January 27, 2024 16:41:35
3
- merged:
4
- at: January 28, 2024 13:17:27
5
- new_attributes: 'Product.CarbonNeutral '
6
- new_resources: ''
7
- new_tab_groups: ''
2
+ date: February 02, 2025 11:44:16
8
3
  recent_changes: works with modified safrs-react-admin
9
4
  version: 0.0.0
10
5
  api_root: '{http_type}://{swagger_host}:{port}/{api}'
11
- authentication:
12
- endpoint: '{http_type}://{swagger_host}:{port}/api/auth/login'
6
+ authentication: '{system-default}'
13
7
  info:
14
8
  number_relationships: 3
15
9
  number_tables: 4
10
+ info_toggle_checked: true
16
11
  resources:
17
12
  Customer:
18
13
  attributes:
19
- - label: ' Customer Name*'
20
- name: CustomerName
21
- required: true
14
+ - label: ' name*'
15
+ name: name
22
16
  search: true
23
17
  sort: true
24
- - name: Address
25
- - name: Phone
26
- - name: Balance
27
- - name: CreditLimit
28
- required: true
29
- - name: CustomerID
18
+ - name: balance
19
+ type: DECIMAL
20
+ - name: credit_limit
21
+ type: DECIMAL
22
+ - name: id
23
+ description: Represents a customer in the system with unique name, balance, and
24
+ credit limit attributes.
25
+ info_list: Represents a customer in the system with unique name, balance, and
26
+ credit limit attributes.
30
27
  tab_groups:
31
28
  - direction: tomany
32
29
  fks:
33
- - CustomerID
30
+ - customer_id
34
31
  name: OrderList
35
32
  resource: Order
36
33
  type: Customer
37
- user_key: CustomerName
34
+ user_key: name
38
35
  Item:
39
36
  attributes:
40
- - label: ' Item I D*'
41
- name: ItemID
37
+ - label: ' id*'
38
+ name: id
42
39
  search: true
43
40
  sort: true
44
- - name: OrderID
45
- - name: ProductID
46
- - name: Quantity
41
+ - name: order_id
42
+ - name: product_id
43
+ - name: quantity
47
44
  required: true
48
- - name: UnitPrice
49
- - name: Amount
45
+ - name: unit_price
46
+ type: DECIMAL
47
+ - name: amount
48
+ type: DECIMAL
49
+ description: Represents an item in an order, including quantity and pricing details.
50
+ info_list: Represents an item in an order, including quantity and pricing details.
50
51
  tab_groups:
51
52
  - direction: toone
52
53
  fks:
53
- - OrderID
54
- name: Order
54
+ - order_id
55
+ name: order
55
56
  resource: Order
56
57
  - direction: toone
57
58
  fks:
58
- - ProductID
59
- name: Product
59
+ - product_id
60
+ name: product
60
61
  resource: Product
61
62
  type: Item
62
- user_key: ItemID
63
+ user_key: id
63
64
  Order:
64
65
  attributes:
65
- - label: ' Order I D*'
66
- name: OrderID
66
+ - label: ' id*'
67
+ name: id
67
68
  search: true
68
69
  sort: true
69
- - name: CustomerID
70
- - name: OrderDate
71
- - name: Notes
72
- - name: ShipDate
73
- - name: AmountTotal
70
+ - name: customer_id
71
+ - name: amount_total
72
+ type: DECIMAL
73
+ - name: notes
74
+ - name: date_shipped
75
+ type: DATE
76
+ description: Represents an order made by a customer, including a notes field.
77
+ info_list: Represents an order made by a customer, including a notes field.
74
78
  tab_groups:
75
79
  - direction: tomany
76
80
  fks:
77
- - OrderID
81
+ - order_id
78
82
  name: ItemList
79
83
  resource: Item
80
84
  - direction: toone
81
85
  fks:
82
- - CustomerID
83
- name: Customer
86
+ - customer_id
87
+ name: customer
84
88
  resource: Customer
85
89
  type: Order
86
- user_key: OrderID
90
+ user_key: id
87
91
  Product:
88
92
  attributes:
89
- - label: ' Product Name*'
90
- name: ProductName
91
- required: true
93
+ - label: ' name*'
94
+ name: name
92
95
  search: true
93
96
  sort: true
94
- - name: UnitPrice
95
- required: true
96
- - name: ProductID
97
- - name: CarbonNeutral
98
- type: Boolean
97
+ - name: unit_price
98
+ type: DECIMAL
99
+ - name: carbon_neutral
100
+ type: BOOLEAN
101
+ - name: id
102
+ description: Represents a product available in the system with a unit price.
103
+ info_list: Represents a product available in the system with a unit price.
99
104
  tab_groups:
100
105
  - direction: tomany
101
106
  fks:
102
- - ProductID
107
+ - product_id
103
108
  name: ItemList
104
109
  resource: Item
105
110
  type: Product
106
- user_key: ProductName
111
+ user_key: name
107
112
  settings:
108
- HomeJS: http://localhost:5656/admin-app/home.js
113
+ HomeJS: /admin-app/home.js
109
114
  max_list_columns: 8
115
+ style_guide:
116
+ applicationLocales:
117
+ - en
118
+ - es
119
+ currency_symbol: $
120
+ currency_symbol_position: left
121
+ date_format: LL
122
+ decimal_max: '1000000000'
123
+ decimal_min: '0'
124
+ decimal_separator: .
125
+ detail_mode: tab
126
+ edit_on_mode: dblclick
127
+ exclude_listpicker: false
128
+ include_translation: 'false'
129
+ keycloak_client_id: alsclient
130
+ keycloak_realm: kcals
131
+ keycloak_url: http://localhost:8080
132
+ locale: en
133
+ max_decimal_digits: '4'
134
+ min_decimal_digits: '2'
135
+ new_mode: dialog
136
+ pick_style: list
137
+ row_height: small,
138
+ serviceType: OntimizeEE
139
+ startSessionPath: /auth/login
140
+ style: light
141
+ thousand_separator: ','
142
+ use_keycloak: 'false'
@@ -730,7 +730,7 @@
730
730
  "id": "runProjectName",
731
731
  "type": "promptString",
732
732
  "description": "Project Name (folder) to run",
733
- "default": "samples/nw"
733
+ "default": "genai_demo"
734
734
  },
735
735
  {
736
736
  "id": "inputGenaiProjectName",