bedrock.generators.generate-endpoints-for-terraform

This script generates the OpenAPI specification for the Bedrock application.

  1"""
  2This script generates the OpenAPI specification for the Bedrock application.
  3"""
  4
  5import json
  6import os
  7import sys
  8from pathlib import Path
  9
 10from bedrock._helpers.string import camelCase_to_snake_case, camelCase_to_kebab_case
 11from bedrock.config._modules import _get_mappings, _get_workers
 12from bedrock.endpoints.endpoint import Endpoint
 13from bedrock.workers.worker import Worker
 14
 15BASE_PATH = None
 16
 17
 18def make_endpoints_json(endpoint_instances):
 19    """
 20    Runs through each endpoint and generates a JSON object with the endpoint name and kafka listeners.
 21    The Kafka listener is obtained from the `@kafka_listener` decorator on the endpoint class.
 22
 23    Result example:
 24    ```json
 25    {
 26        "planets": {
 27            "name": "planets",
 28            "mskTopicsInbound": ["planet-changes"],
 29            "type": "python",
 30            "runtime": "python3.13",
 31            "zipName": "planets.zip"
 32        }
 33    }
 34    ```
 35    """
 36    endpoints = {}
 37    for endpoint in endpoint_instances:
 38        if issubclass(endpoint.__class__, Endpoint):
 39            kafka_topics = getattr(endpoint, "__kafka_topics__", [])
 40            uses_websockets = getattr(endpoint, "__uses_websockets__", False)
 41            snake_case_name = camelCase_to_snake_case(endpoint.__class__.__name__)
 42            kebab_case_name = camelCase_to_kebab_case(endpoint.__class__.__name__)
 43            endpoints[snake_case_name] = {
 44                "name": kebab_case_name,
 45                "mskTopicsInbound": kafka_topics,
 46                "msk_topics_inbound": kafka_topics,  # backwards compatibility
 47                "uses_websockets": uses_websockets,
 48                "type": "python",
 49                "runtime": "python3.13",
 50                "zipName": f"{snake_case_name}.zip"
 51            }
 52
 53    return endpoints
 54
 55
 56def make_workers_json(worker_dict: dict, workers_path):
 57    """
 58    Runs through each worker and generates a JSON object with the worker name.
 59
 60    Result example:
 61    ```json
 62    # For a worker in workers/fetch-systems/
 63    {
 64        "fetch_systems": {
 65            "name": "fetch-systems",
 66            "type": "python",
 67            "runtime": "python3.13",
 68            "needsDependencies": true,
 69            "zipName": "worker_fetch-systems.zip",
 70            "layerZipName": "worker_layer_fetch-systems.zip"
 71        }
 72    }
 73    ```
 74    """
 75    workers_json = {}
 76    for worker_dir_name, worker in worker_dict.items():
 77        if issubclass(worker.__class__, Worker):
 78            snake_case_name = camelCase_to_snake_case(worker.__class__.__name__)
 79            kebab_case_name = camelCase_to_kebab_case(worker.__class__.__name__)
 80            if worker.__worker_type__ == "python":
 81                worker_dependency_file = f"{workers_path}/{worker_dir_name}/requirements.txt"
 82                runtime = "python3.13"
 83            elif worker.__worker_type__ == "javascript":
 84                worker_dependency_file = f"{workers_path}/{worker_dir_name}/package.json"
 85                runtime = "nodejs22.x"
 86            else:
 87                print(f"🔴 [ERROR] Unknown worker type {worker.__worker_type__}")
 88                sys.exit(1)
 89
 90            dependencies = []
 91            print("🔵 [INFO] Checking dependencies...")
 92            print(worker_dependency_file)
 93            if os.path.isfile(worker_dependency_file):
 94                with open(worker_dependency_file) as f:
 95                    worker_dependency_file_contents = f.read()
 96                print(worker_dependency_file_contents)
 97                if worker.__worker_type__ == "python":
 98                    dependencies = [d for d in worker_dependency_file_contents.split("\n") if
 99                                    d and not d.startswith("#") and not d.startswith("boto")]
