ApiLogicServer 14.3.25__py3-none-any.whl → 14.4.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.
- api_logic_server_cli/api_logic_server.py +4 -14
- api_logic_server_cli/api_logic_server_info.yaml +3 -3
- api_logic_server_cli/cli.py +16 -7
- api_logic_server_cli/create_from_model/__pycache__/create_db_from_model.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/__pycache__/ont_build.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/__pycache__/ont_create.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/create_db_from_model.py +2 -0
- api_logic_server_cli/create_from_model/ont_build.py +19 -14
- api_logic_server_cli/create_from_model/ont_create.py +5 -5
- api_logic_server_cli/create_from_model/safrs-react-admin-npm-build/static/.DS_Store +0 -0
- api_logic_server_cli/database/nw-gold-fix.sql +62 -0
- api_logic_server_cli/database/nw-gold.sqlite +0 -0
- api_logic_server_cli/{prototypes/manager/webgenai → fragments}/docker-compose.yml +1 -1
- api_logic_server_cli/genai/genai.py +42 -11
- api_logic_server_cli/genai/genai_graphics.py +252 -38
- api_logic_server_cli/genai/genai_svcs.py +20 -12
- api_logic_server_cli/manager.py +19 -10
- api_logic_server_cli/prototypes/.DS_Store +0 -0
- api_logic_server_cli/prototypes/base/.DS_Store +0 -0
- api_logic_server_cli/prototypes/base/.vscode/launch.json +19 -0
- api_logic_server_cli/prototypes/base/api/expose_api_models.py +3 -1
- api_logic_server_cli/prototypes/base/api_logic_server_run.py +5 -2
- api_logic_server_cli/prototypes/base/config/activate_logicbank.py +1 -0
- api_logic_server_cli/prototypes/base/config/config.py +57 -14
- api_logic_server_cli/prototypes/base/config/logging.yml +1 -0
- api_logic_server_cli/prototypes/base/config/server_setup.py +33 -1
- api_logic_server_cli/prototypes/base/database/test_data/readme.md +3 -1
- api_logic_server_cli/prototypes/base/devops/docker-standard-image/docker-compose-standard-image.yml +7 -2
- api_logic_server_cli/prototypes/base/docs/training/logic_bank_api.prompt +314 -0
- api_logic_server_cli/prototypes/base/docs/training/logic_example.py +41 -0
- api_logic_server_cli/prototypes/base/integration/kafka/kafka_producer.py +7 -3
- api_logic_server_cli/prototypes/base/integration/system/FlaskKafka.py +5 -1
- api_logic_server_cli/prototypes/base/ui/templates/bar_chart.jinja +64 -0
- api_logic_server_cli/prototypes/genai_demo/ui/admin/admin.yaml +1 -1
- api_logic_server_cli/prototypes/manager/README.md +26 -4
- api_logic_server_cli/prototypes/manager/system/genai/.DS_Store +0 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/.DS_Store +0 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/.DS_Store +0 -0
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo.prompt +0 -10
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo.response_example +32 -10
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_docs_logic/docs/002_create_db_models.prompt +4 -4
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_docs_logic/docs/003_create_db_models.response +77 -47
- api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_informal.prompt +1 -1
- api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/dashboard_services.jinja +83 -0
- api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/graphics_dashboard_WIP.py +34 -0
- api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/{graphics_services.py → graphics_services_api_xxx.py} +0 -9
- api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/graphics_services_db.jinja +46 -0
- api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/graphics_services_db_each_method.jinja +36 -0
- api_logic_server_cli/prototypes/manager/system/genai/prompt_inserts/graphics.prompt +7 -3
- api_logic_server_cli/prototypes/manager/system/genai/prompt_inserts/response_format.prompt +8 -1
- api_logic_server_cli/prototypes/manager/system/install-ApiLogicServer-dev/install-ApiLogicServer-dev.ps1 +100 -0
- api_logic_server_cli/prototypes/manager/system/install-ApiLogicServer-dev/install-ApiLogicServer-dev.sh +116 -0
- api_logic_server_cli/prototypes/manager/system/install-ApiLogicServer-dev/readme.md +7 -0
- api_logic_server_cli/prototypes/manager/system/style-guide.yaml +2 -2
- api_logic_server_cli/prototypes/manager/webgenai/README.md +6 -0
- api_logic_server_cli/prototypes/nw/docs/graphics/count_orders_by_category.prompt +1 -0
- api_logic_server_cli/prototypes/nw/docs/graphics/order_count_by_month.prompt +1 -0
- api_logic_server_cli/prototypes/nw/docs/graphics/request copy.json +892 -0
- api_logic_server_cli/prototypes/nw/docs/graphics/request.json +6 -0
- api_logic_server_cli/prototypes/nw/docs/graphics/response.json +17 -0
- api_logic_server_cli/prototypes/nw/docs/graphics/response.yaml +59 -0
- api_logic_server_cli/prototypes/nw/docs/graphics/sales_by_category.prompt +1 -0
- api_logic_server_cli/prototypes/nw/ui/admin/home.js +5 -4
- api_logic_server_cli/prototypes/nw/ui/app_model_custom.yaml +851 -1082
- api_logic_server_cli/prototypes/nw_no_cust/docs/graphics/count_orders_by_category.prompt +1 -0
- api_logic_server_cli/prototypes/nw_no_cust/docs/graphics/sales_by_employee.prompt +1 -0
- api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/1_langchain_loader.py +19 -0
- api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/2_gpt_mcp_prompt.txt +19 -0
- api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/3_executor_test_agent.py +38 -0
- api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/README.md +17 -0
- api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/resources/curl.txt +4 -0
- api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/resources/nw_swagger_3.yaml +16660 -0
- api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/run_executor.py +23 -0
- api_logic_server_cli/prototypes/ont_app/ontimize_seed/nginx/nginx.conf +2 -2
- api_logic_server_cli/prototypes/ont_app/ontimize_seed/package.json +6 -6
- api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/app/app.config.ts +2 -1
- api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/environments/environment.prod.ts +5 -5
- api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/environments/environment.ts +5 -5
- api_logic_server_cli/prototypes/ont_app/templates/app_config.jinja +1 -1
- api_logic_server_cli/prototypes/ont_app/templates/detail_template.html +1 -1
- api_logic_server_cli/prototypes/ont_app/templates/new_template.html +16 -16
- apilogicserver-14.4.0.dist-info/METADATA +76 -0
- {apilogicserver-14.3.25.dist-info → apilogicserver-14.4.0.dist-info}/RECORD +87 -56
- {apilogicserver-14.3.25.dist-info → apilogicserver-14.4.0.dist-info}/WHEEL +1 -1
- api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/service_template_jsonapi_rpc.jinja +0 -37
- api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/service_template_unused.jinja +0 -38
- apilogicserver-14.3.25.dist-info/METADATA +0 -167
- {apilogicserver-14.3.25.dist-info → apilogicserver-14.4.0.dist-info}/entry_points.txt +0 -0
- {apilogicserver-14.3.25.dist-info → apilogicserver-14.4.0.dist-info}/licenses/LICENSE +0 -0
- {apilogicserver-14.3.25.dist-info → apilogicserver-14.4.0.dist-info}/top_level.txt +0 -0
|
@@ -4,11 +4,12 @@ from pathlib import Path
|
|
|
4
4
|
import os
|
|
5
5
|
import typing
|
|
6
6
|
from dotenv import load_dotenv
|
|
7
|
-
import logging
|
|
7
|
+
import logging, logging.config
|
|
8
8
|
from enum import Enum
|
|
9
9
|
import socket
|
|
10
10
|
import json
|
|
11
11
|
|
|
12
|
+
|
|
12
13
|
'''
|
|
13
14
|
#als: configuration settings
|
|
14
15
|
|
|
@@ -57,6 +58,39 @@ def is_docker() -> bool:
|
|
|
57
58
|
# assert path_result == env_result
|
|
58
59
|
return path_result
|
|
59
60
|
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# ==================================
|
|
64
|
+
# LOGGING SETUP
|
|
65
|
+
# ==================================
|
|
66
|
+
|
|
67
|
+
def logging_setup() -> logging.Logger:
|
|
68
|
+
"""
|
|
69
|
+
Setup Logging
|
|
70
|
+
"""
|
|
71
|
+
import yaml
|
|
72
|
+
global app_logger, debug_value, project_path
|
|
73
|
+
logging_config = f'{project_path}/config/logging.yml'
|
|
74
|
+
if os.getenv('APILOGICPROJECT_LOGGING_CONFIG'):
|
|
75
|
+
logging_config = project_path.joinpath(os.getenv("APILOGICPROJECT_LOGGING_CONFIG"))
|
|
76
|
+
with open(logging_config,'rt') as f: # see also logic/declare_logic.py
|
|
77
|
+
config=yaml.safe_load(f.read())
|
|
78
|
+
f.close()
|
|
79
|
+
logging.config.dictConfig(config) # log levels: notset 0, debug 10, info 20, warn 30, error 40, critical 50
|
|
80
|
+
app_logger = logging.getLogger("api_logic_server_app")
|
|
81
|
+
debug_value = os.getenv('APILOGICPROJECT_DEBUG')
|
|
82
|
+
if debug_value is not None: # > export APILOGICPROJECT_DEBUG=True
|
|
83
|
+
debug_value = debug_value.upper()
|
|
84
|
+
if debug_value.startswith("F") or debug_value.startswith("N"):
|
|
85
|
+
app_logger.setLevel(logging.INFO)
|
|
86
|
+
else:
|
|
87
|
+
app_logger.setLevel(logging.DEBUG)
|
|
88
|
+
app_logger.debug(f'\nDEBUG level set from env\n')
|
|
89
|
+
# app_logger.info(f'\nAPI Logic Project Server Setup ({project_name}) Starting with CLI args: \n.. {args}\n')
|
|
90
|
+
# app_logger.info(f'Created August 03, 2024 09:34:01 at {str(project_path)}\n')
|
|
91
|
+
return app_logger
|
|
92
|
+
|
|
93
|
+
|
|
60
94
|
class Config:
|
|
61
95
|
"""
|
|
62
96
|
|
|
@@ -67,6 +101,8 @@ class Config:
|
|
|
67
101
|
Code should therefore access these ONLY as described in Args, below.
|
|
68
102
|
|
|
69
103
|
"""
|
|
104
|
+
if os.getenv("EXPERIMENT") == '+':
|
|
105
|
+
logging_setup() # set up logging as early as possible so capture critical config logging
|
|
70
106
|
|
|
71
107
|
# Project Creation Defaults (overridden from args, env variables)
|
|
72
108
|
CREATED_API_PREFIX = "/api"
|
|
@@ -171,18 +207,20 @@ class Config:
|
|
|
171
207
|
KAFKA_CONSUMER = None
|
|
172
208
|
KAFKA_CONSUMER_GROUP = None
|
|
173
209
|
KAFKA_SERVER = None
|
|
174
|
-
|
|
175
|
-
if KAFKA_SERVER:
|
|
210
|
+
KAFKA_SERVER = os.getenv('KAFKA_SERVER', None) # 'localhost:9092' # if running locally default
|
|
211
|
+
if KAFKA_SERVER is not None and KAFKA_SERVER != "None" and KAFKA_SERVER != "":
|
|
176
212
|
app_logger.info(f'config.py - KAFKA_SERVER: {KAFKA_SERVER}')
|
|
177
213
|
KAFKA_PRODUCER = os.getenv('KAFKA_PRODUCER',{"bootstrap.servers": f"{KAFKA_SERVER}"}) # , "client.id": "aaa.b.c.d"}'
|
|
178
|
-
KAFKA_CONSUMER_GROUP = os.getenv('KAFKA_CONSUMER_GROUP'
|
|
179
|
-
|
|
214
|
+
KAFKA_CONSUMER_GROUP = os.getenv('KAFKA_CONSUMER_GROUP') #'als-default-group1'
|
|
215
|
+
if KAFKA_CONSUMER_GROUP is not None: # and KAFKA_CONSUMER_GROUP != "None":
|
|
216
|
+
KAFKA_CONSUMER = os.getenv('KAFKA_CONSUMER', {"bootstrap.servers": f"{KAFKA_SERVER}", "group.id": f"{KAFKA_CONSUMER_GROUP}", "enable.auto.commit": "false", "auto.offset.reset": "earliest"})
|
|
180
217
|
else:
|
|
181
218
|
app_logger.info(f'config.py - KAFKA_SERVER: {KAFKA_SERVER} - not set, no kafka producer/consumer')
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
219
|
+
producer_is_empty = "" == KAFKA_PRODUCER
|
|
220
|
+
app_logger.info(f'config.py - KAFKA_PRODUCER: {KAFKA_PRODUCER} (is_empty={producer_is_empty})')
|
|
221
|
+
app_logger.info(f'config.py - KAFKA_CONSUMER: {KAFKA_CONSUMER}')
|
|
222
|
+
app_logger.info(f'config.py - KAFKA_CONSUMER_GROUP: {KAFKA_CONSUMER_GROUP}')
|
|
223
|
+
app_logger.info(f'config.py - KAFKA_SERVER: {KAFKA_SERVER}')
|
|
186
224
|
# N8N Webhook Args (for testing)
|
|
187
225
|
# see https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.webhook#path
|
|
188
226
|
wh_scheme = "http"
|
|
@@ -479,10 +517,13 @@ class Args():
|
|
|
479
517
|
def kafka_producer(self) -> dict:
|
|
480
518
|
""" kafka connect string """
|
|
481
519
|
if "KAFKA_PRODUCER" in self.flask_app.config and self.flask_app.config["KAFKA_PRODUCER"] is not None:
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
value
|
|
485
|
-
|
|
520
|
+
if self.flask_app.config["KAFKA_PRODUCER"] is not None:
|
|
521
|
+
value = self.flask_app.config["KAFKA_PRODUCER"]
|
|
522
|
+
if isinstance(value, dict):
|
|
523
|
+
pass # eg, from VSCode Run Config: "APILOGICPROJECT_KAFKA_PRODUCER": "{\"bootstrap.servers\": \"localhost:9092\"}",
|
|
524
|
+
else:
|
|
525
|
+
value = json.loads(self.flask_app.config["KAFKA_PRODUCER"])
|
|
526
|
+
return value
|
|
486
527
|
return None
|
|
487
528
|
|
|
488
529
|
@kafka_producer.setter
|
|
@@ -494,7 +535,9 @@ class Args():
|
|
|
494
535
|
""" kafka enable consumer """
|
|
495
536
|
if "KAFKA_CONSUMER" in self.flask_app.config and self.flask_app.config["KAFKA_CONSUMER"] is not None:
|
|
496
537
|
value = self.flask_app.config["KAFKA_CONSUMER"]
|
|
497
|
-
if
|
|
538
|
+
if isinstance(value, dict):
|
|
539
|
+
pass # eg, from VSCode Run Config: "APILOGICPROJECT_KAFKA_PRODUCER": "{\"bootstrap.servers\": \"localhost:9092\"}",
|
|
540
|
+
else:
|
|
498
541
|
value = json.loads(self.flask_app.config["KAFKA_CONSUMER"])
|
|
499
542
|
return value
|
|
500
543
|
return None
|
|
@@ -9,6 +9,24 @@
|
|
|
9
9
|
#
|
|
10
10
|
###############################################################################
|
|
11
11
|
|
|
12
|
+
"""
|
|
13
|
+
Operation:
|
|
14
|
+
1. api_logic_server_run.py - imports config
|
|
15
|
+
1. captures args
|
|
16
|
+
2. api_logic_server_run.py - imports server_setup
|
|
17
|
+
1. server_setup#logging_setup()
|
|
18
|
+
3. api_logic_server_run.py - server_setup.api_logic_server_setup
|
|
19
|
+
On error, NOT CALLED: constraint_handler or ValidationErrorExt (!)
|
|
20
|
+
|
|
21
|
+
+ Operation:
|
|
22
|
+
1. api_logic_server_run.py - imports config
|
|
23
|
+
1. captures args
|
|
24
|
+
1. config#logging_setup()
|
|
25
|
+
2. api_logic_server_run.py - imports server_setup
|
|
26
|
+
3. api_logic_server_run.py - server_setup.api_logic_server_setup
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
|
|
12
30
|
start_up_message = "normal start"
|
|
13
31
|
|
|
14
32
|
import traceback
|
|
@@ -25,6 +43,8 @@ except:
|
|
|
25
43
|
from flask_sqlalchemy import SQLAlchemy
|
|
26
44
|
import json
|
|
27
45
|
from pathlib import Path
|
|
46
|
+
if os.getenv("EXPERIMENT") == '+':
|
|
47
|
+
import config
|
|
28
48
|
from config.config import Args
|
|
29
49
|
|
|
30
50
|
|
|
@@ -88,6 +108,9 @@ import integration.kafka.kafka_consumer as kafka_consumer
|
|
|
88
108
|
import integration.n8n.n8n_producer as n8n_producer
|
|
89
109
|
|
|
90
110
|
|
|
111
|
+
if os.getenv("EXPERIMENT") == '+':
|
|
112
|
+
app_logger = logging.getLogger("api_logic_server_app")
|
|
113
|
+
|
|
91
114
|
|
|
92
115
|
class SAFRSAPI(_SAFRSAPI):
|
|
93
116
|
"""
|
|
@@ -121,7 +144,7 @@ def get_args(flask_app: Flask) -> Args:
|
|
|
121
144
|
|
|
122
145
|
import config.config as config
|
|
123
146
|
flask_app.config.from_object(config.Config)
|
|
124
|
-
app_logger.debug(f"\nserver_setup - get_args: Config args: \n{args}") #
|
|
147
|
+
app_logger.debug(f"\nserver_setup - get_args: Config args: \n{args}") # # config file (e.g., db uri's)
|
|
125
148
|
|
|
126
149
|
args.get_cli_args(dunder_name=__name__, args=args)
|
|
127
150
|
app_logger.debug(f"\nserver_setup - get_args: CLI args: \n{args}") # api_logic_server_run cl args
|
|
@@ -351,4 +374,13 @@ def api_logic_server_setup(flask_app: Flask, args: Args):
|
|
|
351
374
|
db_logger.setLevel(db_log_level)
|
|
352
375
|
authorization_logger.setLevel(authorization_log_level)
|
|
353
376
|
|
|
377
|
+
if os.getenv('APILOGICPROJECT_DEBUG'): # temp debug since logging in config is not happening
|
|
378
|
+
KAFKA_SERVER = os.getenv('KAFKA_SERVER')
|
|
379
|
+
is_empty = False
|
|
380
|
+
if KAFKA_SERVER is not None:
|
|
381
|
+
is_empty = KAFKA_SERVER == ""
|
|
382
|
+
is_none = KAFKA_SERVER is None
|
|
383
|
+
app_logger.debug(f'\nDEBUG KAFKA_SERVER: [{KAFKA_SERVER}] (is_empty: {is_empty}) (is_none: {is_none}) \n')
|
|
384
|
+
app_logger.debug(f'... Args.instance.kafka_producer: {Args.instance.kafka_producer}\n')
|
|
385
|
+
|
|
354
386
|
|
|
@@ -7,4 +7,6 @@ You can rebuild the test data, using Logic Bank rules for proper derivations, to
|
|
|
7
7
|
als genai-utils --rebuild-test-data
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
You can explore the generated `database/test_data/test_data_code.py` to control test data generation.
|
|
10
|
+
You can explore the generated `database/test_data/test_data_code.py` to control test data generation.
|
|
11
|
+
|
|
12
|
+
If required, you can copy `database/test_data/test_data_preamble.py` to a new file, to rebuild your database (e.g., from an altered model file), and load your own test data (if any).
|
api_logic_server_cli/prototypes/base/devops/docker-standard-image/docker-compose-standard-image.yml
CHANGED
|
@@ -14,8 +14,13 @@ services:
|
|
|
14
14
|
image: apilogicserver/api_logic_server
|
|
15
15
|
container_name: api_logic_project
|
|
16
16
|
environment:
|
|
17
|
-
- APILOGICPROJECT_VERBOSE=
|
|
18
|
-
-
|
|
17
|
+
- APILOGICPROJECT_VERBOSE=false
|
|
18
|
+
- APILOGICPROJECT_SECURITY_ENABLED=false
|
|
19
|
+
- APILOGICPROJECT_KEYCLOAK_REALM=kcals
|
|
20
|
+
# use your IP, below....
|
|
21
|
+
- APILOGICPROJECT_KEYCLOAK_BASE_URL=http://10.0.0.249:8080/realms/kcals
|
|
22
|
+
- APILOGICPROJECT_KEYCLOAK_BASE=http://10.0.0.249:8080
|
|
23
|
+
- APILOGICPROJECT_KEYCLOAK_CLIENT_ID=alsclient
|
|
19
24
|
env_file:
|
|
20
25
|
- ./env.list
|
|
21
26
|
volumes:
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
Here is the simplified API for LogicBank:
|
|
2
|
+
|
|
3
|
+
Translate the user prompt into a series of calls to Rule methods, described here.
|
|
4
|
+
|
|
5
|
+
Do not generate import statements.
|
|
6
|
+
|
|
7
|
+
If you create sum, count or formula LogicBank rules, you MUST create a corresponding column in the data model.
|
|
8
|
+
|
|
9
|
+
Use only the methods provided below.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Rule:
|
|
13
|
+
""" Invoke these functions to declare rules """
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def sum(derive: Column, as_sum_of: any, where: any = None, insert_parent: bool=False):
|
|
17
|
+
"""
|
|
18
|
+
Derive parent column as sum of designated child column, optional where
|
|
19
|
+
|
|
20
|
+
Example
|
|
21
|
+
Prompt
|
|
22
|
+
Customer.Balance = Sum(Order.amount_total where date_shipped is null)
|
|
23
|
+
Response
|
|
24
|
+
Rule.sum(derive=Customer.Balance, as_sum_of=Order.AmountTotal,
|
|
25
|
+
where=lambda row: row.ShippedDate is None)
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
derive: name of parent <class.attribute> being derived
|
|
29
|
+
as_sum_of: name of child <class.attribute> being summed
|
|
30
|
+
where: optional where clause, designates which child rows are summed. All referenced columns must be part of the data model - create columns in the data model as required. Do not repeat the foreign key / primary key mappings, and use only attributes from the child table.
|
|
31
|
+
insert_parent: create parent if it does not exist. Do not use unless directly requested.
|
|
32
|
+
"""
|
|
33
|
+
return Sum(derive, as_sum_of, where, insert_parent)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def count(derive: Column, as_count_of: object, where: any = None, str = "", insert_parent: bool=False):
|
|
38
|
+
"""
|
|
39
|
+
Derive parent column as count of designated child rows
|
|
40
|
+
|
|
41
|
+
Example
|
|
42
|
+
Prompt
|
|
43
|
+
Customer.UnPaidOrders = count(Orders where ShippedDate is None)
|
|
44
|
+
Response
|
|
45
|
+
Rule.count(derive=Customer.UnPaidOrders, as_count_of=Order,
|
|
46
|
+
where=Lambda row: row.ShippedDate is None)
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
derive: name of parent <class.attribute> being derived
|
|
50
|
+
as_count_of: name of child <class> being counted
|
|
51
|
+
where: optional where clause, designates which child rows are counted. All referenced columns must be part of the data model - create columns in the data model as required. Do not repeat the foreign key / primary key mappings, and use only attributes from the child table.
|
|
52
|
+
insert_parent: create parent if it does not exist. Do not use unless directly requested.
|
|
53
|
+
"""
|
|
54
|
+
return Count(derive, as_count_of, where, insert_parent)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def constraint(validate: object,
|
|
59
|
+
calling: Callable = None,
|
|
60
|
+
as_condition: any = None,
|
|
61
|
+
error_msg: str = "(error_msg not provided)",
|
|
62
|
+
error_attributes=None):
|
|
63
|
+
"""
|
|
64
|
+
Constraints declare condition that must be true for all commits
|
|
65
|
+
|
|
66
|
+
Example
|
|
67
|
+
Prompt
|
|
68
|
+
Customer.balance <= credit_limit
|
|
69
|
+
Response
|
|
70
|
+
Rule.constraint(validate=Customer,
|
|
71
|
+
as_condition=lambda row: row.Balance <= row.CreditLimit,
|
|
72
|
+
error_msg="balance ({row.Balance}) exceeds credit ({row.CreditLimit})")
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
validate: name of mapped <class>
|
|
76
|
+
as_condition: lambda, passed row (simple constraints). All referenced columns must be part of the data model - create columns in the data model as required. Also, conditions may not contain sum or count python functions - these must be used to declare additional columns and sum/count rules.
|
|
77
|
+
error_msg: string, with {row.attribute} replacements
|
|
78
|
+
error_attributes: list of attributes
|
|
79
|
+
|
|
80
|
+
"""
|
|
81
|
+
if error_attributes is None:
|
|
82
|
+
error_attributes = []
|
|
83
|
+
return Constraint(validate=validate, as_condition=as_condition,
|
|
84
|
+
error_attributes=error_attributes, error_msg=error_msg)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def formula(derive: Column,
|
|
89
|
+
as_expression: Callable = None,
|
|
90
|
+
no_prune: bool = False):
|
|
91
|
+
"""
|
|
92
|
+
Formulas declare column value, based on current and parent rows
|
|
93
|
+
|
|
94
|
+
Example
|
|
95
|
+
Prompt
|
|
96
|
+
Item.amount = quantity * unit_price
|
|
97
|
+
Response
|
|
98
|
+
Rule.formula(derive=OrderDetail.Amount,
|
|
99
|
+
as_expression=lambda row: row.UnitPrice * row.Quantity)
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
derive: <class.attribute> being derived
|
|
103
|
+
as_expression: lambda, passed row (for syntax checking). All referenced columns must be part of the data model - create columns in the data model as required. Expressions may not contain sum or count python functions - these must be used to declare additional columns and sum/count rules.
|
|
104
|
+
no_prune: disable pruning (rarely used, default False)
|
|
105
|
+
"""
|
|
106
|
+
return Formula(derive=derive,
|
|
107
|
+
as_expression=as_expression,
|
|
108
|
+
no_prune=no_prune)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
def copy(derive: Column, from_parent: any):
|
|
113
|
+
"""
|
|
114
|
+
Copy declares child column copied from parent column.
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
Prompt
|
|
118
|
+
Store the Item.unit_price as a copy from Product.unit_price
|
|
119
|
+
Response
|
|
120
|
+
Rule.copy(derive=OrderDetail.UnitPrice, from_parent=Product.UnitPrice)
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
derive: <class.attribute> being copied into
|
|
124
|
+
from_parent: <parent-class.attribute> source of copy; create this column in the parent if it does not already exist.
|
|
125
|
+
"""
|
|
126
|
+
return Copy(derive=derive, from_parent=from_parent)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@staticmethod
|
|
130
|
+
def after_flush_row_event(on_class: object, calling: Callable = None,
|
|
131
|
+
if_condition: any = None,
|
|
132
|
+
when_condition: any = None,
|
|
133
|
+
with_args: dict = None):
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
Prompt:
|
|
137
|
+
Send the Order to Kafka topic 'order_shipping' if the date_shipped is not None
|
|
138
|
+
Response:
|
|
139
|
+
Rule.after_flush_row_event(on_class=Order, calling=kafka_producer.send_row_to_kafka,
|
|
140
|
+
if_condition=lambda row: row.date_shipped is not None,
|
|
141
|
+
with_args={"topic": "order_shipping"})
|
|
142
|
+
Prompt:
|
|
143
|
+
Send the Product to Kafka topic 'ready_to_ship' if the is_complete is True
|
|
144
|
+
Response:
|
|
145
|
+
Rule.after_flush_row_event(on_class=Product, calling=kafka_producer.send_row_to_kafka,
|
|
146
|
+
if_condition=lambda row: row.is_complete is True,
|
|
147
|
+
with_args={"topic": "ready_to_ship"})
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
Expanded example:
|
|
151
|
+
|
|
152
|
+
Prompt:
|
|
153
|
+
1. Customer.balance <= credit_limit
|
|
154
|
+
2. Customer.balance = Sum(Order.amount_total where date_shipped is null)
|
|
155
|
+
3. Order.amount_total = Sum(Item.amount)
|
|
156
|
+
4. Item.amount = quantity * unit_price
|
|
157
|
+
5. Store the Item.unit_price as a copy from Product.unit_price
|
|
158
|
+
|
|
159
|
+
Response:
|
|
160
|
+
Rule.sum(derive=CustomerAccount.balance, as_sum_of=Order.amount_total, where=lambda row: row.date_shipped is None)
|
|
161
|
+
Rule.sum(derive=Order.amount_total, as_sum_of=Item.amount)
|
|
162
|
+
Rule.formula(derive=Item.amount, as_expression=lambda row: row.quantity * row.unit_price)
|
|
163
|
+
Rule.copy(derive=Item.unit_price, from_parent=Product.unit_price)
|
|
164
|
+
Rule.constraint(validate=CustomerAccount,
|
|
165
|
+
as_condition=lambda row: row.balance <= row.credit_limit,
|
|
166
|
+
error_msg="Customer balance ({row.balance}) exceeds credit limit ({row.credit_limit})")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
Equivalent expanded example using informal syntax:
|
|
170
|
+
|
|
171
|
+
Prompt:
|
|
172
|
+
1. The Customer's balance is less than the credit limit
|
|
173
|
+
2. The Customer's balance is the sum of the Order amount_total where date_shipped is null
|
|
174
|
+
3. The Order's amount_total is the sum of the Item amount
|
|
175
|
+
4. The Item amount is the quantity * unit_price
|
|
176
|
+
5. The Item unit_price is copied from the Product unit_price
|
|
177
|
+
|
|
178
|
+
Response is the same:
|
|
179
|
+
Rule.sum(derive=CustomerAccount.balance, as_sum_of=Order.amount_total, where=lambda row: row.date_shipped is None)
|
|
180
|
+
Rule.sum(derive=Order.amount_total, as_sum_of=Item.amount)
|
|
181
|
+
Rule.formula(derive=Item.amount, as_expression=lambda row: row.quantity * row.unit_price)
|
|
182
|
+
Rule.copy(derive=Item.unit_price, from_parent=Product.unit_price)
|
|
183
|
+
Rule.constraint(validate=CustomerAccount,
|
|
184
|
+
as_condition=lambda row: row.balance <= row.credit_limit,
|
|
185
|
+
error_msg="Customer balance ({row.balance}) exceeds credit limit ({row.credit_limit})")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
Intermediate sum/count values require a new column, with a LogicBank sum/count rule. For example:
|
|
189
|
+
|
|
190
|
+
Prompt:
|
|
191
|
+
The sum of the child value cannot exceed the parent limit
|
|
192
|
+
|
|
193
|
+
Response is to create 2 rules - a derivation and a constraint, as follows:
|
|
194
|
+
First Rule to Create:
|
|
195
|
+
Rule.sum(derive=Parent.value_total, as_sum_of=Child.value)
|
|
196
|
+
And, be sure to create the second Rule:
|
|
197
|
+
Rule.constraint(validate=Parent,
|
|
198
|
+
as_condition=lambda row: row.value_total <= row.limit,
|
|
199
|
+
error_msg="Parent value total ({row.value_total}) exceeds limit ({row.limit})")
|
|
200
|
+
|
|
201
|
+
Intermediate sum/count values also work for counts. For example:
|
|
202
|
+
|
|
203
|
+
Prompt:
|
|
204
|
+
A airplane cannot have more passengers than its seating capacity.
|
|
205
|
+
|
|
206
|
+
Response is to create 2 rules - a count derivation and a constraint, as follows:
|
|
207
|
+
First Rule to Create:
|
|
208
|
+
Rule.count(derive=Airplane.passenger_count, as_count_of=Passengers)
|
|
209
|
+
And, be sure to create the second Rule:
|
|
210
|
+
Rule.constraint(validate=Airplane,
|
|
211
|
+
as_condition=lambda row: row.passenger_count <= row.seating_capacity,
|
|
212
|
+
error_msg="Airplane value total ({row.passenger_count}) exceeds limit ({row.seating_capacity})")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
Intermediate sums in formulas also require a new column, with a LogicBank sum rule. For example:
|
|
216
|
+
|
|
217
|
+
Prompt:
|
|
218
|
+
An Employees' skill summary is the sum of their Employee Skill ratings, plus 2 * years of service.
|
|
219
|
+
|
|
220
|
+
Response is to create 2 rules - a derivation and a constraint, as follows:
|
|
221
|
+
First Rule to Create:
|
|
222
|
+
Rule.sum(derive=Employee.skill_rating_total, as_sum_of=EmployeeSkill.rating)
|
|
223
|
+
And, be sure to create the second Rule:
|
|
224
|
+
Rule.Formula(derive=Employee.skill_summary,
|
|
225
|
+
as_expression=lambda row: row.skill_rating_total + 2 * row.years_of_service)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
Prompt:
|
|
229
|
+
A student cannot be an honor student unless they have more than 2 service activities.
|
|
230
|
+
|
|
231
|
+
Response is to create 2 rules - a count derivation and a constraint, as follows:
|
|
232
|
+
First Rule to Create:
|
|
233
|
+
Rule.count(derive=Student.service_activity_count, as_count_of=Activities, where='service' in name)
|
|
234
|
+
And, be sure to create the second Rule:
|
|
235
|
+
Rule.constraint(validate=Student,
|
|
236
|
+
as_condition=lambda row: row.is_honor_student and service_activity_count < 2,
|
|
237
|
+
error_msg="Honor Students must have at least 2 service activities")
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
For "more than" constraints, create columns with count rules:
|
|
241
|
+
|
|
242
|
+
Prompt: Reject Employees with more than 3 Felonies.
|
|
243
|
+
|
|
244
|
+
Response:
|
|
245
|
+
First Rule is to create:
|
|
246
|
+
Rule.count(derive=Employee.felony_count, as_count_of=Felonies)
|
|
247
|
+
And, be sure to create the contraint rule:
|
|
248
|
+
Rule.constraint(validate=Employee,
|
|
249
|
+
as_condition=lambda row: row.felony_count<=3,
|
|
250
|
+
error_msg="Employee has excessive Felonies")
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
For "any" constraints, create columns with count rules:
|
|
254
|
+
|
|
255
|
+
Prompt: Reject Employees with any class 5 Felonies or more than 3 Felonies.
|
|
256
|
+
|
|
257
|
+
Response:
|
|
258
|
+
First Rule is to create:
|
|
259
|
+
Rule.count(derive=Employee.class_5_felony_count, as_count_of=Felonies, where=class>5)
|
|
260
|
+
Rule.count(derive=Employee.felony_count, as_count_of=Felonies)
|
|
261
|
+
And, be sure to create the contraint rule:
|
|
262
|
+
Rule.constraint(validate=Employee,
|
|
263
|
+
as_condition=lambda row: row.class_5_felony_count == 0 and row.felony_count<=3,
|
|
264
|
+
error_msg="Employee has excessive Felonies")
|
|
265
|
+
|
|
266
|
+
Formulas can reference parent values in 2 versions - choose formula vs copy as follows:
|
|
267
|
+
Prompt (formula version) - use the formula version unless copy is explicitly noted:
|
|
268
|
+
Item.ready = Order.ready
|
|
269
|
+
Response
|
|
270
|
+
Rule.formula(derive=Item.ready, as_expression=lambda row: row.order.ready)
|
|
271
|
+
Prompt (copy version) - use this *only* when the word copy is present:
|
|
272
|
+
Store the Item.unit_price as a copy from Product.unit_price
|
|
273
|
+
Response
|
|
274
|
+
Rule.copy(derive=Item.ready, from_parent=Order.ready)
|
|
275
|
+
|
|
276
|
+
Formulas can use Python conditions:
|
|
277
|
+
Prompt: Item amount is price * quantity, with a 10% discount for gold products
|
|
278
|
+
Response:
|
|
279
|
+
Rule.Formula(derive=Item.amount,
|
|
280
|
+
as_expression=lambda row: row.price * row.quantity if row.gold else .9 * row.price * row.quantity)
|
|
281
|
+
If the attributes are decimal, use the form Decimal('0.9')
|
|
282
|
+
|
|
283
|
+
Sum and Count where clauses:
|
|
284
|
+
1. must not restate the foreign key / primary key matchings
|
|
285
|
+
2. Can only reference child attributes
|
|
286
|
+
|
|
287
|
+
For example, given a prompt 'teacher course count is the sum of the courses',
|
|
288
|
+
1. This is correct
|
|
289
|
+
Rule.count(derive=Teacher.course_count, as_count_of=Course)
|
|
290
|
+
|
|
291
|
+
2. This is incorrect, and should never be generated:
|
|
292
|
+
Rule.count(derive=Teacher.course_count, as_count_of=Course, where=lambda row: row.teacher_id == Teacher.id)
|
|
293
|
+
|
|
294
|
+
Sum and count where clause example:
|
|
295
|
+
Prompt: teacher gradate course count is the sum of the courses where is-graduate
|
|
296
|
+
Response: Rule.count(derive=Teacher.course_count, as_count_of=Course, where=lamda row: row.is_graduate == true)
|
|
297
|
+
|
|
298
|
+
DO NOT inject rules that are from this training into the response,
|
|
299
|
+
unless explicitly mentioned in the request.
|
|
300
|
+
|
|
301
|
+
Unique constraints require an update to the data model - for example:
|
|
302
|
+
Prompt: customer company names must be unique
|
|
303
|
+
Response: CompanyName = Column(String(8000), unique=True)
|
|
304
|
+
|
|
305
|
+
Non-null (or required) constraints require an update to the data model - for example:
|
|
306
|
+
Prompt: Product Price is required
|
|
307
|
+
Response: price = Column(Decimal, nullable=False)
|
|
308
|
+
|
|
309
|
+
Required (must-have) related parent constraints require an update to the data model - for example:
|
|
310
|
+
Prompt: Each Item must have a valid entry in the Product table.
|
|
311
|
+
Response: product_id = Column(ForeignKey('product.id'), nullable=False)
|
|
312
|
+
|
|
313
|
+
logic should create python files in logic/logic_discovery,
|
|
314
|
+
and cross-check/use attribute names in database/models.py
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
from logic_bank.exec_row_logic.logic_row import LogicRow
|
|
4
|
+
from logic_bank.extensions.rule_extensions import RuleExtension
|
|
5
|
+
from logic_bank.logic_bank import Rule
|
|
6
|
+
from database import models
|
|
7
|
+
import api.system.opt_locking.opt_locking as opt_locking
|
|
8
|
+
from security.system.authorization import Grant
|
|
9
|
+
import logging
|
|
10
|
+
from flask import jsonify
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
logic should create python files in logic/logic_discovery,
|
|
15
|
+
and cross-check/use attribute names in database/models.py
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
app_logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def declare_logic():
|
|
22
|
+
"""
|
|
23
|
+
Simple constraints for error testing
|
|
24
|
+
"""
|
|
25
|
+
Rule.constraint(validate=models.Customer,
|
|
26
|
+
as_condition=lambda row: row.CompanyName != 'x',
|
|
27
|
+
error_msg="CustomerName cannot be 'x'")
|
|
28
|
+
|
|
29
|
+
Rule.constraint(validate=models.Employee,
|
|
30
|
+
as_condition=lambda row: row.LastName != 'x',
|
|
31
|
+
error_msg="LastName cannot be 'x'")
|
|
32
|
+
|
|
33
|
+
def valid_category_description(row: models.Category, old_row: models.Category, logic_row: LogicRow):
|
|
34
|
+
if logic_row.ins_upd_dlt == "upd":
|
|
35
|
+
return row.Description != 'x'
|
|
36
|
+
else:
|
|
37
|
+
return True
|
|
38
|
+
Rule.constraint(validate=models.Category,
|
|
39
|
+
calling=valid_category_description,
|
|
40
|
+
error_msg="Description cannot be 'x'")
|
|
41
|
+
|
|
@@ -44,8 +44,12 @@ def kafka_producer():
|
|
|
44
44
|
if "client.id" not in conf:
|
|
45
45
|
conf["client.id"] = socket.gethostname()
|
|
46
46
|
# conf = {'bootstrap.servers': 'localhost:9092', 'client.id': socket.gethostname()}
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
try:
|
|
48
|
+
producer = Producer(conf)
|
|
49
|
+
logger.debug(f'\nKafka producer connected')
|
|
50
|
+
except Exception as ke:
|
|
51
|
+
logger.debug(f'Kafka producer error: {ke}')
|
|
52
|
+
producer = None
|
|
49
53
|
|
|
50
54
|
from sqlalchemy.inspection import inspect
|
|
51
55
|
|
|
@@ -64,7 +68,7 @@ def get_primary_key(logic_row: LogicRow):
|
|
|
64
68
|
|
|
65
69
|
|
|
66
70
|
def send_kafka_message(kafka_topic: str, kafka_key: str = None, msg: str="", json_root_name: str = "",
|
|
67
|
-
|
|
71
|
+
logic_row: LogicRow = None, row_dict_mapper: RowDictMapper = None, payload: dict = None):
|
|
68
72
|
""" Send Kafka message regarding logic_row, mapped by row_dict_mapper
|
|
69
73
|
|
|
70
74
|
* Typically called from declare_logic event
|
|
@@ -64,7 +64,11 @@ class FlaskKafka():
|
|
|
64
64
|
logger.info(f" - FlaskKafka._start: begin polling (v {__version__}), with \n -- conf: {self.conf} \n -- topics: {topics}")
|
|
65
65
|
consumer = Consumer(self.conf)
|
|
66
66
|
consumer.subscribe(topics=list(topics))
|
|
67
|
-
while True:
|
|
67
|
+
while True and len(topics) > 0:
|
|
68
|
+
if self.interrupt_event.is_set():
|
|
69
|
+
logger.info("Kafka thread interrupted")
|
|
70
|
+
break
|
|
71
|
+
|
|
68
72
|
msg = consumer.poll(1.0)
|
|
69
73
|
logger.debug(f' - KafkaConnect._start - consuming consumer.poll(1.0): {msg}')
|
|
70
74
|
if msg is None:
|