ApiLogicServer 14.4.0__py3-none-any.whl → 14.5.3__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 (95) hide show
  1. api_logic_server_cli/add_cust/add_cust.py +269 -0
  2. api_logic_server_cli/api_logic_server.py +18 -238
  3. api_logic_server_cli/api_logic_server_info.yaml +3 -3
  4. api_logic_server_cli/cli.py +38 -28
  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__/dbml.cpython-312.pyc +0 -0
  7. api_logic_server_cli/create_from_model/__pycache__/ont_build.cpython-312.pyc +0 -0
  8. api_logic_server_cli/create_from_model/__pycache__/ont_create.cpython-312.pyc +0 -0
  9. api_logic_server_cli/create_from_model/api_logic_server_utils.py +47 -0
  10. api_logic_server_cli/create_from_model/dbml.py +113 -58
  11. api_logic_server_cli/create_from_model/ont_build.py +83 -60
  12. api_logic_server_cli/create_from_model/ont_create.py +2 -1
  13. api_logic_server_cli/database/basic_demo.sqlite +0 -0
  14. api_logic_server_cli/database/basic_demo.txt +1 -0
  15. api_logic_server_cli/database/basic_demo_wg.sqlite +0 -0
  16. api_logic_server_cli/manager.py +3 -2
  17. api_logic_server_cli/prototypes/base/.vscode/launch.json +3 -2
  18. api_logic_server_cli/prototypes/base/config/config.py +66 -11
  19. api_logic_server_cli/prototypes/base/config/default.env +7 -1
  20. api_logic_server_cli/prototypes/base/database/test_data/readme.md +2 -1
  21. api_logic_server_cli/prototypes/base/integration/kafka/kafka_producer.py +5 -2
  22. api_logic_server_cli/prototypes/base/integration/n8n/n8n_producer.py +68 -21
  23. api_logic_server_cli/prototypes/base/integration/n8n/n8n_readme.md +19 -0
  24. api_logic_server_cli/prototypes/base/test/basic/server_test.py +1 -1
  25. api_logic_server_cli/prototypes/basic_demo/README.md +29 -52
  26. api_logic_server_cli/prototypes/basic_demo/customizations/api/.DS_Store +0 -0
  27. api_logic_server_cli/prototypes/basic_demo/customizations/api/api_discovery/mcp_discovery.py +139 -0
  28. api_logic_server_cli/prototypes/basic_demo/customizations/api/api_discovery/openapi.py +92 -0
  29. api_logic_server_cli/prototypes/basic_demo/customizations/config/default.env +13 -0
  30. api_logic_server_cli/prototypes/basic_demo/customizations/config/server_setup.py +388 -0
  31. api_logic_server_cli/prototypes/basic_demo/customizations/database/db.sqlite +0 -0
  32. api_logic_server_cli/prototypes/basic_demo/customizations/database/models.py +131 -0
  33. api_logic_server_cli/prototypes/basic_demo/customizations/database/system/SAFRSBaseX.py +136 -0
  34. api_logic_server_cli/prototypes/basic_demo/customizations/integration/.DS_Store +0 -0
  35. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/.DS_Store +0 -0
  36. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/README_mcp.md +15 -0
  37. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/mcp_client_executor.py +350 -0
  38. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/mcp_schema.txt +47 -0
  39. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/mcp_server_discovery.json +9 -0
  40. api_logic_server_cli/prototypes/{nw_no_cust/integration/mcp → basic_demo/customizations/integration/openai_function}/3_executor_test_agent.py +20 -6
  41. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/README_functon.md +201 -0
  42. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/ai_plugin.json +17 -0
  43. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/nw-swagger_3.json +1731 -0
  44. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/snippets.txt +5 -0
  45. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3 genai_demo_with_get.json +1731 -0
  46. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3.json +1782 -0
  47. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3_genai_demo.json +264 -0
  48. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3_genai_demo_with_update.json +1782 -0
  49. api_logic_server_cli/prototypes/basic_demo/customizations/logic/declare_logic.py +79 -41
  50. api_logic_server_cli/prototypes/basic_demo/customizations/security/declare_security.py +11 -12
  51. api_logic_server_cli/prototypes/basic_demo/customizations/ui/admin/admin.yaml +166 -0
  52. api_logic_server_cli/prototypes/basic_demo/iteration/api/{customize_api.py → api_discovery/order_b2b.py} +17 -23
  53. api_logic_server_cli/prototypes/basic_demo/iteration/database/db.sqlite +0 -0
  54. api_logic_server_cli/prototypes/basic_demo/iteration/integration/row_dict_maps/OrderB2B.py +6 -5
  55. api_logic_server_cli/prototypes/basic_demo/iteration/integration/row_dict_maps/OrderShipping.py +4 -4
  56. api_logic_server_cli/prototypes/basic_demo/iteration/logic/declare_logic.py +69 -43
  57. api_logic_server_cli/prototypes/basic_demo/iteration/ui/admin/admin.yaml +125 -50
  58. api_logic_server_cli/prototypes/manager/README.md +4 -0
  59. api_logic_server_cli/prototypes/nw/logic/declare_logic.py +2 -2
  60. api_logic_server_cli/prototypes/nw_no_cust/.obsidian/app.json +1 -0
  61. api_logic_server_cli/prototypes/nw_no_cust/.obsidian/appearance.json +1 -0
  62. api_logic_server_cli/prototypes/nw_no_cust/.obsidian/core-plugins.json +31 -0
  63. api_logic_server_cli/prototypes/nw_no_cust/.obsidian/workspace.json +166 -0
  64. api_logic_server_cli/prototypes/nw_no_cust/Tutorial.md +45 -26
  65. api_logic_server_cli/prototypes/nw_no_cust/api/api_discovery/openapi.py +130 -0
  66. api_logic_server_cli/prototypes/nw_no_cust/api/api_discovery/proper_update_def.json +71 -0
  67. api_logic_server_cli/prototypes/nw_no_cust/config/default.env +13 -0
  68. api_logic_server_cli/prototypes/ont_app/ontimize_seed/package-lock.json +9725 -1180
  69. api_logic_server_cli/prototypes/ont_app/ontimize_seed/package.json +3 -6
  70. api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/app/shared/app.services.config.ts +1 -1
  71. api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/assets/css/app.scss +4 -0
  72. api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/assets/i18n/en.json +1 -1
  73. api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/assets/i18n/es.json +14 -12
  74. api_logic_server_cli/prototypes/ont_app/templates/app_config.jinja +1 -1
  75. api_logic_server_cli/prototypes/ont_app/templates/date_template.html +1 -1
  76. api_logic_server_cli/prototypes/ont_app/templates/textarea_template.html +1 -1
  77. api_logic_server_cli/prototypes/ont_app/templates/timestamp_template.html +1 -1
  78. api_logic_server_cli/prototypes/sample_ai/logic/declare_logic.py +30 -13
  79. apilogicserver-14.5.3.dist-info/METADATA +168 -0
  80. {apilogicserver-14.4.0.dist-info → apilogicserver-14.5.3.dist-info}/RECORD +84 -61
  81. {apilogicserver-14.4.0.dist-info → apilogicserver-14.5.3.dist-info}/WHEEL +1 -1
  82. api_logic_server_cli/prototypes/basic_demo/apply_customizations.ps1 +0 -17
  83. api_logic_server_cli/prototypes/basic_demo/apply_customizations.sh +0 -14
  84. api_logic_server_cli/prototypes/basic_demo/apply_iteration.ps1 +0 -20
  85. api_logic_server_cli/prototypes/basic_demo/apply_iteration.sh +0 -15
  86. api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/1_langchain_loader.py +0 -19
  87. api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/2_gpt_mcp_prompt.txt +0 -19
  88. api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/README.md +0 -17
  89. api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/resources/curl.txt +0 -4
  90. api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/resources/nw_swagger_3.yaml +0 -16660
  91. api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/run_executor.py +0 -23
  92. apilogicserver-14.4.0.dist-info/METADATA +0 -76
  93. {apilogicserver-14.4.0.dist-info → apilogicserver-14.5.3.dist-info}/entry_points.txt +0 -0
  94. {apilogicserver-14.4.0.dist-info → apilogicserver-14.5.3.dist-info}/licenses/LICENSE +0 -0
  95. {apilogicserver-14.4.0.dist-info → apilogicserver-14.5.3.dist-info}/top_level.txt +0 -0
