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='./'):