# Architecture ## Summary The API wrapper is a schema-driven Flask service. Flask provides HTTP routing and request handling, while S3 JSON schemas define most endpoint behavior. The same Python pipeline handles all configured endpoints. The main architectural idea is: ```text HTTP request -> dynamic Flask route -> authentication -> endpoint schema selection from S3 -> init functions -> JSON schema validation -> business rules -> post-validation functions -> persistence -> schema-defined response ``` This means endpoint behavior can change through S3 schema updates even when Python code is unchanged. ## Main Runtime Components | Component | File | Responsibility | | --- | --- | --- | | Flask app | `app.py` | Creates the app, registers routes, adds response headers | | Dynamic routing | `routes.py` | Reads route schema from S3 and registers endpoints | | Request orchestrator | `main.py` | Runs the end-to-end request pipeline | | Authentication | `authentication.py` | Validates host, auth mode, credentials, JWT, and optional IP whitelist | | Schema loading | `utilities/schema_loader.py` | Reads route/auth/endpoint schemas from S3 and uploads files to S3 | | Validation | `schema_validation.py` | Validates request body with `jsonschema` | | Rules | `rules_aggregator.py` | Evaluates schema-defined conditional rule blocks | | Functions | `dry_run_function.py`, `helper_functions.py` | Executes schema-defined function blocks | | Operations | `operations.py` | Executes reusable operation dictionaries | | Key resolution | `key_resolver.py`, `components.py` | Resolves dynamic schema values and component calls | | Persistence | `data_persistence.py`, `db/*` | Executes schema-defined MySQL operations | | Response generation | `response/response_generation.py` | Builds schema-defined response content and status | | Business helpers | `static_functions.py` | Static utility functions referenced by schemas | | External integrations | `integrations/karza.py` | Karza electricity verification and report generation | | Logging | `logger_module/package_logger.py` | Console and CloudWatch logging | ## Request Lifecycle ### 1. App Startup `app.py` creates the Flask app and calls: ```python Routes().main(app, app_name) ``` `app_name` currently contains: ```python ['test1', 'mconnect'] ``` `Routes.main()` loads route definitions from: ```text api_wrapper/api/routes.json ``` For every endpoint in the enabled app groups, it registers a Flask route dynamically. Every route delegates to: ```python APIWrapper(request).main() ``` ### 2. Request Preparation And Authentication `APIWrapper.main()` starts by logging the request and calling: ```python AuthenticationSchema().auth(request) ``` Authentication does the following: 1. Extracts the host prefix from the `Host` header. 2. Loads `api_wrapper/authentication/authentication_schema.json` from S3. 3. Confirms the host exists in the auth schema. 4. Loads route config and checks whether auth is required for the requested path. 5. Runs supported auth modes from route config and auth schema. 6. Optionally checks IP whitelisting. 7. Loads merchant details when merchant authentication succeeds. 8. Stores request, merchant, and token data in Flask `g`. GET requests are converted from query params into a request-style dictionary. POST requests use the JSON body. ### 3. Endpoint Schema Selection After auth, `main.py` selects an endpoint behavior schema from S3. Admin schema path: ```text api_wrapper/{source}/{domain}/category/default/default.json ``` Merchant-level candidate: ```text api_wrapper/merchant/{merchant_id}/{domain}/{merchant_name}.json ``` Category-level candidate: ```text api_wrapper/category/{category_id}/{domain}/{category_name}.json ``` Merchant-level schema is preferred. If it does not exist, the service checks for category-level schema. ### 4. Function Initialization Before validation, `DryRunFunction(...).main('functions', 'init')` executes the schema's `functions.init` block. This is typically used to prepare state values that validation, rules, persistence, or responses may depend on. ### 5. Request Validation `SchemaLoading.schema_validation()` validates `request.get_json` against: ```text paths.{endpoint}.{method}.requestBody.content ``` On success it returns `CF200`. On validation failure it attempts to resolve a schema-defined error message and return code, stores it as `response` state, and returns a failure `cf_code`. ### 6. Rule Evaluation `RulesAggregator.main()` reads: ```text paths.{endpoint}.{method}.requestBody.rules.data ``` The rule engine evaluates conditions through `condition_check()`. When a condition branch resolves to an operation, `Operations.perform_operation()` executes it. Rules can short-circuit the request by returning a non-success response operation. ### 7. Post-Validation Functions After rules pass, `DryRunFunction(...).main('functions')` executes all configured functions except `init`. These function blocks can call operations, nested condition blocks, built-in helpers, or static functions. ### 8. Persistence `DataPersistence.main()` reads: ```text paths.{endpoint}.{method}.persistence ``` Each persistence block can run unconditionally or behind conditions. Persistence delegates DB work to `DatabaseOperations`, which builds and executes insert, update, select, or raw queries. ### 9. Response Generation `ResponseGeneration.generate_response(cf_code)` reads: ```text paths.{endpoint}.{method}.responses.{cf_code} ``` It resolves dynamic response values, executes optional response actions, stores the final response in Flask `g`, and returns: ```python (content, status_code) ``` ## Request-Scoped State The service uses Flask `g` as request-scoped state. `helper_functions.set_data()` stores values, and `helper_functions.get_data()` retrieves them. Common state values include: - `request` - `token` - `merchant_details` - `schema_path` - inserted auto IDs such as `{table}_{auto_id}` - intermediate function outputs - final `response` This state is only valid for the current request. ## Error And Response Codes The Python pipeline uses internal `cf_code` values to select response blocks from the schema. Common codes in the codebase: - `CF200`: success - `CF300`: validation or business-rule failure - `CF500`: internal error or schema validation fallback failure The actual API response body and HTTP status are controlled by the endpoint schema response block. ## Design Implications - Adding a normal endpoint usually requires S3 schema changes, not a new Flask route function. - Adding a new reusable behavior usually means adding a new operation or `StaticFunctions` method, then referencing it from schema. - S3 schema changes should be reviewed like code because they can change auth, validation, DB writes, API calls, and response behavior. - The repository is only half of the service contract. The S3 schemas are required to fully understand endpoint behavior.