@@ -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
  ```
@@ -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
@@ -12,21 +12,19 @@ See how to build a complete database system -- in minutes instead of weeks or mo
12
12
 
13
13
  We'll use API Logic Server (open source), providing:
14
14
 
15
- | Key Feature | Providing | Why It Matters|
16
- | :--- |:---|:---|
17
- | **Automation** | Instant Project Creation:<br>An API and an Admin web app | Unblock UI App Dev<br>Instant Agile Collaboration |
18
- | **Customization** | Declarative logic and security <br> 5 rules vs. 200 lines of Python | 40X less backend code |
19
- | **Iteration** | Revising the data model, and <br>Adding rules plus Python | Iterative development <br> Extensiblity with Python |
15
+ | Key Feature | Providing | Why It Matters |
16
+ | :---------------- | :------------------------------------------------------------------ | :-------------------------------------------------- |
17
+ | **Automation** | Instant Project Creation:<br>An API and an Admin web app | Unblock UI App Dev<br>Instant Agile Collaboration |
18
+ | **Customization** | Declarative logic and security <br> 5 rules vs. 200 lines of Python | 40X less backend code |
19
+ | **Iteration** | Revising the data model, and <br>Adding rules plus Python | Iterative development <br> Extensiblity with Python |
20
20
 
21
21
  The entire process takes 10 minutes, instead of several weeks using traditional development.
22
22
 
23
23
  You can use this article in several ways:
24
24
 
25
- * Conceptual Overview - focus on the basic process. Operational details are moved to the Appendix to retain focus on the concepts.
26
-
27
- * Self-demo - you can create this system yourself.
28
-
29
- * Self-demo with video - you can also use [this video](https://www.youtube.com/watch?v=sD6RFp8S6Fg) (it's the same system, but the database is created with ChatGPT).
25
+ - Conceptual Overview - focus on the basic process. Operational details are moved to the Appendix to retain focus on the concepts.
26
+ - Self-demo - you can create this system yourself.
27
+ - Self-demo with video - you can also use [this video](https://www.youtube.com/watch?v=sD6RFp8S6Fg) (it's the same system, but the database is created with ChatGPT).
30
28
 
31
29
  &nbsp;
32
30
 
@@ -45,9 +43,7 @@ This creates a project by reading your schema. The database is Customer, Orders
45
43
  You can open with VSCode, and run it as follows:
46
44
 
47
45
  1. **Create Virtual Environment:** as shown in the Appendix.
48
-
49
46
  2. **Start the Server:** F5 (also described in the Appendix).
50
-
51
47
  3. **Start the Admin App:** either use the links provided in the IDE console, or click [http://localhost:5656/](http://localhost:5656/). The screen shown below should appear in your Browser.
52
48
 
53
49
  The sections below explore the system that has been created (which would be similar for your own database).
@@ -58,7 +54,7 @@ The sections below explore the system that has been created (which would be simi
58
54
 
59
55
  The system creates an API with end points for each table, with filtering, sorting, pagination, optimistic locking and related data access -- **[self-serve](https://apilogicserver.github.io/Docs/API-Self-Serve/), ready for custom app dev.**
60
56
 
61
- <img src="https://github.com/ApiLogicServer/Docs/blob/main/docs/images/basic_demo/api-swagger.jpeg?raw=true">
57
+ ![api-swagger](https://github.com/ApiLogicServer/Docs/blob/main/docs/images/basic_demo/api-swagger.jpeg?raw=true)
62
58
 
63
59
  ### Admin App
64
60
 
@@ -66,32 +62,29 @@ It also creates an Admin App: multi-page, multi-table apps -- ready for **[busin
66
62
 
67
63
  You can click Customer 2, see their Orders, and Items.
68
64
 
69
- <img src="https://github.com/ApiLogicServer/Docs/blob/main/docs/images/basic_demo/admin-app-initial.jpeg?raw=true">
65
+ ![admin-app-initial](https://github.com/ApiLogicServer/Docs/blob/main/docs/images/basic_demo/api-swagger.jpeg?raw=true)
70
66
 
71
67
  ## 2. Customize in your IDE
72
68
 
73
69
  While API/UI automation is a great start, it's critical to enforce logic and security. Here's how.
74
70
 
75
- The follwing `apply_customizations` process simulates adding security to your project, and using your IDE to declare logic and security in `logic/declare_logic.sh` and `security/declare_security.py`. You can diff these files to their created versions, and/or examine the declared logic.
71
+ The follwing *add customizations* process simulates adding security to your project, and using your IDE to declare logic and security in `logic/declare_logic.sh` and `security/declare_security.py`. You can diff these files to their created versions, and/or examine the declared logic.
76
72
 
77
73
  In a terminal window for your project:
78
74
 
79
75
  **1. Stop the Server** (Red Stop button, or Shift-F5 -- see Appendix)
80
76
 
81
- **2. Apply Customizations**
77
+ **2. Add Customizations**
82
78
 
83
79
  ```bash
