ApiLogicServer 14.3.25__py3-none-any.whl → 14.5.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 (167) hide show
  1. api_logic_server_cli/add_cust/add_cust.py +283 -0
  2. api_logic_server_cli/api_logic_server.py +18 -250
  3. api_logic_server_cli/api_logic_server_info.yaml +3 -3
  4. api_logic_server_cli/cli.py +54 -35
  5. api_logic_server_cli/create_from_model/__pycache__/api_logic_server_utils.cpython-312.pyc +0 -0
  6. api_logic_server_cli/create_from_model/__pycache__/create_db_from_model.cpython-312.pyc +0 -0
  7. api_logic_server_cli/create_from_model/__pycache__/dbml.cpython-312.pyc +0 -0
  8. api_logic_server_cli/create_from_model/__pycache__/ont_build.cpython-312.pyc +0 -0
  9. api_logic_server_cli/create_from_model/__pycache__/ont_create.cpython-312.pyc +0 -0
  10. api_logic_server_cli/create_from_model/api_logic_server_utils.py +47 -0
  11. api_logic_server_cli/create_from_model/create_db_from_model.py +2 -0
  12. api_logic_server_cli/create_from_model/dbml.py +113 -58
  13. api_logic_server_cli/create_from_model/ont_build.py +102 -74
  14. api_logic_server_cli/create_from_model/ont_create.py +7 -6
  15. api_logic_server_cli/create_from_model/safrs-react-admin-npm-build/static/.DS_Store +0 -0
  16. api_logic_server_cli/database/basic_demo.sqlite +0 -0
  17. api_logic_server_cli/database/basic_demo.txt +1 -0
  18. api_logic_server_cli/database/basic_demo_wg.sqlite +0 -0
  19. api_logic_server_cli/database/nw-gold-fix.sql +62 -0
  20. api_logic_server_cli/database/nw-gold.sqlite +0 -0
  21. api_logic_server_cli/{prototypes/manager/webgenai → fragments}/docker-compose.yml +1 -1
  22. api_logic_server_cli/genai/genai.py +42 -11
  23. api_logic_server_cli/genai/genai_graphics.py +252 -38
  24. api_logic_server_cli/genai/genai_svcs.py +20 -12
  25. api_logic_server_cli/manager.py +22 -12
  26. api_logic_server_cli/prototypes/.DS_Store +0 -0
  27. api_logic_server_cli/prototypes/base/.DS_Store +0 -0
  28. api_logic_server_cli/prototypes/base/.vscode/launch.json +22 -2
  29. api_logic_server_cli/prototypes/base/api/expose_api_models.py +3 -1
  30. api_logic_server_cli/prototypes/base/api_logic_server_run.py +5 -2
  31. api_logic_server_cli/prototypes/base/config/activate_logicbank.py +1 -0
  32. api_logic_server_cli/prototypes/base/config/config.py +123 -25
  33. api_logic_server_cli/prototypes/base/config/default.env +7 -1
  34. api_logic_server_cli/prototypes/base/config/logging.yml +1 -0
  35. api_logic_server_cli/prototypes/base/config/server_setup.py +33 -1
  36. api_logic_server_cli/prototypes/base/database/test_data/readme.md +5 -2
  37. api_logic_server_cli/prototypes/base/devops/docker-standard-image/docker-compose-standard-image.yml +7 -2
  38. api_logic_server_cli/prototypes/base/docs/training/logic_bank_api.prompt +314 -0
  39. api_logic_server_cli/prototypes/base/docs/training/logic_example.py +41 -0
  40. api_logic_server_cli/prototypes/base/integration/kafka/kafka_producer.py +12 -5
  41. api_logic_server_cli/prototypes/base/integration/n8n/n8n_producer.py +68 -21
  42. api_logic_server_cli/prototypes/base/integration/n8n/n8n_readme.md +19 -0
  43. api_logic_server_cli/prototypes/base/integration/system/FlaskKafka.py +5 -1
  44. api_logic_server_cli/prototypes/base/test/basic/server_test.py +1 -1
  45. api_logic_server_cli/prototypes/base/ui/templates/bar_chart.jinja +64 -0
  46. api_logic_server_cli/prototypes/basic_demo/README.md +29 -52
  47. api_logic_server_cli/prototypes/basic_demo/customizations/api/.DS_Store +0 -0
  48. api_logic_server_cli/prototypes/basic_demo/customizations/api/api_discovery/mcp_server_executor.py +138 -0
  49. api_logic_server_cli/prototypes/basic_demo/customizations/api/api_discovery/openapi.py +92 -0
  50. api_logic_server_cli/prototypes/basic_demo/customizations/api/api_discovery/proper_update_def.json +71 -0
  51. api_logic_server_cli/prototypes/basic_demo/customizations/config/default.env +13 -0
  52. api_logic_server_cli/prototypes/basic_demo/customizations/database/db.sqlite +0 -0
  53. api_logic_server_cli/prototypes/basic_demo/customizations/database/models.py +131 -0
  54. api_logic_server_cli/prototypes/basic_demo/customizations/integration/.DS_Store +0 -0
  55. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/.DS_Store +0 -0
  56. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/1_langchain_loader.py +71 -0
  57. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/2_gpt_mcp_prompt.txt +19 -0
  58. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/README_mcp.md +13 -0
  59. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/mcp_client_executor.py +295 -0
  60. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/mcp_schema.txt +47 -0
  61. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/mcp_server_discovery.json +9 -0
  62. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/multi_mcp_flow/multi_mcp_flow.png +0 -0
  63. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/multi_mcp_flow/multi_mcp_orchestration.yaml +49 -0
  64. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/multi_mcp_flow/wny mcp flows.png +0 -0
  65. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/natlang_to_api.py +73 -0
  66. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/curl.txt +5 -0
  67. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/images/MCP Overview.png +0 -0
  68. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/images/MCP_Arch.png +0 -0
  69. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/images/MCP_Overview_Executor.png +0 -0
  70. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/invoke_llm/1 - prompt_messages_array.json +10 -0
  71. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/invoke_llm/2 - completion_tool_context.json +12 -0
  72. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/llm_schema.txt +38 -0
  73. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/nw_swagger_2.yaml +17393 -0
  74. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/nw_swagger_3.yaml +16660 -0
  75. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/nw_swagger_3_relaxed.yaml +109 -0
  76. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/proxy_server.py +51 -0
  77. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/proxy_serverZ.py +72 -0
  78. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/validate_jsonapi.py +64 -0
  79. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/run_executor.py +23 -0
  80. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/swagger_converter.py +65 -0
  81. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/z_old/3_executor_test_agent.py +52 -0
  82. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/3_executor_test_agent.py +52 -0
  83. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/README_functon.md +201 -0
  84. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/ai_plugin.json +17 -0
  85. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/nw-swagger_3.json +1731 -0
  86. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/snippets.txt +5 -0
  87. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3 genai_demo_with_get.json +1731 -0
  88. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3.json +1782 -0
  89. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3_genai_demo.json +264 -0
  90. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3_genai_demo_with_update.json +1782 -0
  91. api_logic_server_cli/prototypes/basic_demo/customizations/logic/declare_logic.py +62 -44
  92. api_logic_server_cli/prototypes/basic_demo/customizations/security/declare_security.py +11 -12
  93. api_logic_server_cli/prototypes/basic_demo/customizations/ui/admin/admin.yaml +166 -0
  94. api_logic_server_cli/prototypes/basic_demo/iteration/api/{customize_api.py → api_discovery/order_b2b.py} +17 -23
  95. api_logic_server_cli/prototypes/basic_demo/iteration/database/db.sqlite +0 -0
  96. api_logic_server_cli/prototypes/basic_demo/iteration/integration/row_dict_maps/OrderB2B.py +6 -5
  97. api_logic_server_cli/prototypes/basic_demo/iteration/integration/row_dict_maps/OrderShipping.py +4 -4
  98. api_logic_server_cli/prototypes/basic_demo/iteration/logic/declare_logic.py +69 -43
  99. api_logic_server_cli/prototypes/basic_demo/iteration/ui/admin/admin.yaml +125 -50
  100. api_logic_server_cli/prototypes/genai_demo/ui/admin/admin.yaml +1 -1
  101. api_logic_server_cli/prototypes/manager/README.md +30 -4
  102. api_logic_server_cli/prototypes/manager/README_X.md +663 -0
  103. api_logic_server_cli/prototypes/manager/system/genai/.DS_Store +0 -0
  104. api_logic_server_cli/prototypes/manager/system/genai/examples/.DS_Store +0 -0
  105. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/.DS_Store +0 -0
  106. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo.prompt +0 -10
  107. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo.response_example +32 -10
  108. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_docs_logic/docs/002_create_db_models.prompt +4 -4
  109. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_docs_logic/docs/003_create_db_models.response +77 -47
  110. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_informal.prompt +1 -1
  111. api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/dashboard_services.jinja +83 -0
  112. api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/graphics_dashboard_WIP.py +34 -0
  113. api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/{graphics_services.py → graphics_services_api_xxx.py} +0 -9
  114. api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/graphics_services_db.jinja +46 -0
  115. api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/graphics_services_db_each_method.jinja +36 -0
  116. api_logic_server_cli/prototypes/manager/system/genai/prompt_inserts/graphics.prompt +7 -3
  117. api_logic_server_cli/prototypes/manager/system/genai/prompt_inserts/response_format.prompt +8 -1
  118. api_logic_server_cli/prototypes/manager/system/install-ApiLogicServer-dev/install-ApiLogicServer-dev.ps1 +100 -0
  119. api_logic_server_cli/prototypes/manager/system/install-ApiLogicServer-dev/install-ApiLogicServer-dev.sh +116 -0
  120. api_logic_server_cli/prototypes/manager/system/install-ApiLogicServer-dev/readme.md +7 -0
  121. api_logic_server_cli/prototypes/manager/system/style-guide.yaml +2 -2
  122. api_logic_server_cli/prototypes/manager/webgenai/README.md +6 -0
  123. api_logic_server_cli/prototypes/nw/docs/graphics/count_orders_by_category.prompt +1 -0
  124. api_logic_server_cli/prototypes/nw/docs/graphics/order_count_by_month.prompt +1 -0
  125. api_logic_server_cli/prototypes/nw/docs/graphics/request copy.json +892 -0
  126. api_logic_server_cli/prototypes/nw/docs/graphics/request.json +6 -0
  127. api_logic_server_cli/prototypes/nw/docs/graphics/response.json +17 -0
  128. api_logic_server_cli/prototypes/nw/docs/graphics/response.yaml +59 -0
  129. api_logic_server_cli/prototypes/nw/docs/graphics/sales_by_category.prompt +1 -0
  130. api_logic_server_cli/prototypes/nw/ui/admin/home.js +5 -4
  131. api_logic_server_cli/prototypes/nw/ui/app_model_custom.yaml +851 -1082
  132. api_logic_server_cli/prototypes/nw_no_cust/Tutorial.md +45 -26
  133. api_logic_server_cli/prototypes/nw_no_cust/api/api_discovery/openapi.py +130 -0
  134. api_logic_server_cli/prototypes/nw_no_cust/api/api_discovery/proper_update_def.json +71 -0
  135. api_logic_server_cli/prototypes/nw_no_cust/config/default.env +13 -0
  136. api_logic_server_cli/prototypes/nw_no_cust/docs/graphics/count_orders_by_category.prompt +1 -0
  137. api_logic_server_cli/prototypes/nw_no_cust/docs/graphics/sales_by_employee.prompt +1 -0
  138. api_logic_server_cli/prototypes/ont_app/ontimize_seed/nginx/nginx.conf +2 -2
  139. api_logic_server_cli/prototypes/ont_app/ontimize_seed/package-lock.json +9725 -1180
  140. api_logic_server_cli/prototypes/ont_app/ontimize_seed/package.json +6 -9
  141. api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/app/app.config.ts +2 -1
  142. api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/app/shared/app.services.config.ts +1 -1
  143. api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/assets/css/app.scss +4 -0
  144. api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/assets/i18n/en.json +1 -1
  145. api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/assets/i18n/es.json +14 -12
  146. api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/environments/environment.prod.ts +5 -5
  147. api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/environments/environment.ts +5 -5
  148. api_logic_server_cli/prototypes/ont_app/templates/app_config.jinja +1 -1
  149. api_logic_server_cli/prototypes/ont_app/templates/date_template.html +1 -1
  150. api_logic_server_cli/prototypes/ont_app/templates/detail_template.html +1 -1
  151. api_logic_server_cli/prototypes/ont_app/templates/new_template.html +16 -16
  152. api_logic_server_cli/prototypes/ont_app/templates/textarea_template.html +1 -1
  153. api_logic_server_cli/prototypes/ont_app/templates/timestamp_template.html +1 -1
  154. api_logic_server_cli/prototypes/sample_ai/logic/declare_logic.py +30 -13
  155. apilogicserver-14.5.0.dist-info/METADATA +76 -0
  156. {apilogicserver-14.3.25.dist-info → apilogicserver-14.5.0.dist-info}/RECORD +160 -88
  157. {apilogicserver-14.3.25.dist-info → apilogicserver-14.5.0.dist-info}/WHEEL +1 -1
  158. api_logic_server_cli/prototypes/basic_demo/apply_customizations.ps1 +0 -17
  159. api_logic_server_cli/prototypes/basic_demo/apply_customizations.sh +0 -14
  160. api_logic_server_cli/prototypes/basic_demo/apply_iteration.ps1 +0 -20
  161. api_logic_server_cli/prototypes/basic_demo/apply_iteration.sh +0 -15
  162. api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/service_template_jsonapi_rpc.jinja +0 -37
  163. api_logic_server_cli/prototypes/manager/system/genai/graphics_templates/service_template_unused.jinja +0 -38
  164. apilogicserver-14.3.25.dist-info/METADATA +0 -167
  165. {apilogicserver-14.3.25.dist-info → apilogicserver-14.5.0.dist-info}/entry_points.txt +0 -0
  166. {apilogicserver-14.3.25.dist-info → apilogicserver-14.5.0.dist-info}/licenses/LICENSE +0 -0
  167. {apilogicserver-14.3.25.dist-info → apilogicserver-14.5.0.dist-info}/top_level.txt +0 -0
