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.
Files changed (75) hide show
  1. {ApiLogicServer-14.2.2.dist-info → ApiLogicServer-14.3.0.dist-info}/METADATA +2 -2
  2. {ApiLogicServer-14.2.2.dist-info → ApiLogicServer-14.3.0.dist-info}/RECORD +73 -54
  3. api_logic_server_cli/api_logic_server.py +47 -10
  4. api_logic_server_cli/api_logic_server_info.yaml +3 -3
  5. api_logic_server_cli/cli.py +9 -3
  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/client.py +24 -0
  16. api_logic_server_cli/genai/genai.py +37 -17
  17. api_logic_server_cli/genai/genai_logic_builder.py +21 -35
  18. api_logic_server_cli/genai/genai_svcs.py +109 -13
  19. api_logic_server_cli/genai/genai_utils.py +0 -1
  20. api_logic_server_cli/model_migrator/model_migrator_start.py +1 -1
  21. api_logic_server_cli/model_migrator/reposreader.py +9 -1
  22. api_logic_server_cli/model_migrator/rule_obj.py +24 -6
  23. api_logic_server_cli/prototypes/base/api/api_discovery/ontimize_api.py +4 -1
  24. api_logic_server_cli/prototypes/base/config/activate_logicbank.py +8 -4
  25. api_logic_server_cli/prototypes/base/config/config.py +10 -6
  26. api_logic_server_cli/prototypes/base/database/bind_dbs.py +2 -1
  27. api_logic_server_cli/prototypes/base/database/test_data/readme.md +5 -5
  28. api_logic_server_cli/prototypes/base/logic/declare_logic.py +8 -3
  29. api_logic_server_cli/prototypes/base/logic/load_verify_rules.py +216 -0
  30. api_logic_server_cli/prototypes/base/logic/logic_discovery/auto_discovery.py +23 -11
  31. api_logic_server_cli/prototypes/genai_demo/database/models.py +11 -55
  32. api_logic_server_cli/prototypes/genai_demo/logic/declare_logic.py +29 -21
  33. api_logic_server_cli/prototypes/manager/.vscode/launch.json +3 -3
  34. api_logic_server_cli/prototypes/manager/README.md +25 -10
  35. api_logic_server_cli/prototypes/manager/system/genai/create_db_models_inserts/create_db_models_imports.py +1 -0
  36. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo.response_example +19 -18
  37. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/.DS_Store +0 -0
  38. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/000_you_are.prompt +1 -0
  39. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/001_logic_training.prompt +314 -0
  40. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/002_create_db_models.prompt +150 -0
  41. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/003_create_db_models.response +134 -0
  42. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/004_iteratio_logic.prompt +131 -0
  43. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/005_create_db_models.response-example +141 -0
  44. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/create_db_models.py +105 -0
  45. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/db.dbml +70 -0
  46. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/readme.md +6 -0
  47. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/response.json +178 -0
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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
  53. 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
  54. 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
  55. 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
  56. 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
  57. api_logic_server_cli/prototypes/manager/system/genai/examples/time_tracking_billing/002_create_db_models.prompt +194 -0
  58. api_logic_server_cli/prototypes/manager/system/genai/examples/time_tracking_billing/003_create_db_models.response +298 -0
  59. api_logic_server_cli/prototypes/manager/system/genai/examples/time_tracking_billing/db.sqlite +0 -0
  60. api_logic_server_cli/prototypes/manager/system/genai/examples/time_tracking_billing/readme.md +8 -0
  61. api_logic_server_cli/prototypes/manager/system/genai/learning_requests/logic_bank_api.prompt +14 -10
  62. api_logic_server_cli/prototypes/manager/system/genai/prompt_inserts/iteration.prompt +2 -1
  63. api_logic_server_cli/prototypes/nw_no_cust/venv_setup/system_note.txt +1 -1
  64. api_logic_server_cli/prototypes/ont_app/templates/home_tree_template.html +9 -0
  65. api_logic_server_cli/prototypes/ont_app/templates/tree_routing.jinja +32 -0
  66. api_logic_server_cli/sqlacodegen_wrapper/sqlacodegen/sqlacodegen/__pycache__/codegen.cpython-312.pyc +0 -0
  67. api_logic_server_cli/sqlacodegen_wrapper/sqlacodegen/sqlacodegen/codegen.py +2 -1
  68. api_logic_server_cli/tools/mini_skel/logic/load_verify_rules.py +1 -1
  69. api_logic_server_cli/tools/mini_skel/run.py +1 -0
  70. api_logic_server_cli/model_migrator/system/custom_endpoint.py +0 -545
  71. api_logic_server_cli/prototypes/base/database/test_data/z_test_data_rows.py +0 -98
  72. {ApiLogicServer-14.2.2.dist-info → ApiLogicServer-14.3.0.dist-info}/LICENSE +0 -0
  73. {ApiLogicServer-14.2.2.dist-info → ApiLogicServer-14.3.0.dist-info}/WHEEL +0 -0
  74. {ApiLogicServer-14.2.2.dist-info → ApiLogicServer-14.3.0.dist-info}/entry_points.txt +0 -0
  75. {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
- log.info(f"Svcs log file: /tmp/genai_svcs.log")
70
+
69
71
  except Exception as exc:
70
72
  pass # this is just for WebGenAI, ok to ignore error
71
73
 
72
- def get_code(rule_list: List[DotMap]) -> str:
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
- return translated_logic
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 == "*": # system default chatgpt 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, response_format=WGResult,
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 = "fedex" # pass as args.lac_project_name TODO
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["functionType"]
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
- entityLower = entity.lower()
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
- return jsonify({"code":1,"message":f"{ex}","data":[],"sqlTypes":None})
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
- raise e
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
- 'authentication': flask_app.config['SQLALCHEMY_DATABASE_URI_AUTHENTICATION']
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
- GenAI often fails to build proper test data that matches the derivation rules.
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
- from logic.logic_discovery.auto_discovery import discover_logic
26
- discover_logic()
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
+