84
- # mac, linux
85
- sh apply_customizations.sh
86
-
87
- #windows
88
- ./apply_customizations.ps1
80
+ als add-cust
89
81
  ```
82
+
90
83
  &nbsp;
91
84
 
92
85
  ### Declare Security
93
86
 
94
- The `apply_customizations` process above has simulated the `ApiLogicServer add-auth` command, and using your IDE to declare security in `logic/declare_logic.sh`.
87
+ The *add customizations* process above has simulated the `ApiLogicServer add-auth` command, and using your IDE to declare security in `logic/declare_logic.sh`.
95
88
 
96
89
  To see security in action:
97
90
 
@@ -115,7 +108,7 @@ Logic (multi-table derivations and constraints) is a significant portion of a sy
115
108
 
116
109
  Rules are declared in Python, simplified with IDE code completion. The screen below shows the 5 rules for **Check Credit Logic.**
117
110
 
118
- The `apply_customizations` process above has simulated the process of using your IDE to declare logic in `logic/declare_logic.sh`.
111
+ The *add customizations* process above has simulated the process of using your IDE to declare logic in `logic/declare_logic.sh`.
119
112
 
120
113
  To see logic in action:
121
114
 
@@ -169,11 +162,9 @@ Not only are spreadsheet-like rules 40X more concise, they meaningfully simplify
169
162
 
170
163
  The follwing `apply_iteration` process simulates an iteration:
171
164
 
172
- * acquires a new database with `Product.CarbonNeutral`
173
-
174
- * and a revised `ui/admin/admin.yaml` that shows this new column
175
-
176
- * revised logic - in `logic/declare_logic.py`, we replace the 2 lines for the `models.Item.Amount` formula with this (next screenshot shows revised logic executing with breakpoint):
165
+ - acquires a new database with `Product.CarbonNeutral`
166
+ - and a revised `ui/admin/admin.yaml` that shows this new column
167
+ - revised logic - in `logic/declare_logic.py`, we replace the 2 lines for the `models.Item.Amount` formula with this (next screenshot shows revised logic executing with breakpoint):
177
168
 
178
169
  ```python
