diff --git a/README.md b/README.md new file mode 100644 index 0000000..18d754c --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ + +https://www.asyncapi.com/docs/tools/generator/generator-template#overview-of-steps + +asyncapi generate fromTemplate openapi_templete/test/fixtures/asyncapi_test.yaml ./openapi_templete -o ./generated-code --force-write \ No newline at end of file diff --git a/asyncapi.yaml b/asyncapi.yaml index cc2b749..29a5ffb 100644 --- a/asyncapi.yaml +++ b/asyncapi.yaml @@ -1,8 +1,12 @@ -# .venv/bin/asyncapi-python-codegen asyncapi.yaml out --force +# .venv/bin/asyncapi-python-codegen asyncapi.yaml out --force-write # kouknout na FastStream # https://nats.io/blog/nats-supported-by-faststream/#writing-app-code # https://github.com/asyncapi/python-paho-template # https://www.asyncapi.com/docs/tools/generator/generator-template +# https://www.asyncapi.com/docs/tools/generator/generator-template --force-write +# https://pypi.org/project/asyncapi-python/ +# https://github.com/G-USI/asyncapi-python + asyncapi: 3.0.0 info: diff --git a/my_template/package.json b/my_template/package.json new file mode 100644 index 0000000..41430b8 --- /dev/null +++ b/my_template/package.json @@ -0,0 +1,24 @@ +{ + "name": "@your-org/asyncapi-python-template", + "version": "1.0.0", + "description": "Custom Python async client generator for AsyncAPI", + "generator": { + "supportedProtocols": ["mqtt", "kafka", "nats"], + "parameters": { + "client_name": { + "description": "Name of the generated client class", + "default": "AsyncClient", + "required": false + }, + "async_mode": { + "description": "Enable async/await support", + "default": true, + "required": false + } + } + }, + "dependencies": { + "@asyncapi/generator-react-sdk": "^1.1.0", + "@asyncapi/modelina": "^4.0.0" + } +} diff --git a/my_template/template/components/client.py b/my_template/template/components/client.py new file mode 100644 index 0000000..e69de29 diff --git a/my_template/template/components/models.py b/my_template/template/components/models.py new file mode 100644 index 0000000..e69de29 diff --git a/my_template/template/components/publisher.py b/my_template/template/components/publisher.py new file mode 100644 index 0000000..e69de29 diff --git a/my_template/template/hooks/befoje.js b/my_template/template/hooks/befoje.js new file mode 100644 index 0000000..9b0e303 --- /dev/null +++ b/my_template/template/hooks/befoje.js @@ -0,0 +1,8 @@ +module.exports = { + 'generate:before': ({ asyncapi, templateParams = {} }) => { + // Modify the AsyncAPI document before generation + if (templateParams.version) { + asyncapi._json.info.version = templateParams.version; + } + } +}; diff --git a/my_template/template/index.js b/my_template/template/index.js new file mode 100644 index 0000000..7e7e855 --- /dev/null +++ b/my_template/template/index.js @@ -0,0 +1,56 @@ +import { File, Text } from "@asyncapi/generator-react-sdk"; +import { PythonGenerator, FormatHelpers } from "@asyncapi/modelina"; + +export default async function({ asyncapi, params, originalAsyncAPI }) { + const files = []; + + // Generate Python data models using Modelina + const pythonGenerator = new PythonGenerator(); + const models = await pythonGenerator.generate(asyncapi); + + // Create models.py file + const modelsContent = models.map(model => + `# ${model.modelName}\n${model.result}\n` + ).join('\n'); + + files.push( + + """Auto-generated Python models from AsyncAPI spec.""" + {modelsContent} + + ); + + // Generate async client + files.push( + + {generateClientCode(asyncapi, params)} + + ); + + return files; +} + +function generateClientCode(asyncapi, params) { + const clientName = params.client_name || "AsyncClient"; + const servers = asyncapi.servers().all(); + + let code = `"""Auto-generated async client for ${asyncapi.info().title()}"""\n\n`; + code += `import asyncio\nimport aiohttp\n\n`; + code += `class ${clientName}:\n`; + code += ` """Async client for ${asyncapi.info().title()}"""\n\n`; + code += ` def __init__(self, host: str):\n`; + code += ` self.host = host\n`; + code += ` self.session = None\n\n`; + + // Add methods for each channel/operation + asyncapi.channels().all().forEach(channel => { + const channelName = FormatHelpers.toCamelCase(channel.address() || channel.name()); + code += ` async def ${channelName}_publish(self, payload):\n`; + code += ` """Publish to ${channel.name()}"""\n`; + code += ` if not self.session:\n`; + code += ` self.session = aiohttp.ClientSession()\n`; + code += ` # Implementation for ${channel.name()}\n\n`; + }); + + return code; +} diff --git a/my_template/template/package.json b/my_template/template/package.json new file mode 100644 index 0000000..063dc50 --- /dev/null +++ b/my_template/template/package.json @@ -0,0 +1,8 @@ +{ + "name": "python-async-template", + "generator": {}, + "dependencies": { + "@asyncapi/generator-react-sdk": "^1.1.0", + "@asyncapi/modelina": "^4.0.0" + } +} diff --git a/openapi_templete/package.json b/openapi_templete/package.json index d31abd3..3c5ac90 100644 --- a/openapi_templete/package.json +++ b/openapi_templete/package.json @@ -1,11 +1,11 @@ { "name": "python-mqtt-client-template", "version": "0.0.1", - "description": "A template that generates a Python FastStream NATS client.", + "description": "A template that generates a Python MQTT client using MQTT.", "generator": { - "apiVersion": "v1", + "apiVersion": "v3", "generator": ">=2.0.0 <4.0.0", - "supportedProtocols": ["faststream", "nats"] + "supportedProtocols": ["mqtt"] }, "dependencies": { "@asyncapi/generator-react-sdk": "^0.2.25" diff --git a/openapi_templete/template/index.js b/openapi_templete/template/index.js index 2bd0a36..fe1216e 100644 --- a/openapi_templete/template/index.js +++ b/openapi_templete/template/index.js @@ -1,7 +1,31 @@ -//1 -import { File } from '@asyncapi/generator-react-sdk' -//2 +// //1 +// import { File } from '@asyncapi/generator-react-sdk' +// //2 +// export default function ({ asyncapi }) { +// //3 +// return {asyncapi.info().title()} +// } + + + +import { File } from '@asyncapi/generator-react-sdk'; + export default function ({ asyncapi }) { -//3 - return {asyncapi.info().title()} -} + return ( + + {`import paho.mqtt.client as mqtt + +mqttBroker = "test.mosquitto.org" + +class TemperatureServiceClient: + def __init__(self): + self.client = mqtt.Client() + self.client.connect(mqttBroker) + + + def sendTemperatureChange(self, id): + topic = "temperature/changed" + self.client.publish(topic, id)`} + + ) +} \ No newline at end of file diff --git a/openapi_templete/template/index2.js b/openapi_templete/template/index2.js new file mode 100644 index 0000000..2b49052 --- /dev/null +++ b/openapi_templete/template/index2.js @@ -0,0 +1,29 @@ +// //1 +// import { File } from '@asyncapi/generator-react-sdk' +// //2 +// export default function ({ asyncapi }) { +// //3 +// return {asyncapi.info().title()} +// } + + + +import { File } from '@asyncapi/generator-react-sdk'; + +export default function ({ asyncapi, params }) { + const channels = Object.keys(asyncapi.components()); + + let content = '' + for (const channel of channels) { + content += `# channel: ${channel}\n` + } + return ( + # {asyncapi.info().title()} + {`\n`} + # {asyncapi.info().description()} + {`\n`} + {channels.map((ch) => `# channel: ${ch}\n`)} + {content} + + ) +} diff --git a/openapi_templete/test/fixtures/asyncapi.yaml b/openapi_templete/test/fixtures/asyncapi.yaml index 3fb7241..de3b507 100644 --- a/openapi_templete/test/fixtures/asyncapi.yaml +++ b/openapi_templete/test/fixtures/asyncapi.yaml @@ -1,44 +1,35 @@ -# .venv/bin/asyncapi-python-codegen asyncapi.yaml out --force -# asyncapi generate fromTemplate ./test/fixtures/asyncapi.yaml ./ -o ./out/project -# kouknout na FastStream -# https://nats.io/blog/nats-supported-by-faststream/#writing-app-code -# https://github.com/asyncapi/python-paho-template -# https://www.asyncapi.com/docs/tools/generator/generator-template - asyncapi: 3.0.0 - info: title: Temperature Service version: 1.0.0 - description: This service is in charge of processing all the events related to temperature. - + description: Temperature sensor publishing readings servers: - dev: - host: test.mosquitto.org #in case you're using local mosquitto instance, change this value to localhost. - protocol: mqtt - + production: + host: localhost:4222 + protocol: nats channels: - temperatureChanged: - address: temperature/changed + temperature/read: + address: temperature/read messages: - temperatureChange: - description: Message that is being sent when the temperature in the bedroom changes. - payload: - $ref: '#/components/schemas/temperatureId' - description: Updates the bedroom temperature in the database when the temperatures drops or goes up. - + TemperatureMessage: + $ref: '#/components/messages/TemperatureMessage' operations: - temperatureChange: + receiveTemperature: action: receive - summary: Message sent to the broker when the temperature is changed. channel: - $ref: '#/channels/temperatureChanged' - + $ref: '#/channels/temperature/read' + description: Receive temperature readings components: - schemas: - temperatureId: - type: object - additionalProperties: false - properties: - temperatureId: - type: string \ No newline at end of file + messages: + TemperatureMessage: + name: TemperatureMessage + title: Temperature Message + payload: + type: object + properties: + value: + type: number + timestamp: + type: string + sensor_id: + type: string \ No newline at end of file diff --git a/openapi_templete/test/fixtures/asyncapi_test.yaml b/openapi_templete/test/fixtures/asyncapi_test.yaml new file mode 100644 index 0000000..29a5ffb --- /dev/null +++ b/openapi_templete/test/fixtures/asyncapi_test.yaml @@ -0,0 +1,70 @@ +# .venv/bin/asyncapi-python-codegen asyncapi.yaml out --force-write +# kouknout na FastStream +# https://nats.io/blog/nats-supported-by-faststream/#writing-app-code +# https://github.com/asyncapi/python-paho-template +# https://www.asyncapi.com/docs/tools/generator/generator-template +# https://www.asyncapi.com/docs/tools/generator/generator-template --force-write +# https://pypi.org/project/asyncapi-python/ +# https://github.com/G-USI/asyncapi-python + + +asyncapi: 3.0.0 +info: + title: Job Events + version: 1.0.0 + +servers: + nats_localhost: + host: localhost:4222 + protocol: nats + +channels: + run_test: + address: mtr_test.run_test + messages: + run_test: + $ref: '#/components/messages/mtr_request' + return_uuid: + address: null + messages: + run_test: + $ref: '#/components/messages/mtr_uuid' + +operations: + get_mtr_test: + action: send + channel: + $ref: '#/channels/run_test' + reply: + address: + location: "$message.header#/replayTo" + channel: + $ref: '#/channels/return_uuid' + +components: + schemas: + mtr_request_schema: + payload: + type: object + properties: + to_server: + type: string + mtr_sleep: + type: integer + mtr_uuid_schema: + payload: + type: object + properties: + uuid: + type: string + messages: + mtr_request: + name: mtr_request + payload: + type: object +# required: +# - to_server + $ref: '#/components/schemas/mtr_request_schema' + mtr_uuid: + payload: + $ref: '#/components/schemas/mtr_uuid_schema' diff --git a/openapi_templete/test/project/client.py b/openapi_templete/test/project/client.py new file mode 100644 index 0000000..b2aaa6c --- /dev/null +++ b/openapi_templete/test/project/client.py @@ -0,0 +1,18 @@ +# 1 +import paho.mqtt.client as mqtt +# 2 +mqttBroker = "test.mosquitto.org" + +class TemperatureServiceClient: + def __init__(self): + # 3 + self.client = mqtt.Client() + # 4 + self.client.connect(mqttBroker) + + + def sendTemperatureChange(self, id): + # 5 + topic = "temperature/changed" + # 6 + self.client.publish(topic, id) diff --git a/openapi_templete/test/project/test.py b/openapi_templete/test/project/test.py new file mode 100644 index 0000000..b866cea --- /dev/null +++ b/openapi_templete/test/project/test.py @@ -0,0 +1,16 @@ +from client import TemperatureServiceClient +from random import randrange +import time + +client = TemperatureServiceClient() + +id_length = 8 +min_value = 10**(id_length-1) # Minimum value with 8 digits (e.g., 10000000) +max_value = 10**id_length - 1 # Maximum value with 8 digits (e.g., 99999999) + +while True: + randomId = randrange(min_value, max_value + 1) + client.sendTemperatureChange(randomId) + print("New temperature detected " + str(randomId) + " sent to temperature/changed") + time.sleep(1) + \ No newline at end of file diff --git a/uv.lock b/uv.lock index ee3578e..d57dfcb 100644 --- a/uv.lock +++ b/uv.lock @@ -225,7 +225,7 @@ wheels = [ [[package]] name = "datamodel-code-generator" -version = "0.57.0" +version = "0.59.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argcomplete" }, @@ -237,9 +237,9 @@ dependencies = [ { name = "pydantic" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/44/87d5980f813a1e323c5d726b3ac5fec8c915ce8a77fcdceaf9c00457dbae/datamodel_code_generator-0.57.0.tar.gz", hash = "sha256:0eda778ea06eaa476e542a5f1fe1d14cc3bbf686edb33a0ad6151c7d19089906", size = 932941 } +sdist = { url = "https://files.pythonhosted.org/packages/8d/50/21488efdb515afc04039e19d876ee668d85a5c9590a3a4bb25b27f2b99c0/datamodel_code_generator-0.59.0.tar.gz", hash = "sha256:054e4d5568c27db5a993f6b3e1d34af53bd1f6d1b6c18b7166908b0f3dc04bd4", size = 1098070 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/c1/4fb9a44bb4a305b860c5a5b1866dcccfac3b76f5f170a9e68fc7733e16d2/datamodel_code_generator-0.57.0-py3-none-any.whl", hash = "sha256:d26bf5defe5154493d0aa5a822b7725332b9e9dd2abccc2f8856052286aa83b5", size = 259343 }, + { url = "https://files.pythonhosted.org/packages/f6/ef/8e2479e3ba432bb3333d0f01d6bdc520cc7f765ccbfac134b015d806bc2b/datamodel_code_generator-0.59.0-py3-none-any.whl", hash = "sha256:c8c119ab618d24a619d635fef7aa9c96b69e069a4d287c9adfc148ae28368a69", size = 316232 }, ] [package.optional-dependencies] @@ -448,11 +448,11 @@ wheels = [ [[package]] name = "more-itertools" -version = "11.0.2" +version = "11.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/f7/139d22fef48ac78127d18e01d80cf1be40236ae489769d17f35c3d425293/more_itertools-11.0.2.tar.gz", hash = "sha256:392a9e1e362cbc106a2457d37cabf9b36e5e12efd4ebff1654630e76597df804", size = 144659 } +sdist = { url = "https://files.pythonhosted.org/packages/de/1d/f4da6f02cdffe04d6362210b807146a26044c88d839208aec273bb0d9184/more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", size = 145772 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/98/6af411189d9413534c3eb691182bff1f5c6d44ed2f93f2edfe52a1bbceb8/more_itertools-11.0.2-py3-none-any.whl", hash = "sha256:6e35b35f818b01f691643c6c611bc0902f2e92b46c18fffa77ae1e7c46e912e4", size = 71939 }, + { url = "https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226 }, ] [[package]] @@ -522,11 +522,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.9.6" +version = "4.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400 } +sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224 } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348 }, + { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743 }, ] [[package]] @@ -771,17 +771,17 @@ wheels = [ [[package]] name = "typer" -version = "0.25.1" +version = "0.26.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, - { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "rich" }, { name = "shellingham" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276 } +sdist = { url = "https://files.pythonhosted.org/packages/8e/d3/90c1ee19209cb59f6ad185883fd4ccfcf72f8f0bfd549d5a8b70474611d0/typer-0.26.4.tar.gz", hash = "sha256:25b128964de66c5ea36d5ac82adc579e5e113509b17469edf9f5a4a1864ff2a9", size = 201191 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", size = 58409 }, + { url = "https://files.pythonhosted.org/packages/f0/6d/5a525c69df4a90892135e5d490b00e9e46402491f3416d4395fcb0d0201e/typer-0.26.4-py3-none-any.whl", hash = "sha256:11bfd7b43557137e373c2b10f6967a555f9678a61ed72c808968b011d95534d6", size = 122436 }, ] [[package]]