@@ -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
+
@@ -12,7 +12,7 @@ You do not normally need to alter this file
12
12
  from config.config import Args
13
13
  from confluent_kafka import Producer
14
14
  import socket
15
- import logging
15
+ import logging, os
16
16
  from logic_bank.exec_row_logic.logic_row import LogicRow
17
17
  from integration.system.RowDictMapper import RowDictMapper
18
18
  from flask import jsonify
@@ -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
- producer = Producer(conf)
48
- logger.debug(f'\nKafka producer connected')
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
- logic_row: LogicRow = None, row_dict_mapper: RowDictMapper = None, payload: dict = None):
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
@@ -78,7 +82,10 @@ def send_kafka_message(kafka_topic: str, kafka_key: str = None, msg: str="", jso
78
82
  json_root_name (str, optional): json name for json payload root; default is logic_row.name
79
83
  """
80
84
 
81
-
85
+ if '1' == os.getenv("APILOGICPROJECT_NO_FLASK", None):
86
+ logger.debug("kafka_producer#send_kafka_message: not safrs class (e.g. database/test_data/test_data_code.py)")
87
+ return
88
+
82
89
  if isinstance(payload, dict):
83
90
  row_obj_dict = payload
84
91
  elif row_dict_mapper is not None:
@@ -19,7 +19,18 @@ from integration.system.RowDictMapper import RowDictMapper
19
19
  from flask import jsonify
20
20
  import api.system.api_utils as api_utils
21
21
  from config.config import Args
22
-
22
+ '''
23
+ wh_scheme = "http"
24
+ wh_server = "localhost" # or cloud.n8n.io...
25
+ wh_port = 5678
26
+ wh_endpoint = "webhook-test"
27
+ wh_path = "002fa0e8-f7aa-4e04-b4e3-e81aa29c6e69"
28
+ token = "YWRtaW46cA==" # Basic auth for n8n
29
+ N8N_PRODUCER = {"authorization": f"Basic {token}", "n8n_url": f'"{wh_scheme}://{wh_server}:{wh_port}/{wh_endpoint}/{wh_path}"'}
30
+ # Or enter the n8n_url directly:
31
+ N8N_PRODUCER = {"authorization": f"Basic {token}","n8n_url":"http://localhost:5678/webhook-test/002fa0e8-f7aa-4e04-b4e3-e81aa29c6e69"}
32
+
33
+ '''
23
34
  producer = None
24
35
  """ connected producer (or null if N8N not enabled in Config.py) """
25
36
 
@@ -31,27 +42,60 @@ logger.debug("n8n_connect imported")
31
42
 
32
43
  def n8n_producer():
33
44
  """
34
- Called by api_logic_server_run>server_setup to listen on kafka using confluent_kafka
35
-
36
- Enabled by config.KAFKA_CONNECT (dict, of bootstrap.servers, client.id)
37
45
 
46
+ Enabled by config (dict, of bootstrap.servers, client.id)
47
+ OR
48
+ token = getenv("N8N_TOKEN", Args.instance.wh_token) #basic auth
49
+ scheme = getenv("N8N_SCHEME",Args.instance.wh_scheme) # http or https
50
+ server = getenv("N8N_SERVER", Args.instance.wh_server)
51
+ port = getenv("N8N_PORT",Args.instance.wh_port)
52
+ endpoint = getenv("N8N_ENDPOINT", wh_endpoint) # this can change for different workflows
53
+ path = getenv("N8N_PATH", Args.instance.wh_path)
54
+ conf = getenv("N8N_PRODUCER", Args.instance.n8n_producer)
38
55
  Args:
39
56
  none
40
57
  """
58
+
59
+ producer = configure_n8n_producer()
41
60
 
42
- global conf
43
- if Args.instance.n8n_producer:
44
- conf = Args.instance.n8n_producer
45
- producer = conf
46
- # good place to do defaults, get api keys, etc
47
- logger.debug('N8N producer initialized')
61
+ def configure_n8n_producer(wh_endpoint: str = None, wh_path: str = None) -> dict:
62
+ """
63
+ Need to be able to change the endpoint for different workflows and webhooks
48
64
 
65
+ Args:
66
+ wh_endpoint (str, optional): _description_. Defaults to None.
49
67
 
50
- def send_n8n_message(http_method: str = "POST", ins_upd_dlt: str = "ins", msg: str = "",
68
+ Returns:
69
+ dict: _description_
70
+ """
71
+ global conf
72
+ conf = None
73
+ from os import getenv
74
+ endpoint = wh_endpoint if wh_endpoint is not None else Args.instance.wh_endpoint
75
+ n8n_path = wh_path if wh_path is not None else Args.instance.wh_path
76
+ token = getenv("N8N_TOKEN", Args.instance.wh_token)
77
+ scheme = getenv("N8N_SCHEME",Args.instance.wh_scheme)
78
+ server = getenv("N8N_SERVER", Args.instance.wh_server)
79
+ port = getenv("N8N_PORT",Args.instance.wh_port)
80
+ endpoint = getenv("N8N_ENDPOINT", endpoint)
81
+ path = getenv("N8N_PATH", n8n_path)
82
+ if token is None or scheme is None or server is None or port is None or path is None:
83
+ logger.debug('N8N producer not configured in config/Config.py or environment variables')
84
+ conf = getenv("N8N_PRODUCER", Args.instance.n8n_producer)
85
+ else:
86
+ conf = {"authorization": f"Basic {token}", "n8n_url": f'"{scheme}://{server}:{port}/{wh_endpoint if wh_endpoint is not None else endpoint}/{path}"'}
87
+
88
+ return conf
89
+
90
+ def send_n8n_message(http_method: str = "POST",
91
+ ins_upd_dlt: str = "ins",
92
+ msg: str = "",
51
93
  wh_entity: str = "unknown",
52
94
  logic_row: LogicRow = None,
53
95
  row_dict_mapper: RowDictMapper = None,
54
- payload: dict = None) -> any:
96
+ payload: dict = None,
97
+ wh_endpoint: str = None,
98
+ wh_path: str= None) -> str:
55
99
  """ Send N8N webhook message regarding [logic_row, mapped by row_dict_mapper or by payload]
56
100
 
57
101
  * Typically called from declare_logic event
@@ -64,13 +108,16 @@ def send_n8n_message(http_method: str = "POST", ins_upd_dlt: str = "ins", msg: s
64
108
  row_dict_mapper (RowDictMapper): (Optional) typically subclass of RowDictMapper, transforms row to dict
65
109
  payload (str): (Optional) JSON data to be sent as string (json.dumps(row.to_dict()))
66
110
  wh_entity (str): the webhook entity name pass in header
111
+ wh_endpoint (str): (Optional) override the webhook endpoint name modify the producer
112
+ wh_path (str): (Optional) override the webhook path name modify the producer
67
113
  """
68
114
 
69
- global conf
70
- if conf is None:
71
- conf = Args.instance.n8n_producer #FIXME not sure why this fails - conf is None
115
+ #global conf
116
+ #if conf is None:
117
+ # conf = Args.instance.n8n_producer #FIXME not sure why this fails - conf is None
72
118
  #return "N8N not enabled in Config.py"
73
-
119
+ conf = configure_n8n_producer(wh_endpoint, wh_path)
120
+ logger.debug(f"n8n_producer: conf: {conf}")
74
121
  if row_dict_mapper is None and payload is None and logic_row is None:
75
122
  return "send_n8n_message: payload, logic_row, row_dict_mapper are all None - must provide one"
76
123
  row_obj_dict = None
@@ -96,17 +143,18 @@ def send_n8n_message(http_method: str = "POST", ins_upd_dlt: str = "ins", msg: s
96
143
  status = {"status_code": 500}
97
144
 
98
145
  if conf is None:
99
- logger.debug(f"n8n_producer: not configured in config/Config.py")
146
+ logger.debug(f"n8n_producer.py: N8N not configured in config/Config.py or environment variables")
100
147
  else:
101
148
  headers = {
102
149
  "Authorization": conf['authorization'],
103
150
  "Content-Type": "application/json",
104
151
  "wh_state": wh_state,
105
- "wh_entity": wh_entity
152
+ "wh_entity": wh_entity,
153
+ "wh_source": "api_logic_server",
106
154
  }
107
155
  endpoint = f'{conf["n8n_url"]}'
108
156
  status = {"status_code": 500}
109
- if http_method in {"post", "POST"}:
157
+ if http_method.lower() == "post":
110
158
  #Only passing payload in this example
111
159
  status = requests.post(endpoint, json=row_obj_dict, headers=headers)
112
160
  elif http_method.lower() == "get":
@@ -121,5 +169,4 @@ def send_n8n_message(http_method: str = "POST", ins_upd_dlt: str = "ins", msg: s
121
169
  logger.error(f"\nn8n_producer fails with: {e}")
122
170
  long_message = traceback.format_exc()
123
171
  logger.error(long_message)
124
- return long_message
125
-
172
+ return long_message
@@ -45,4 +45,23 @@ in the workflow (e.g. SendGrid email)
45
45
  logic_row.debug(status)
46
46
 
47
47
  Rule.after_flush_row_event(on_class=models.Customer, calling=call_n8n_workflow)
48
+ ```
49
+ ## Environment override
50
+ You can override the configuration of the N8N_PRODUCER or the components used to build the producer.
51
+ ```
52
+ # source n8n.env
53
+ # to configure your n8n webhook
54
+ export N8N_TOKEN=YWRtaW46cA==
55
+ export N8N_SCHEME=http
56
+ export N8N_SERVER=localhost
57
+ export N8N_PORT=5678
58
+ # each webhook may have a unique name
59
+ export N8N_ENDPOINT=webhook_test
60
+
61
+ # the path will change between dev and production
62
+ export N8N_PATH=002fa0e8-f7aa-4e04-b4e3-e81aa29c6e69
63
+
64
+ # OR set the n8n producer before calling the webhook
65
+ # {"authorization": f"Basic {token}", "n8n_url": f'"{scheme}://{server}:{port}/{wh_endpoint if wh_endpoint is not None else endpoint}/{path}"'}
66
+ export N8N_PRODUCER=http://localhost:5678/webhook-test/002fa0e8-f7aa-4e04-b4e3-e81aa29c6e69
48
67
  ```
@@ -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:
@@ -26,7 +26,7 @@ def server_tests(host, port, version):
26
26
  port - server port
27
27
  version - ApiLogicServer version
28
28
  """
29
- pass # Your Code Goes Here
29
+ ... # Your Code Goes Here
30
30
 
31
31
  if __name__ == "__main__":
32
32
  # only to run when not called via 'import' here
@@ -0,0 +1,64 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Sales by Region</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
8
+ <style>
9
+ body {
10
+ margin: 0;
11
+ padding: 0;
12
+ display: flex;
13
+ overflow: hidden;
14
+ justify-content: center;
15
+ align-items: center;
16
+ height: 100%;
17
+ box-sizing: border-box;
18
+ }
19
+ canvas {
20
+ display: block;
21
+ }
22
+ </style>
23
+ </head>
24
+ <body>
25
+ <canvas id="salesChart" width="200" height="200"></canvas>
26
+ <script>
27
+ const labels = [];
28
+ const data = [];
29
+ result = {{ result | tojson }};
30
+ console.log(result);
31
+ const type = result.chart_type;
32
+ const title = result.title;
33
+ const columns = result.columns;
34
+ console.log(JSON.stringify(result));
35
+ result.results.forEach(item => {
36
+ labels.push(item[columns[0]]);
37
+ data.push(parseFloat(item[columns[1]]));
38
+ });
39
+ const ctx = document.getElementById('salesChart').getContext('2d');
40
+ const salesChart = new Chart(ctx, {
41
+ type: type,
42
+ data: {
43
+ labels: labels,
44
+ datasets: [{
45
+ label: title,
46
+ data: data,
47
+ backgroundColor: {{ color | tojson }},
48
+ borderColor: 'rgba(75, 192, 192, 1)',
49
+ borderWidth: 1
50
+ }]
51
+ },
52
+ options: {
53
+ responsive: true,
54
+ maintainAspectRatio: false,
55
+ scales: {
56
+ y: {
57
+ beginAtZero: true
58
+ }
59
+ }
60
+ }
61
+ });
62
+ </script>
63
+ </body>
64
+ </html>