179
170
  def derive_amount(row: models.Item, old_row: models.Item, logic_row: LogicRow):
@@ -185,8 +176,6 @@ The follwing `apply_iteration` process simulates an iteration:
185
176
  Rule.formula(derive=models.Item.Amount, calling=derive_amount)
186
177
  ```
187
178
 
188
- * issues the `ApiLogicServer rebuild-from-database` command that rebuilds your project (the database models, the api), while preserving the customizations we made above.
189
-
190
179
  In a terminal window for your project:
191
180
 
192
181
  **1. Stop the Server** (Red Stop button, or Shift-F5 -- see Appendix)
@@ -194,12 +183,10 @@ In a terminal window for your project:
194
183
  **2. Apply Iteration**
195
184
 
196
185
  ```bash
197
- # mac, linux
198
- sh apply_iteration.sh
199
-
200
- #windows
201
- ./apply_iteration.ps1
186
+ als add-cust
187
+ als rebuild-from-database
202
188
  ```
189
+
203
190
  &nbsp;
204
191
 
205
192
  **3. Set the breakpoint as shown**
@@ -210,7 +197,7 @@ sh apply_iteration.sh
210
197
 
211
198
  At the breakpoint, note you can use standard debugger services to debug your logic (examine `Item` attributes, step, etc).
212
199
 
213
- <img src="https://github.com/ApiLogicServer/Docs/blob/main/docs/images/basic_demo/logic-debugging.jpeg?raw=true">
200
+ ![logic-debugging](https://github.com/ApiLogicServer/Docs/blob/main/docs/images/basic_demo/logic-debugging.jpeg?raw=true)!
214
201
 
215
202
  &nbsp;
216
203
 
@@ -250,11 +237,9 @@ Note we rebuilt the project from our altered database, illustrating we can **ite
250
237
 
251
238
  Of course, we all know that all businesses the world over depend on the `hello world` app. This is provided in `api/customize_api`. Observe that it's:
252
239
 
253
- * standard Python
254
-
255
- * using Flask
256
-
257
- * and, for database access, SQLAlchemy. Note all updates from custom APIs also enforce your logic.
240
+ - standard Python
241
+ - using Flask
242
+ - and, for database access, SQLAlchemy. Note all updates from custom APIs also enforce your logic.
258
243
 
259
244
  &nbsp;
260
245
 
@@ -270,7 +255,7 @@ API Logic Server also creates scripts for deployment. While these are ***not re
270
255
 
271
256
  ## Summary
272
257
 
273
- <img src="https://github.com/ApiLogicServer/Docs/blob/main/docs/images/basic_demo/summary.jpeg?raw=true">
258
+ ![summary](https://github.com/ApiLogicServer/Docs/blob/main/docs/images/basic_demo/summary.jpeg?raw=true)
274
259
 
275
260
  In minutes - not days or weeks - you've used API Logic Server to convert an idea into **working software,** added **logic and security,** and **iterated** to meet new requirements.
276
261
 
@@ -282,8 +267,9 @@ To dive deeper, you can install [API Logic Server](https://apilogicserver.github
282
267
 
283
268
  ## Appendix: Database Schema
284
269
 
285
- <img src="https://github.com/ApiLogicServer/Docs/blob/main/docs/images/basic_demo/basic_demo_data_model.jpeg?raw=true" width="500">
286
270
 
271
+
272
+ ![basic_demo_data_model](https://github.com/ApiLogicServer/Docs/blob/main/docs/images/basic_demo/basic_demo_data_model.jpeg?raw=true" width="500")
287
273
  &nbsp;
288
274
 
289
275
  ## Appendix: Procedures
@@ -321,28 +307,19 @@ For PyCharm, start the server with CTL-D, Stop with red stop button.
321
307
  To enter a new Order:
322
308
 
323
309
  1. Click `Customer 1``