100                elif worker.__worker_type__ == "javascript":
101                    print([d for d in json.loads(worker_dependency_file_contents)["dependencies"] if
102                           not d.startswith("boto")])
103                    print(json.loads(worker_dependency_file_contents))
104                    dependencies = [d for d in json.loads(worker_dependency_file_contents)["dependencies"] if
105                                    not d.startswith("boto")]
106
107            workers_json[snake_case_name] = {
108                "name": kebab_case_name,
109                "type": worker.__worker_type__,
110                "runtime": runtime,
111                "needsDependencies": len(dependencies) > 0,
112                "zipName": f"worker_{worker_dir_name}.zip",
113                "layerZipName": f"worker_layer_{worker_dir_name}.zip" if len(dependencies) > 0 else ""
114            }
115
116    return workers_json
117
118
119def dump_dict_to_file(filename: str, d: dict, folder="./"):
120    directory = f"{folder}/".replace("//", "/")
121    Path(directory).mkdir(parents=True, exist_ok=True)
122    with open(f"{directory}{filename}", "w") as f:
123        json.dump(d, f, indent=2)
124
125
126if __name__ == '__main__':
127    BASE_PATH = sys.argv[1]
128    print("🔵 [INFO] Generating endpoints.json...")
129    mappings = _get_mappings(f"{BASE_PATH}/app/endpoints")
130    dump_dict_to_file("endpoints.json", make_endpoints_json(mappings.values()), f"{BASE_PATH}/tf.support.files/")
131    print("🔵 [INFO] Generating workers.json...")
132    workers = _get_workers(f"{BASE_PATH}/app/workers")
133    dump_dict_to_file("workers.json", make_workers_json(workers, f"{BASE_PATH}/app/workers"),
134                      f"{BASE_PATH}/tf.support.files/")
135    print("🟢 [SUCCESS] Done!")
BASE_PATH = None
def make_endpoints_json(endpoint_instances):
19def make_endpoints_json(endpoint_instances):
20    """
21    Runs through each endpoint and generates a JSON object with the endpoint name and kafka listeners.
22    The Kafka listener is obtained from the `@kafka_listener` decorator on the endpoint class.
23
24    Result example:
25    ```json
26    {
27        "planets": {
28            "name": "planets",
29            "mskTopicsInbound": ["planet-changes"],
30            "type": "python",
31            "runtime": "python3.13",
32            "zipName": "planets.zip"
33        }
34    }
35    ```
36    """
37    endpoints = {}
38    for endpoint in endpoint_instances:
39        if issubclass(endpoint.__class__, Endpoint):
40            kafka_topics = getattr(endpoint, "__kafka_topics__", [])
41            uses_websockets = getattr(endpoint, "__uses_websockets__", False)
42            snake_case_name = camelCase_to_snake_case(endpoint.__class__.__name__)
43            kebab_case_name = camelCase_to_kebab_case(endpoint.__class__.__name__)
44            endpoints[snake_case_name] = {
45                "name": kebab_case_name,
46                "mskTopicsInbound": kafka_topics,
47                "msk_topics_inbound": kafka_topics,  # backwards compatibility
48                "uses_websockets": uses_websockets,
49                "type": "python",
50                "runtime": "python3.13",
51                "zipName": f"{snake_case_name}.zip"
52            }
53
54    return endpoints

Runs through each endpoint and generates a JSON object with the endpoint name and kafka listeners. The Kafka listener is obtained from the @kafka_listener decorator on the endpoint class.

Result example:

{
    "planets": {
        "name": "planets",
        "mskTopicsInbound": ["planet-changes"],
        "type": "python",
        "runtime": "python3.13",
        "zipName": "planets.zip"
    }
}
def make_workers_json(worker_dict: dict, workers_path):
 57def make_workers_json(worker_dict: dict, workers_path):
 58    """
 59    Runs through each worker and generates a JSON object with the worker name.
 60
 61    Result example:
 62    ```json
 63    # For a worker in workers/fetch-systems/
 64    {
 65        "fetch_systems": {
 66            "name": "fetch-systems",
 67            "type": "python",
 68            "runtime": "python3.13",
 69            "needsDependencies": true,
 70            "zipName": "worker_fetch-systems.zip",
 71            "layerZipName": "worker_layer_fetch-systems.zip"
 72        }
 73    }
 74    ```
 75    """
 76    workers_json = {}
 77    for worker_dir_name, worker in worker_dict.items():
 78        if issubclass(worker.__class__, Worker):
 79            snake_case_name = camelCase_to_snake_case(worker.__class__.__name__)
 80            kebab_case_name = camelCase_to_kebab_case(worker.__class__.__name__)
 81            if worker.__worker_type__ == "python":
 82                worker_dependency_file = f"{workers_path}/{worker_dir_name}/requirements.txt"
 83                runtime = "python3.13"
 84            elif worker.__worker_type__ == "javascript":
 85                worker_dependency_file = f"{workers_path}/{worker_dir_name}/package.json"
 86                runtime = "nodejs22.x"
 87            else:
 88                print(f"🔴 [ERROR] Unknown worker type {worker.__worker_type__}")
 89                sys.exit(1)
 90
 91            dependencies = []
 92            print("🔵 [INFO] Checking dependencies...")
 93            print(worker_dependency_file)
 94            if os.path.isfile(worker_dependency_file):
 95                with open(worker_dependency_file) as f:
 96                    worker_dependency_file_contents = f.read()
 97                print(worker_dependency_file_contents)
 98                if worker.__worker_type__ == "python":
 99                    dependencies = [d for d in worker_dependency_file_contents.split("\n") if
100                                    d and not d.startswith("#") and not d.startswith("boto")]
101                elif worker.__worker_type__ == "javascript":
102                    print([d for d in json.loads(worker_dependency_file_contents)["dependencies"] if
103                           not d.startswith("boto")])
104                    print(json.loads(worker_dependency_file_contents))
105                    dependencies = [d for d in json.loads(worker_dependency_file_contents)["dependencies"] if
106                                    not d.startswith("boto")]
107
108            workers_json[snake_case_name] = {
109                "name": kebab_case_name,
110                "type": worker.__worker_type__,
111                "runtime": runtime,
112                "needsDependencies": len(dependencies) > 0,
113                "zipName": f"worker_{worker_dir_name}.zip",
114                "layerZipName": f"worker_layer_{worker_dir_name}.zip" if len(dependencies) > 0 else ""
115            }
116
117    return workers_json

Runs through each worker and generates a JSON object with the worker name.

Result example:

# For a worker in workers/fetch-systems/
{
    "fetch_systems": {
        "name": "fetch-systems",
        "type": "python",
        "runtime": "python3.13",
        "needsDependencies": true,
        "zipName": "worker_fetch-systems.zip",
        "layerZipName": "worker_layer_fetch-systems.zip"
    }
}
def dump_dict_to_file(filename: str, d: dict, folder='./'):
120def dump_dict_to_file(filename: str, d: dict, folder="./"):
121    directory = f"{folder}/".replace("//", "/")
122    Path(directory).mkdir(parents=True, exist_ok=True)
123    with open(f"{directory}{filename}", "w") as f:
124        json.dump(d, f, indent=2)