ApiLogicServer 14.3.0__py3-none-any.whl → 14.3.11__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.3.0.dist-info → ApiLogicServer-14.3.11.dist-info}/METADATA +3 -3
- {ApiLogicServer-14.3.0.dist-info → ApiLogicServer-14.3.11.dist-info}/RECORD +58 -54
- api_logic_server_cli/api_logic_server.py +2 -1
- api_logic_server_cli/api_logic_server_info.yaml +3 -3
- api_logic_server_cli/cli.py +5 -2
- api_logic_server_cli/create_from_model/__pycache__/ont_build.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/ont_build.py +9 -9
- api_logic_server_cli/create_from_model/safrs-react-admin-npm-build/asset-manifest.json +3 -3
- api_logic_server_cli/create_from_model/safrs-react-admin-npm-build/build-0213.txt +1 -0
- api_logic_server_cli/create_from_model/safrs-react-admin-npm-build/index.html +1 -1
- api_logic_server_cli/create_from_model/safrs-react-admin-npm-build/static/js/main.7c8c0e37.js +3 -0
- api_logic_server_cli/create_from_model/safrs-react-admin-npm-build/static/js/{main.bfe80d1d.js.map → main.7c8c0e37.js.map} +1 -1
- api_logic_server_cli/database/nw-gold.sqlite +0 -0
- api_logic_server_cli/genai/genai.py +13 -3
- api_logic_server_cli/genai/genai_svcs.py +2 -0
- api_logic_server_cli/manager.py +20 -16
- api_logic_server_cli/prototypes/base/api/system/expression_parser.py +10 -4
- api_logic_server_cli/prototypes/base/devops/docker-image/env.list +7 -2
- api_logic_server_cli/prototypes/base/integration/kafka/kafka_producer.py +31 -8
- api_logic_server_cli/prototypes/base/integration/system/RowDictMapper.py +33 -16
- api_logic_server_cli/prototypes/base/logic/declare_logic.py +1 -0
- api_logic_server_cli/prototypes/base/logic/load_verify_rules.py +2 -1
- api_logic_server_cli/prototypes/genai_demo/api/customize_api.py +9 -11
- api_logic_server_cli/prototypes/genai_demo/database/.DS_Store +0 -0
- api_logic_server_cli/prototypes/genai_demo/database/db.sqlite +0 -0
- api_logic_server_cli/prototypes/genai_demo/database/models.py +52 -42
- api_logic_server_cli/prototypes/genai_demo/integration/row_dict_maps/OrderB2B.py +4 -6
- api_logic_server_cli/prototypes/genai_demo/integration/row_dict_maps/__pycache__/OrderB2B.cpython-312.pyc +0 -0
- api_logic_server_cli/prototypes/genai_demo/integration/row_dict_maps/row_dict_maps_readme.md +3 -0
- api_logic_server_cli/prototypes/genai_demo/logic/__pycache__/declare_logic.cpython-312.pyc +0 -0
- api_logic_server_cli/prototypes/genai_demo/logic/__pycache__/load_verify_rules.cpython-312.pyc +0 -0
- api_logic_server_cli/prototypes/genai_demo/logic/declare_logic.py +57 -69
- api_logic_server_cli/prototypes/genai_demo/logic/load_verify_rules.py +216 -0
- api_logic_server_cli/prototypes/genai_demo/logic/logic_discovery/__pycache__/__init__.cpython-312.pyc +0 -0
- api_logic_server_cli/prototypes/genai_demo/logic/logic_discovery/__pycache__/auto_discovery.cpython-312.pyc +0 -0
- api_logic_server_cli/prototypes/genai_demo/logic/logic_discovery/__pycache__/error_testing.cpython-312.pyc +0 -0
- api_logic_server_cli/prototypes/genai_demo/logic/logic_discovery/auto_discovery.py +52 -0
- api_logic_server_cli/prototypes/genai_demo/logic/readme_declare_logic.md +172 -0
- api_logic_server_cli/prototypes/genai_demo/security/__pycache__/declare_security.cpython-312.pyc +0 -0
- api_logic_server_cli/prototypes/genai_demo/ui/admin/admin.yaml +86 -53
- api_logic_server_cli/prototypes/manager/.vscode/ApiLogicServer.code-workspace +2 -2
- api_logic_server_cli/prototypes/manager/.vscode/launch.json +21 -21
- api_logic_server_cli/prototypes/manager/README.md +1 -1
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo.prompt +4 -1
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo.response_example +15 -8
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_informal.prompt +3 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/time_tracking_billing/002_create_db_models.prompt +3 -132
- api_logic_server_cli/prototypes/manager/system/genai/examples/time_tracking_billing/Invoice Made Ready.png +0 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/time_tracking_billing/readme.md +59 -6
- api_logic_server_cli/prototypes/manager/system/genai/learning_requests/logic_bank_api.prompt +22 -1
- api_logic_server_cli/prototypes/nw/logic/declare_logic.py +1 -1
- 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/create_from_model/safrs-react-admin-npm-build/build-0106.txt +0 -1
- api_logic_server_cli/create_from_model/safrs-react-admin-npm-build/static/js/main.bfe80d1d.js +0 -3
- api_logic_server_cli/prototypes/genai_demo/database/chatgpt/__pycache__/copilot_models.cpython-312.pyc +0 -0
- api_logic_server_cli/prototypes/genai_demo/database/chatgpt/__pycache__/sample_ai_models.cpython-312.pyc +0 -0
- api_logic_server_cli/prototypes/genai_demo/database/chatgpt/sample_ai.chatgpt +0 -16
- api_logic_server_cli/prototypes/genai_demo/database/chatgpt/sample_ai.sql +0 -66
- api_logic_server_cli/prototypes/genai_demo/database/chatgpt/sample_ai.sqlite +0 -0
- api_logic_server_cli/prototypes/genai_demo/database/chatgpt/sample_ai_items.sqlite +0 -0
- api_logic_server_cli/prototypes/genai_demo/database/chatgpt/sample_ai_models.py +0 -156
- api_logic_server_cli/prototypes/genai_demo/database/chatgpt/sample_ai_models.sqlite +0 -0
- api_logic_server_cli/prototypes/genai_demo/logic/cocktail-napkin.jpg +0 -0
- {ApiLogicServer-14.3.0.dist-info → ApiLogicServer-14.3.11.dist-info}/LICENSE +0 -0
- {ApiLogicServer-14.3.0.dist-info → ApiLogicServer-14.3.11.dist-info}/WHEEL +0 -0
- {ApiLogicServer-14.3.0.dist-info → ApiLogicServer-14.3.11.dist-info}/entry_points.txt +0 -0
- {ApiLogicServer-14.3.0.dist-info → ApiLogicServer-14.3.11.dist-info}/top_level.txt +0 -0
- /api_logic_server_cli/create_from_model/safrs-react-admin-npm-build/static/js/{main.bfe80d1d.js.LICENSE.txt → main.7c8c0e37.js.LICENSE.txt} +0 -0
|
Binary file
|
|
@@ -185,6 +185,10 @@ class GenAI(object):
|
|
|
185
185
|
with open(genai_demo_response_path, 'r') as response_file:
|
|
186
186
|
response_dict = json.load(response_file)
|
|
187
187
|
log.debug(f'.. used standard genai_demo response: {genai_demo_response_path}')
|
|
188
|
+
genai_demo_response_path = Path('system/genai/temp/response.json')
|
|
189
|
+
with open(genai_demo_response_path, 'w') as response_file:
|
|
190
|
+
json.dump(response_dict, response_file, indent=4)
|
|
191
|
+
|
|
188
192
|
else: # for retry from corrected response... eg system/genai/temp/chatgpt_retry.response
|
|
189
193
|
self.resolved_model = "(n/a: model not used for repaired response)"
|
|
190
194
|
log.debug(f'\nUsing [corrected] response from: {self.project.genai_repaired_response}')
|
|
@@ -311,8 +315,14 @@ class GenAI(object):
|
|
|
311
315
|
log.debug(f'.. from file: {self.project.genai_using}')
|
|
312
316
|
raw_prompt = file.read()
|
|
313
317
|
prompt = self.get_prompt__with_inserts(raw_prompt=raw_prompt, for_iteration=False) # insert db-specific logic
|
|
314
|
-
self.logic_enabled =
|
|
315
|
-
if
|
|
318
|
+
self.logic_enabled = True
|
|
319
|
+
if os.environ.get("APILOGICPROJECT_LOGIC_ENABLED") is not None and \
|
|
320
|
+
os.environ.get("APILOGICPROJECT_LOGIC_ENABLED") == 'False':
|
|
321
|
+
self.logic_enabled = False
|
|
322
|
+
log.info("*** Initial Logic Disabled: {self.logic_enabled}")
|
|
323
|
+
else:
|
|
324
|
+
log.debug(f'.. Initial Logic enabled: {self.logic_enabled}')
|
|
325
|
+
if self.logic_enabled == True or ('LogicBank' in prompt and K_LogicBankOff not in prompt): # if prompt has logic, we need to insert the training
|
|
316
326
|
prompt_messages.extend( self.get_prompt_learning_requests())
|
|
317
327
|
self.logic_enabled = True
|
|
318
328
|
if prompt.startswith('You are a '): # if it's a preset, we need to insert the prompt
|
|
@@ -325,7 +335,7 @@ class GenAI(object):
|
|
|
325
335
|
active_rules_json_path = Path(self.project.genai_using).joinpath('logic/active_rules.json')
|
|
326
336
|
# assert active_rules_json_path.exists(), f"Missing active_rules.json: {active_rules_json_path}"
|
|
327
337
|
if not active_rules_json_path.exists():
|
|
328
|
-
log.info("*** Internal error: --active_rules specified, but no --using/logic/active_rules.json found - try to
|
|
338
|
+
log.info("*** Internal error: --active_rules specified, but no --using/logic/active_rules.json found - try to proceed")
|
|
329
339
|
else:
|
|
330
340
|
with open(active_rules_json_path, 'r') as file:
|
|
331
341
|
active_rules_str = file.read()
|
|
@@ -193,6 +193,8 @@ def get_code_update_logic_file(rule_list: List[DotMap], logic_file_path: Path =
|
|
|
193
193
|
pass
|
|
194
194
|
elif 'Rule.constraint' in each_line:
|
|
195
195
|
pass
|
|
196
|
+
elif 'Rule.after_flush_row_event' in each_line:
|
|
197
|
+
pass
|
|
196
198
|
elif 'Rule.allocate' in each_line:
|
|
197
199
|
pass
|
|
198
200
|
elif 'Rule.calculate' in each_line:
|
api_logic_server_cli/manager.py
CHANGED
|
@@ -11,14 +11,14 @@ from pathlib import Path
|
|
|
11
11
|
import api_logic_server_cli.api_logic_server as PR
|
|
12
12
|
|
|
13
13
|
def create_manager(clean: bool, open_with: str, api_logic_server_path: Path,
|
|
14
|
-
volume: str = "", open_manager: bool = True):
|
|
14
|
+
volume: str = "", open_manager: bool = True, samples: bool = True):
|
|
15
15
|
"""Implements als start to create manager - called from api_logic_server_cli/cli.py
|
|
16
16
|
|
|
17
17
|
create Manager at os.getcwd(), including:
|
|
18
18
|
|
|
19
19
|
1. .vscode, readme
|
|
20
20
|
2. System folder (GenAI sample prompts / responses, others TBD)
|
|
21
|
-
3. pre-created samples
|
|
21
|
+
3. pre-created samples (optional)
|
|
22
22
|
|
|
23
23
|
Example, from CLI in directory containing a `venv` (see https://apilogicserver.github.io/Docs/Manager/):
|
|
24
24
|
als start
|
|
@@ -122,21 +122,25 @@ def create_manager(clean: bool, open_with: str, api_logic_server_path: Path,
|
|
|
122
122
|
except: # do NOT fail
|
|
123
123
|
pass # just fall back to using the pip-installed version
|
|
124
124
|
|
|
125
|
-
if
|
|
126
|
-
|
|
125
|
+
if not samples:
|
|
126
|
+
shutil.rmtree(to_dir.joinpath(f'{docker_volume}system/app_model_editor'))
|
|
127
|
+
shutil.rmtree(to_dir.joinpath(f'{docker_volume}system/genai/examples/genai_demo/wg_dev_merge'))
|
|
127
128
|
else:
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
129
|
+
if project.is_docker:
|
|
130
|
+
log.debug(f" tutorial not created for docker\n\n")
|
|
131
|
+
else:
|
|
132
|
+
tutorial_project = PR.ProjectRun(command="tutorial",
|
|
133
|
+
project_name='./samples',
|
|
134
|
+
db_url="",
|
|
135
|
+
execute=False,
|
|
136
|
+
open_with="NO_AUTO_OPEN"
|
|
137
|
+
)
|
|
138
|
+
tutorial_project = tutorial_project.tutorial(msg="Creating:") ##, create='tutorial')
|
|
139
|
+
|
|
140
|
+
samples_project = PR.ProjectRun(command= "create", project_name=f'{docker_volume}samples/nw_sample', db_url='nw+', open_with="NO_AUTO_OPEN")
|
|
141
|
+
log.setLevel(mgr_save_level)
|
|
142
|
+
log.disabled = False # todo why was it reset?
|
|
143
|
+
samples_project = PR.ProjectRun(command= "create", project_name=f'{docker_volume}samples/nw_sample_nocust', db_url='nw', open_with="NO_AUTO_OPEN")
|
|
140
144
|
log.info('')
|
|
141
145
|
log.setLevel(mgr_save_level)
|
|
142
146
|
log.disabled = False
|
|
@@ -153,18 +153,24 @@ def fixup_sort(clz, data):
|
|
|
153
153
|
continue
|
|
154
154
|
return sort
|
|
155
155
|
def fixup_data(data, sqltypes):
|
|
156
|
+
new_data = None
|
|
156
157
|
if data:
|
|
158
|
+
new_data = {}
|
|
157
159
|
for key, value in data.items():
|
|
160
|
+
new_data[key] = value
|
|
158
161
|
if sqltypes and key in sqltypes and isinstance(value, str):
|
|
159
162
|
if sqltypes[key] in [-5,2,4,5,-6]: #BIGINT, TINYINT, INT, SMALLINT, INTEGER
|
|
160
|
-
|
|
163
|
+
if new_data[key].isdigit():
|
|
164
|
+
new_data[key] = int(value)
|
|
165
|
+
else:
|
|
166
|
+
del new_data[key]
|
|
161
167
|
elif sqltypes[key] in [6]: #DECIMAL
|
|
162
|
-
|
|
168
|
+
new_data[key] = Decimal(value)
|
|
163
169
|
if sqltypes and key in sqltypes and sqltypes[key] in [91,93] and isinstance(value, int): #DATE, TIMESTAMP
|
|
164
170
|
from datetime import datetime
|
|
165
171
|
fmt = "%Y-%m-%d" if sqltypes[key] == 91 else "%Y-%m-%d %H:%M:%S"
|
|
166
|
-
|
|
167
|
-
return
|
|
172
|
+
new_data[key] = datetime.fromtimestamp(value / 1000) #.strftime(fmt)
|
|
173
|
+
return new_data
|
|
168
174
|
|
|
169
175
|
def _parseFilter(filter: dict, sqltypes: any):
|
|
170
176
|
# {filter":{"@basic_expression":{"lop":"BALANCE","op":"<=","rop":35000}}
|
|
@@ -40,11 +40,16 @@
|
|
|
40
40
|
# whether to invoke dbinit befoce connecting...
|
|
41
41
|
# APILOGICSERVER_ORACLE_THICK=~/Downloads/instantclient_19_16
|
|
42
42
|
|
|
43
|
+
# enables aggregate defaulting, and defaulting for all numerics and strings
|
|
44
|
+
# used to initialize rows prior to logic, to avoid excessive None testing
|
|
45
|
+
# AGGREGATE_DEFAULTS=True
|
|
46
|
+
# ALL_DEFAULTS=True
|
|
47
|
+
|
|
43
48
|
APILOGICPROJECT_VERBOSE=True
|
|
44
49
|
|
|
45
50
|
# APILOGICPROJECT_LOG_CONFIG=
|
|
46
|
-
|
|
51
|
+
# name of log.yml file (eg, config/logging_prod.yml)
|
|
47
52
|
|
|
48
53
|
# APILOGICPROJECT_STOP_OK=FALSE
|
|
49
|
-
|
|
54
|
+
# dev only - enable stop url: http://localhost:5656/stop?msg=reason
|
|
50
55
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
|
|
3
|
-
Version
|
|
3
|
+
Version 2.1
|
|
4
4
|
|
|
5
5
|
Invoked at server start (api_logic_server_run.py -> config/setup.py)
|
|
6
6
|
|
|
@@ -47,8 +47,23 @@ def kafka_producer():
|
|
|
47
47
|
producer = Producer(conf)
|
|
48
48
|
logger.debug(f'\nKafka producer connected')
|
|
49
49
|
|
|
50
|
+
from sqlalchemy.inspection import inspect
|
|
50
51
|
|
|
51
|
-
def
|
|
52
|
+
def get_primary_key(logic_row: LogicRow):
|
|
53
|
+
""" Return primary key for row, if it exists
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
logic_row (LogicRow): The SQLAlchemy row object
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
dict: A dictionary with primary key column names and their values
|
|
60
|
+
"""
|
|
61
|
+
primary_key_columns = inspect(logic_row.row).mapper.primary_key
|
|
62
|
+
primary_key = {column.name: getattr(logic_row.row, column.name) for column in primary_key_columns}
|
|
63
|
+
return primary_key
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def send_kafka_message(kafka_topic: str, kafka_key: str = None, msg: str="", json_root_name: str = "",
|
|
52
67
|
logic_row: LogicRow = None, row_dict_mapper: RowDictMapper = None, payload: dict = None):
|
|
53
68
|
""" Send Kafka message regarding logic_row, mapped by row_dict_mapper
|
|
54
69
|
|
|
@@ -80,18 +95,26 @@ def send_kafka_message(kafka_topic: str, kafka_key: str, msg: str="", json_root_
|
|
|
80
95
|
else:
|
|
81
96
|
root_name = logic_row.name
|
|
82
97
|
|
|
98
|
+
if kafka_key is None:
|
|
99
|
+
kafka_key = get_primary_key(logic_row)
|
|
100
|
+
|
|
101
|
+
log_msg = msg if msg != "" else f"Sending {root_name} to Kafka topic '{kafka_topic}'"
|
|
102
|
+
|
|
83
103
|
json_string = jsonify({f'{root_name}': row_obj_dict}).data.decode('utf-8')
|
|
84
|
-
log_msg =
|
|
104
|
+
log_msg = log_msg
|
|
85
105
|
if producer: # enabled in config/config.py?
|
|
86
106
|
try:
|
|
87
107
|
producer.produce(value=json_string, topic="order_shipping", key=kafka_key)
|
|
88
108
|
if logic_row:
|
|
89
|
-
logic_row.log(
|
|
109
|
+
logic_row.log(log_msg)
|
|
90
110
|
except KafkaException as ke:
|
|
91
111
|
logger.error("kafka_producer#send_kafka_message error: {ke}")
|
|
92
112
|
else:
|
|
93
|
-
log_msg += "
|
|
113
|
+
log_msg += " [Note: **Kafka not enabled** ]"
|
|
94
114
|
if logic_row is not None:
|
|
95
|
-
logic_row.log(f'
|
|
96
|
-
|
|
97
|
-
|
|
115
|
+
logic_row.log(f'{log_msg}')
|
|
116
|
+
logger.debug(f'\n\n{log_msg}\n{json_string}')
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def send_row_to_kafka(row: object, old_row: object, logic_row: LogicRow, with_args: dict):
|
|
120
|
+
send_kafka_message(logic_row=logic_row, kafka_topic=with_args["topic"])
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from database import models
|
|
2
2
|
from flask import request, jsonify
|
|
3
3
|
import sqlalchemy as sqlalchemy
|
|
4
|
-
from sqlalchemy import Column
|
|
4
|
+
from sqlalchemy import Column, inspect
|
|
5
5
|
from sqlalchemy.ext.declarative import declarative_base
|
|
6
6
|
from flask_sqlalchemy.model import DefaultMeta
|
|
7
7
|
from sqlalchemy.ext.hybrid import hybrid_property
|
|
@@ -276,8 +276,7 @@ class RowDictMapper():
|
|
|
276
276
|
parent_lookup_list.append(self.parent_lookups)
|
|
277
277
|
for each_parent_lookup in parent_lookup_list:
|
|
278
278
|
self._parent_lookup_from_child(child_row_dict = row_dict,
|
|
279
|
-
|
|
280
|
-
parent_class = each_parent_lookup[0],
|
|
279
|
+
parent_lookup = each_parent_lookup,
|
|
281
280
|
child_row = sql_alchemy_row,
|
|
282
281
|
session = session)
|
|
283
282
|
|
|
@@ -354,27 +353,31 @@ class RowDictMapper():
|
|
|
354
353
|
|
|
355
354
|
def _parent_lookup_from_child(self, child_row_dict: dict, child_row: object,
|
|
356
355
|
session: object,
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
""" Used from child -- parent_lookups
|
|
356
|
+
parent_lookup: tuple[DefaultMeta, list[tuple[Column, str]]]):
|
|
357
|
+
""" Used from child -- parent_lookups (e,g, B2B Product)
|
|
360
358
|
|
|
361
359
|
Args:
|
|
362
|
-
child_row_dict (dict):
|
|
363
|
-
child_row (object):
|
|
364
|
-
|
|
365
|
-
|
|
360
|
+
child_row_dict (dict): the incoming payload
|
|
361
|
+
child_row (object): row
|
|
362
|
+
parent_lookup (tuple[DefaultMeta, list[tuple[Column, str]]]): parent class, list of attrs/json keys
|
|
363
|
+
session (object): SqlAlchemy session
|
|
364
|
+
|
|
365
|
+
Example lookup_fields (genai_demo/OrderB2B.py):
|
|
366
|
+
parent_lookup = ( models.Customer, [(models.Customer.name, 'Account')] )
|
|
367
|
+
parent_class: parent_lookups[0] Customer)
|
|
368
|
+
lookup_fields: parent_lookups[1] [(models.Customer.name, 'Account')]
|
|
366
369
|
|
|
367
370
|
Raises:
|
|
368
|
-
ValueError:
|
|
369
|
-
ValueError: _description_
|
|
371
|
+
ValueError: eg, missing parent
|
|
370
372
|
"""
|
|
371
|
-
|
|
373
|
+
parent_class = parent_lookup[0]
|
|
374
|
+
lookup_fields = parent_lookup[1]
|
|
372
375
|
query = session.query(parent_class)
|
|
373
376
|
|
|
374
377
|
if parent_class.__name__ in ['Product', 'Customer']:
|
|
375
378
|
logging.debug(f'_parent_lookup_from_child {parent_class.__name__}' )
|
|
376
|
-
for each_lookup_param_field in lookup_fields:
|
|
377
|
-
attr_name = each_lookup_param_field
|
|
379
|
+
for each_lookup_param_field in lookup_fields: # e.g, (models.Customer.name, 'Account')
|
|
380
|
+
attr_name = each_lookup_param_field # <col_def> <filter-val>
|
|
378
381
|
if isinstance(each_lookup_param_field, tuple):
|
|
379
382
|
col_def = each_lookup_param_field[0]
|
|
380
383
|
attr_name = each_lookup_param_field[1]
|
|
@@ -394,6 +397,20 @@ class RowDictMapper():
|
|
|
394
397
|
raise ValueError('Lookup failed: missing parent', child_row, parent_class.__name__, str(child_row_dict))
|
|
395
398
|
|
|
396
399
|
parent_row = parent_rows[0]
|
|
397
|
-
|
|
400
|
+
|
|
401
|
+
# find parent accessor - usually parent_class.__name__, unless fk is lower case (B2bOrders)
|
|
402
|
+
mapper = inspect(child_row).mapper
|
|
403
|
+
parent_accessor = None
|
|
404
|
+
for each_attribute in mapper.attrs: # find parent accessors
|
|
405
|
+
if isinstance(each_attribute, sqlalchemy.orm.relationships.RelationshipProperty):
|
|
406
|
+
if each_attribute.argument == parent_class.__name__:
|
|
407
|
+
if parent_accessor is None:
|
|
408
|
+
parent_accessor = each_attribute.key
|
|
409
|
+
else:
|
|
410
|
+
raise ValueError(f'Parent accessor not unique: {parent_accessor}') # TODO - multiple parents
|
|
411
|
+
if parent_accessor is None:
|
|
412
|
+
raise ValueError(f'Parent accessor not found: {parent_class.__name__}')
|
|
413
|
+
|
|
414
|
+
setattr(child_row, parent_accessor, parent_row)
|
|
398
415
|
|
|
399
416
|
return
|
|
@@ -8,6 +8,7 @@ import database.models as models
|
|
|
8
8
|
import api.system.opt_locking.opt_locking as opt_locking
|
|
9
9
|
from security.system.authorization import Grant, Security
|
|
10
10
|
from logic.load_verify_rules import load_verify_rules
|
|
11
|
+
import integration.kafka.kafka_producer as kafka_producer
|
|
11
12
|
import logging
|
|
12
13
|
|
|
13
14
|
app_logger = logging.getLogger(__name__)
|
|
@@ -26,6 +26,7 @@ declare_logic_message = "ALERT: *** No Rules Yet ***" # printed in api_logic_s
|
|
|
26
26
|
rule_import_template = """
|
|
27
27
|
from logic_bank.logic_bank import Rule
|
|
28
28
|
from database.models import *
|
|
29
|
+
import integration.kafka.kafka_producer as kafka_producer
|
|
29
30
|
|
|
30
31
|
def init_rule():
|
|
31
32
|
{rule_code}
|
|
@@ -213,4 +214,4 @@ def load_verify_rules():
|
|
|
213
214
|
app_logger.warning(f"{Fore.RED}Failed to load active exported rules: {exc}{Style.RESET_ALL}")
|
|
214
215
|
|
|
215
216
|
root_logger.removeHandler(file_handler)
|
|
216
|
-
|
|
217
|
+
|
|
@@ -5,8 +5,8 @@ from flask import request, jsonify
|
|
|
5
5
|
from safrs import jsonapi_rpc
|
|
6
6
|
from database import models
|
|
7
7
|
import integration.system.RowDictMapper as row_dict_mapper
|
|
8
|
-
from integration.row_dict_maps.OrderShipping import OrderShipping
|
|
9
|
-
from integration.row_dict_maps.OrderB2B import OrderB2B
|
|
8
|
+
# from integration.row_dict_maps.OrderShipping import OrderShipping
|
|
9
|
+
from integration.row_dict_maps.OrderB2B import OrderB2B # TODO - how to drive; B2B...
|
|
10
10
|
|
|
11
11
|
# called by api_logic_server_run.py, to customize api (new end points, services).
|
|
12
12
|
# separate from expose_api_models.py, to simplify merge if project recreated
|
|
@@ -140,15 +140,13 @@ class ServicesEndPoint(safrs.JABase):
|
|
|
140
140
|
""" # yaml creates Swagger description
|
|
141
141
|
args :
|
|
142
142
|
order:
|
|
143
|
-
Account: "
|
|
143
|
+
Account: "Alice"
|
|
144
144
|
Notes: "Please Rush"
|
|
145
145
|
Items :
|
|
146
|
-
- ProductName: "
|
|
146
|
+
- ProductName: "Product 1"
|
|
147
147
|
QuantityOrdered: 1
|
|
148
|
-
- ProductName: "
|
|
148
|
+
- ProductName: "Product 2"
|
|
149
149
|
QuantityOrdered: 2
|
|
150
|
-
- ProductName: "Green Apples"
|
|
151
|
-
QuantityOrdered: 3
|
|
152
150
|
---
|
|
153
151
|
|
|
154
152
|
Note attribute alias, Lookup automation in OrderB2B
|
|
@@ -159,19 +157,19 @@ class ServicesEndPoint(safrs.JABase):
|
|
|
159
157
|
$(venv) ApiLogicServer login --user=admin --password=p
|
|
160
158
|
$(venv) ApiLogicServer curl "'POST' 'http://localhost:5656/api/ServicesEndPoint/OrderB2B'" --data '
|
|
161
159
|
{"meta": {"args": {"order": {
|
|
162
|
-
"Account": "
|
|
160
|
+
"Account": "Alice",
|
|
163
161
|
"Notes": "Please Rush",
|
|
164
162
|
"Items": [
|
|
165
163
|
{
|
|
166
|
-
"ProductName": "
|
|
164
|
+
"ProductName": "Product 1",
|
|
167
165
|
"QuantityOrdered": 1
|
|
168
166
|
},
|
|
169
167
|
{
|
|
170
|
-
"ProductName": "
|
|
168
|
+
"ProductName": "Product 2",
|
|
171
169
|
"QuantityOrdered": 2
|
|
172
170
|
},
|
|
173
171
|
{
|
|
174
|
-
"ProductName": "
|
|
172
|
+
"ProductName": "Green Apples",
|
|
175
173
|
"QuantityOrdered": 2
|
|
176
174
|
}
|
|
177
175
|
]
|
|
Binary file
|
|
Binary file
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
|
-
from sqlalchemy import
|
|
2
|
+
from sqlalchemy import DECIMAL, DateTime # API Logic Server GenAI assist
|
|
3
|
+
from sqlalchemy import Column, Date, ForeignKey, Integer, Numeric, String, Boolean
|
|
3
4
|
from sqlalchemy.orm import relationship
|
|
4
5
|
from sqlalchemy.ext.declarative import declarative_base
|
|
5
6
|
|
|
@@ -9,13 +10,13 @@ from sqlalchemy.ext.declarative import declarative_base
|
|
|
9
10
|
# Alter this file per your database maintenance policy
|
|
10
11
|
# See https://apilogicserver.github.io/Docs/Project-Rebuild/#rebuilding
|
|
11
12
|
#
|
|
12
|
-
# Created:
|
|
13
|
-
# Database: sqlite:////Users/val/dev/ApiLogicServer/ApiLogicServer-dev/
|
|
13
|
+
# Created: January 31, 2025 17:52:29
|
|
14
|
+
# Database: sqlite:////Users/val/dev/ApiLogicServer/ApiLogicServer-dev/build_and_test/ApiLogicServer/genai_demo/database/db.sqlite
|
|
14
15
|
# Dialect: sqlite
|
|
15
16
|
#
|
|
16
17
|
# mypy: ignore-errors
|
|
17
18
|
########################################################################################################################
|
|
18
|
-
|
|
19
|
+
|
|
19
20
|
from database.system.SAFRSBaseX import SAFRSBaseX, TestBase
|
|
20
21
|
from flask_login import UserMixin
|
|
21
22
|
import safrs, flask_sqlalchemy, os
|
|
@@ -42,72 +43,81 @@ else:
|
|
|
42
43
|
print('*** Models.py Using TestBase ***')
|
|
43
44
|
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
|
|
47
|
+
class Customer(Base): # type: ignore
|
|
48
|
+
"""
|
|
49
|
+
description: Customer table with unique name, balance and credit_limit.
|
|
50
|
+
"""
|
|
51
|
+
__tablename__ = 'customer'
|
|
47
52
|
_s_collection_name = 'Customer' # type: ignore
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
Balance : DECIMAL = Column(DECIMAL(10, 2))
|
|
54
|
-
CreditLimit : DECIMAL = Column(DECIMAL(10, 2), nullable=False)
|
|
54
|
+
id = Column(Integer, primary_key=True)
|
|
55
|
+
name = Column(String(255), unique=True)
|
|
56
|
+
balance = Column(Numeric)
|
|
57
|
+
credit_limit = Column(Numeric)
|
|
55
58
|
|
|
56
59
|
# parent relationships (access parent)
|
|
57
60
|
|
|
58
61
|
# child relationships (access children)
|
|
59
|
-
OrderList : Mapped[List["Order"]] = relationship(back_populates="
|
|
62
|
+
OrderList : Mapped[List["Order"]] = relationship(back_populates="customer")
|
|
60
63
|
|
|
61
64
|
|
|
62
|
-
class Product(Base):
|
|
63
|
-
__tablename__ = 'Products'
|
|
64
|
-
_s_collection_name = 'Product' # type: ignore
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
class Product(Base): # type: ignore
|
|
67
|
+
"""
|
|
68
|
+
description: Product table with unit_price used for copying to Item.
|
|
69
|
+
"""
|
|
70
|
+
__tablename__ = 'product'
|
|
71
|
+
_s_collection_name = 'Product' # type: ignore
|
|
70
72
|
|
|
73
|
+
id = Column(Integer, primary_key=True)
|
|
74
|
+
name = Column(String(255))
|
|
75
|
+
unit_price = Column(Numeric)
|
|
76
|
+
carbon_neutral = Column(Boolean)
|
|
71
77
|
# parent relationships (access parent)
|
|
72
78
|
|
|
73
79
|
# child relationships (access children)
|
|
74
|
-
ItemList : Mapped[List["Item"]] = relationship(back_populates="
|
|
80
|
+
ItemList : Mapped[List["Item"]] = relationship(back_populates="product")
|
|
75
81
|
|
|
76
82
|
|
|
77
83
|
|
|
78
|
-
class Order(Base):
|
|
79
|
-
|
|
84
|
+
class Order(Base): # type: ignore
|
|
85
|
+
"""
|
|
86
|
+
description: Order table with a foreign key to Customer, a notes field, date_shipped and derived amount_total.
|
|
87
|
+
"""
|
|
88
|
+
__tablename__ = 'order'
|
|
80
89
|
_s_collection_name = 'Order' # type: ignore
|
|
81
90
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
AmountTotal : DECIMAL = Column(DECIMAL(10, 2))
|
|
91
|
+
id = Column(Integer, primary_key=True)
|
|
92
|
+
customer_id = Column(ForeignKey('customer.id'))
|
|
93
|
+
date_shipped = Column(Date)
|
|
94
|
+
notes = Column(String(255))
|
|
95
|
+
amount_total = Column(Numeric)
|
|
88
96
|
|
|
89
97
|
# parent relationships (access parent)
|
|
90
|
-
|
|
98
|
+
customer : Mapped["Customer"] = relationship(back_populates=("OrderList"))
|
|
91
99
|
|
|
92
100
|
# child relationships (access children)
|
|
93
|
-
ItemList : Mapped[List["Item"]] = relationship(back_populates="
|
|
101
|
+
ItemList : Mapped[List["Item"]] = relationship(back_populates="order")
|
|
94
102
|
|
|
95
103
|
|
|
96
104
|
|
|
97
|
-
class Item(Base):
|
|
98
|
-
|
|
105
|
+
class Item(Base): # type: ignore
|
|
106
|
+
"""
|
|
107
|
+
description: Item table with non-null quantity, derived amount and unit_price copied from Product.
|
|
108
|
+
"""
|
|
109
|
+
__tablename__ = 'item'
|
|
99
110
|
_s_collection_name = 'Item' # type: ignore
|
|
100
111
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
112
|
+
id = Column(Integer, primary_key=True)
|
|
113
|
+
order_id = Column(ForeignKey('order.id'))
|
|
114
|
+
product_id = Column(ForeignKey('product.id'))
|
|
115
|
+
quantity = Column(Integer, nullable=False)
|
|
116
|
+
unit_price = Column(Numeric)
|
|
117
|
+
amount = Column(Numeric)
|
|
107
118
|
|
|
108
119
|
# parent relationships (access parent)
|
|
109
|
-
|
|
110
|
-
|
|
120
|
+
order : Mapped["Order"] = relationship(back_populates=("ItemList"))
|
|
121
|
+
product : Mapped["Product"] = relationship(back_populates=("ItemList"))
|
|
111
122
|
|
|
112
123
|
# child relationships (access children)
|
|
113
|
-
|
|
@@ -19,15 +19,13 @@ class OrderB2B(RowDictMapper):
|
|
|
19
19
|
order = super(OrderB2B, self).__init__(
|
|
20
20
|
model_class=models.Order
|
|
21
21
|
, alias = "order"
|
|
22
|
-
, fields = [(models.Order.Notes)]
|
|
23
|
-
, parent_lookups = [( models.Customer,
|
|
24
|
-
[(models.Customer.CustomerName, 'Account')]
|
|
25
|
-
)]
|
|
22
|
+
, fields = [(models.Order.notes, "Notes")]
|
|
23
|
+
, parent_lookups = [( models.Customer, [(models.Customer.name, 'Account')] )]
|
|
26
24
|
, related = [
|
|
27
25
|
(RowDictMapper(model_class=models.Item
|
|
28
26
|
, alias="Items"
|
|
29
|
-
, fields = [(models.Item.
|
|
30
|
-
, parent_lookups = [( models.Product, [models.Product.ProductName] )]
|
|
27
|
+
, fields = [(models.Item.quantity, "QuantityOrdered")]
|
|
28
|
+
, parent_lookups = [( models.Product, [(models.Product.name, 'ProductName')] )]
|
|
31
29
|
)
|
|
32
30
|
)
|
|
33
31
|
]
|
|
Binary file
|
|
Binary file
|