324
-
325
310
  2. Click `+ ADD NEW ORDER`
326
-
327
311
  3. Set `Notes` to "hurry", and press `SAVE AND SHOW`
328
-
329
312
  4. Click `+ ADD NEW ITEM`
330
-
331
313
  5. Enter Quantity 1, lookup "Product 1", and click `SAVE AND ADD ANOTHER`
332
-
333
314
  6. Enter Quantity 2000, lookup "Product 2", and click `SAVE`
334
-
335
315
  7. Observe the constraint error, triggered by rollups from the `Item` to the `Order` and `Customer`
336
-
337
316
  8. Correct the quantity to 2, and click `Save`
338
317
 
339
-
340
318
  **4. Update the Order**
341
319
 
342
320
  To explore our new logic for green products:
343
321
 
344
322
  1. Access the previous order, and `ADD NEW ITEM`
345
-
346
323
  2. Enter quantity 11, lookup product `Green`, and click `Save`.
347
324
 
348
325
  &nbsp;
@@ -0,0 +1,139 @@
1
+ from flask import request, jsonify
2
+ from flask import Flask, redirect, send_from_directory, send_file
3
+ import logging
4
+ import os
5
+ import json
6
+ import io
7
+
8
+ import requests
9
+ from config.config import Args # circular import error if at top
10
+
11
+ app_logger = logging.getLogger("api_logic_server_app")
12
+
13
+ def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators = []):
14
+ pass
15
+
16
+ def get_server_url():
17
+ """ return the server URL for the OpenAPI spec """
18
+ result = f'http://{Args.instance.swagger_host}:{Args.instance.swagger_port}'
19
+ # get env variable API_LOGIC_SERVER_TUNNEL (or None)
20
+ if tunnel_url := os.getenv("API_LOGIC_SERVER_TUNNEL", None):
21
+ app_logger.info(f".. tunnel URL: {tunnel_url}")
22
+ result = tunnel_url
23
+ return result # + '/api'
24
+
25
+
26
+ @app.before_request
27
+ def before_any_request():
28
+ # print(f"[DEBUG] Incoming request: {request.method} {request.url}")
29
+ if activate_openapi_logging := True:
30
+ if request.content_type == 'application/json' and request.method in ['POST', 'PUT', 'PATCH']:
31
+ # openapi: Incoming request: PATCH http://localhost:5656/api/Customer/1/ {'data': {'attributes': {'credit_limit': 5555}, 'type': 'Customer', 'id': '1'}}
32
+ # openapi: Incoming request: PATCH http://6f6f-2601-644-4900-d6f0-ecc9-6df3-8863-c5b2.ngrok-free.app/api/Customer/1 {'credit_limit': 5555}
33
+
34
+ app_logger.info(f"openapi: Incoming request: {request.method} {request.url} {str(request.json)}")
35
+ else:
36
+ app_logger.info(f"openapi: Incoming request: {request.method} {request.url}")
37
+ # app_logger.info(f"openapi: Incoming request headers: {request.headers}")
38
+
39
+ chatgpt_request_json = {
40
+ "credit_limit": 25000,
41
+ }
42
+ standard_request_json = {
43
+ "data": {
44
+ "type": "Customer",
45
+ "id": "ALFKI",
46
+ "attributes": {
47
+ "name": "Alice",
48
+ "credit_limit": 25000,
49
+ "balance": 12345
50
+ }
51
+ }
52
+ }
53
+ swagger_request_json = {
54
+ 'data': {
55
+ 'attributes': {
56
+ 'credit_limit': 5555
57
+ },
58
+ 'type': 'Customer',
59
+ 'id': '1'
60
+ }
61
+ }
62
+ pass
63
+
64
+
65
+ @app.route('/mcp_server_executor', methods=['GET'])
66
+ def mcp_server_executor(path=None):
67
+ ''' sample response printed in mcp_client_executor.py:
68
+ FIXME - incorrect.
69
+ But do provide: https://localhost:5656/.well-known/mcp.json
70
+ ```
71
+ MCP MCP Response (simulated):
72
+ {
73
+ "get_json": {
74
+ "filter": {
75
+ "filter": {
76
+ "credit_limit": {
77
+ "gt": 4000
78
+ }
79
+ },
80
+ "headers": {
81
+ "Accept": "application/vnd.api+json",
82
+ "Authorization": "Bearer your_token"
83
+ },
84
+ "type": "Customer",
85
+ "url": "http://localhost:5656/api/Customer"
86
+ }
87
+ },
88
+ "name": "mcp_server_executor",
89
+ "openapiUrl": "TUNNEL_URL/api/openapi.json",
90
+ "serverUrl": "TUNNEL_URL/api"
91
+ }
92
+ ```
93
+ '''
94
+ get_json = request.get_json()
95
+ app_logger.info(f"mcp_server_executor sees mcp request: \n{json.dumps(get_json, indent=4)}")
96
+
97
+ # process verb, filter here (stub for now)
98
+ filter_json = get_json['filter'] # {"credit_limit": {"gt": 4000}} # todo: bunch'o parsing here
99
+
100
+ filter_json = {"name": "credit_limit", "op": "gt", "val":4000} # https://github.com/thomaxxl/safrs/wiki/JsonApi-filtering
101
+ filter = json.dumps(filter_json) # {"name": "credit_limit", "op": "gt", "val": 4000}
102
+ get_uri = get_json['url'] + '?filter=' + filter # get_uri = "http://localhost:5656/api/Customer?filter[credit_limit]=1000"
103
+ response = requests.get(url=get_uri, headers= request.headers)
104
+
105
+ return response.json(), 200, {'Content-Type': 'application/json; charset=utf-8'}
106
+
107
+
108
+ @app.route('/.well-known/mcp.json', methods=['GET'])
109
+ def mcp_discovery(path=None):
110
+ ''' called by mcp_client_executor for discovery, eg:
111
+ ```
112
+ {
113
+ "tool_type": "json-api",
114
+ "schema_version": "1.0",
115
+ "base_url": "https://crm.company.com",
116
+ "resources": [
117
+ {
118
+ "name": "Customer",
119
+ "path": "/Customer",
120
+ "methods": ["GET", "PATCH"],
121
+ "fields": ["id", "name", "balance", "credit_limit"],
122
+ "filterable": ["name", "credit_limit"],
123
+ "example": "List customers with credit over 5000"
124
+ }
125
+ ]
126
+ }
127
+ ```
128
+ test: curl -X GET "http://localhost:5656/.well-known/mcp.json"
129
+ '''
130
+ # return docs/mcp_schema.json
131
+ schema_path = os.path.join(project_dir, "docs", "mcp_schema.json")
132
+ try:
133
+ with open(schema_path, "r") as schema_file:
134
+ schema = json.load(schema_file)
135
+ return jsonify(schema), 200
136
+ except Exception as e:
137
+ app_logger.error(f"Error loading MCP schema: {e}")
138
+ return jsonify({"error": "MCP schema not found"}), 404
139
+ pass
@@ -0,0 +1,92 @@
1
+ from flask import request, jsonify
2
+ from flask import Flask, redirect, send_from_directory, send_file
3
+ import logging
4
+ import os
5
+ import json
6
+ import io
7
+ from config.config import Args # circular import error if at top
8
+
9
+ app_logger = logging.getLogger("api_logic_server_app")
10
+
11
+
12
+
13
+ def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators = []):
14
+ pass
15
+
16
+ def get_server_url():
17
+ """ return the server URL for the OpenAPI spec """
18
+ result = f'http://{Args.instance.swagger_host}:{Args.instance.swagger_port}'
19
+ # get env variable API_LOGIC_SERVER_TUNNEL (or None)
20
+ if tunnel_url := os.getenv("API_LOGIC_SERVER_TUNNEL", None):
21
+ app_logger.info(f".. tunnel URL: {tunnel_url}")
22
+ result = tunnel_url
23
+ return result # + '/api'
24
+
25
+ @app.route('/mcp.json')
26
+ def mcp(path=None):
27
+ '''
28
+ test: curl -X GET http://localhost:5656/mcp.json
29
+ '''
30
+
31
+ mcp_json = {
32
+ "name": "MCP genai_demo test",
33
+ "description": "You are an AI Planner + Executor for a live JSON:API server.",
34
+ "instructions": [
35
+ "When a user gives you a natural language goal (e.g., 'list customers from Germany'), you:",
36
+ "Identify the resource (Customer, Order, Product).",
37
+ "Map filters (e.g., Country=Germany).",
38
+ "Construct a JSON:API call to the live endpoint (through a function called fetch_resource).",
39
+ "Execute the live API call through the function.",
40
+ "Format and display the results neatly."
41
+ ],
42
+ "serverUrl": "http://localhost:5656/api",
43
+ "openapiUrl": "http://localhost:5656/api/openapi.json",
44
+ "apiStandard": "JSON:API (application/vnd.api+json)"
45
+ }
46
+ mcp_json["serverUrl"] = get_server_url() + '/api'
47
+ mcp_json["openapiUrl"] = get_server_url() + '/api/openapi.json'
48
+ # return jsonify(mcp), 200, {'Content-Type': 'text/plain; charset=utf-8'}
49
+ return jsonify(mcp_json), 200, {'Content-Type': 'application/json; charset=utf-8'}
50
+
51
+
52
+ @app.route('/api/openapi.json')
53
+ def openapi(path=None):
54
+ """ return integration/openai_plugin/swagger_3.json
55
+ * with updated tunnel URL if API_LOGIC_SERVER_TUNNEL is set
56
+
57
+ test: curl -X GET http://localhost:5656/api/openapi.json
58
+ like: curl -X GET http://localhost:5656/api/swagger.json
59
+ """
60
+
61
+ # read dict from json file integration/openai_plugin/swagger_3.json
62
+ with open("integration/openai_function/swagger_3.json", "r") as json_file:
63
+ swagger_dict = json.load(json_file)
64
+ app_logger.info(f"openapi: Swagger JSON loaded: {swagger_dict}")
65
+
66
+ server_url = get_server_url()
67
+ swagger_dict["servers"][0]["url"] = server_url + '/api'
68
+
69
+ # convert dict to buffered stream
70
+ swagger_dict_mem = io.BytesIO(json.dumps(swagger_dict).encode('utf-8'))
71
+ return send_file(swagger_dict_mem, mimetype='text/json')
72
+
73
+
74
+ @app.route('/api/ai_plugin')
75
+ def ai_plugin(path=None):
76
+ """ return integration/openai_plugin/ai_plugin.json (disparaged)
77
+ * with updated tunnel URL if API_LOGIC_SERVER_TUNNEL is set
78
+
79
+ test: curl -X GET http://localhost:5656/api/ai_plugin
80
+ """
81
+
82
+ # read dict from json file integration/openai_plugin/swagger_3.json
83
+ with open("integration/openai_plugin/ai_plugin.json", "r") as json_file:
84
+ swagger_dict = json.load(json_file)
85
+ app_logger.info(f"openapi: ai_plugin JSON loaded: {swagger_dict}")
86
+
87
+ server_url = get_server_url()
88
+ swagger_dict["servers"][0]["url"] = server_url
89
+
90
+ # convert dict to buffered stream
91
+ swagger_dict_mem = io.BytesIO(json.dumps(swagger_dict).encode('utf-8'))
92
+ return send_file(swagger_dict_mem, mimetype='text/json')
@@ -0,0 +1,13 @@
1
+ SECRET_KEY = "whatnothow"
2
+ SQLALCHEMY_TRACK_MODIFICATIONS = False
3
+ SQLAlCHEMY_ECHO = False
4
+ # AGGREGATE_DEFAULTS = True
5
+ # ALL_DEFAULTS = True
6
+ # APILOGICPROJECT_KAFKA_PRODUCER = "{\"bootstrap.servers\": \"localhost:9092\"}"
7
+ # SQLALCHEMY_DATABASE_URI=db.sqlite
8
+
9
+ SECURITY_ENABLED = false
10
+
11
+ # if using tunnel for mcp, function, or ai_plugin
12
+ # eg, https://tunnel_url.ngrok-free.app
13
+ API_LOGIC_SERVER_TUNNEL = "TUNNEL_URL"