From 0a7e4e19b73b67e60638f36d304a222575faf65e Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Tue, 7 Mar 2023 17:09:57 +0100 Subject: [PATCH] init repo --- .devops/docker-build.yml | 62 +++++++++++ .editorconfig | 26 +++++ .flake8 | 8 ++ .github/PULL_REQUEST_TEMPLATE.md | 30 ++++++ .github/workflows/beta-docker-branch.yml | 47 +++++++++ .github/workflows/pr-title.yml | 56 ++++++++++ .github/workflows/release.yml | 62 +++++++++++ .github/workflows/trivy.yml | 52 +++++++++ .gitignore | 129 +++++++++++++++++++++++ .hadolint.yaml | 5 + .releaserc.json | 15 +++ .vscode/fastapi.code-workspace | 11 ++ .vscode/launch.json | 38 +++++++ .vscode/settings.json | 6 ++ Dockerfile | 11 ++ README.md | 11 ++ app/main.py | 27 +++++ app/routes/color.py | 44 ++++++++ app/routes/cosmosdb.py | 30 ++++++ app/routes/status.py | 9 ++ docker-compose.yml | 14 +++ launch-debug.sh | 5 + launch.sh | 5 + requirements.txt | 7 ++ 24 files changed, 710 insertions(+) create mode 100644 .devops/docker-build.yml create mode 100644 .editorconfig create mode 100644 .flake8 create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/beta-docker-branch.yml create mode 100644 .github/workflows/pr-title.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/trivy.yml create mode 100644 .gitignore create mode 100644 .hadolint.yaml create mode 100644 .releaserc.json create mode 100644 .vscode/fastapi.code-workspace create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 app/main.py create mode 100644 app/routes/color.py create mode 100644 app/routes/cosmosdb.py create mode 100644 app/routes/status.py create mode 100644 docker-compose.yml create mode 100644 launch-debug.sh create mode 100755 launch.sh create mode 100644 requirements.txt diff --git a/.devops/docker-build.yml b/.devops/docker-build.yml new file mode 100644 index 0000000..cb60bad --- /dev/null +++ b/.devops/docker-build.yml @@ -0,0 +1,62 @@ +# Deploy to Azure Kubernetes Service +# Build and push image to Azure Container Registry; Deploy to Azure Kubernetes Service +# https://docs.microsoft.com/azure/devops/pipelines/languages/docker + +trigger: + branches: + include: + - '*' + paths: + include: + - app/* + - Dockerfiles/* + - launch.sh + - pyproject.toml + - .* + +pr: + - main + - master + - develop + +resources: + - repo: self + +variables: + dockerfileRelativePath: '$(DOCKERFILE)' # Dockerfile + + # Agent VM image name for Build + vmImageNameDefault: 'ubuntu-latest' + + # Image Repository Name + imageRepository: '$(docker_image_repository_name)' + + environment: 'LAB' + dockerRegistryServiceConnection: '$(LAB_CONTAINER_REGISTRY_SERVICE_CONN)' + containerRegistry: '$(LAB_CONTAINER_REGISTRY_NAME)' + selfHostedAgentPool: $(LAB_AGENT_POOL) + +stages: + - stage: 'build_and_publish_docker' + displayName: 'build_and_pusblih_to_${{ variables.environment }}' + dependsOn: [] + jobs: + - job: build_and_publish_docker_image + displayName: build docker image + pool: + vmImage: $(vmImageNameDefault) + steps: + - task: CmdLine@2 + inputs: + script: 'ls -la' + - task: Docker@2 + displayName: 'publish_image_to_${{ variables.environment }}' + condition: succeeded() + inputs: + command: 'buildAndPush' + containerRegistry: '$(dockerRegistryServiceConnection)' + repository: '$(imageRepository)' + Dockerfile: '$(Build.Repository.LocalPath)/$(dockerfileRelativePath)' + tags: | + $(Build.SourceBranchName)-$(Build.BuildId) + latest diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0f1deb2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,26 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +# 4 space indentation +[*.{py,java,r,R}] +indent_style = space +indent_size = 4 + +# 2 space indentation +[*.{js,json,y{a,}ml,html,cwl}] +indent_style = space +indent_size = 2 + +[*.{md,Rmd,rst}] +trim_trailing_whitespace = false +indent_style = space +indent_size = 2 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..7c3a63b --- /dev/null +++ b/.flake8 @@ -0,0 +1,8 @@ +[flake8] +max-line-length = 120 +exclude = .venv, docs, .vscode, pychache, .git,__pycache__,__init__.py,.mypy_cache,.pytest_cache +ignore = E501, W503, E226, E203, W503, W293, I004, E266, W391, W292, I001 +# E501: Line too long +# W503: Line break occurred before binary operator +# E226: Missing white space around arithmetic operator +# I001: Import wrong positions diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..0f2e585 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,30 @@ + + + + + +#### List of Changes + + +#### Motivation and Context + + +#### How Has This Been Tested? + + + + +#### Screenshots (if appropriate): + +#### Types of changes + +- [ ] Chore (nothing changes by a user perspective) +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) + +#### Checklist: + + +- [ ] My change requires a change to the documentation. +- [ ] I have updated the documentation accordingly. diff --git a/.github/workflows/beta-docker-branch.yml b/.github/workflows/beta-docker-branch.yml new file mode 100644 index 0000000..fcad890 --- /dev/null +++ b/.github/workflows/beta-docker-branch.yml @@ -0,0 +1,47 @@ +name: Beta docker on dev branch + +on: + push: + # Sequence of patterns matched against refs/heads + branches-ignore: + - 'main' + paths-ignore: + - 'CODEOWNERS' + - '**.md' + - '.**' + +jobs: + release: + name: Beta docker on dev branch + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + id: checkout + # from https://github.com/actions/checkout/commits/main + uses: actions/checkout@1f9a0c22da41e6ebfa534300ef656657ea2c6707 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Log in to the Container registry + id: docker_login + # from https://github.com/docker/login-action/commits/master + uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push Docker image + id: docker_build_push + # from https://github.com/docker/build-push-action/commits/master + uses: docker/build-push-action@c56af957549030174b10d6867f20e78cfd7debc5 + with: + context: . + push: true + tags: | + ghcr.io/${{ github.repository }}:beta-${{ github.ref_name }} + labels: | + maintainer=https://pagopa.it + org.opencontainers.image.source=https://github.com/${{ github.repository }} diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml new file mode 100644 index 0000000..3262661 --- /dev/null +++ b/.github/workflows/pr-title.yml @@ -0,0 +1,56 @@ +name: "Validate PR title" + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +jobs: + main: + name: Validate PR title + runs-on: ubuntu-22.04 + steps: + # Please look up the latest version from + # https://github.com/amannn/action-semantic-pull-request/releases + # from https://github.com/amannn/action-semantic-pull-request/commits/main + - uses: amannn/action-semantic-pull-request@01d5fd8a8ebb9aafe902c40c53f0f4744f7381eb + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Configure which types are allowed. + # Default: https://github.com/commitizen/conventional-commit-types + types: | + fix + feat + docs + chore + breaking + # Configure that a scope must always be provided. + requireScope: false + # Configure additional validation for the subject based on a regex. + # This example ensures the subject starts with an uppercase character. + subjectPattern: ^[A-Z].+$ + # If `subjectPattern` is configured, you can use this property to override + # the default error message that is shown when the pattern doesn't match. + # The variables `subject` and `title` can be used within the message. + subjectPatternError: | + The subject "{subject}" found in the pull request title "{title}" + didn't match the configured pattern. Please ensure that the subject + starts with an uppercase character. + # For work-in-progress PRs you can typically use draft pull requests + # from Github. However, private repositories on the free plan don't have + # this option and therefore this action allows you to opt-in to using the + # special "[WIP]" prefix to indicate this state. This will avoid the + # validation of the PR title and the pull request checks remain pending. + # Note that a second check will be reported if this is enabled. + wip: true + # When using "Squash and merge" on a PR with only one commit, GitHub + # will suggest using that commit message instead of the PR title for the + # merge commit, and it's easy to commit this by mistake. Enable this option + # to also validate the commit message for one commit PRs. + validateSingleCommit: false + # Related to `validateSingleCommit` you can opt-in to validate that the PR + # title matches a single commit to avoid confusion. + validateSingleCommitMatchesPrTitle: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d438931 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,62 @@ +name: Release + +on: + # Trigger the workflow on push on the main branch + push: + branches: + - main + paths-ignore: + - 'CODEOWNERS' + - '**.md' + - '.**' + +jobs: + release: + name: Release + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + id: checkout + # from https://github.com/actions/checkout/commits/main + uses: actions/checkout@1f9a0c22da41e6ebfa534300ef656657ea2c6707 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Release + id: release + # from https://github.com/cycjimmy/semantic-release-action/commits/main + uses: cycjimmy/semantic-release-action@bdd914ff2423e2792c73475f11e8da603182f32d + with: + semantic_version: 18.0.0 + extra_plugins: | + @semantic-release/release-notes-generator@10.0.3 + @semantic-release/git@10.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to the Container registry + id: docker_login + if: steps.release.outputs.new_release_published == 'true' + # from https://github.com/docker/login-action/commits/master + uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push Docker image + id: docker_build_push + if: steps.release.outputs.new_release_published == 'true' + # from https://github.com/docker/build-push-action/commits/master + uses: docker/build-push-action@c56af957549030174b10d6867f20e78cfd7debc5 + with: + context: . + push: true + tags: | + ghcr.io/${{ github.repository }}:latest + ghcr.io/${{ github.repository }}:v${{ steps.release.outputs.new_release_version }} + labels: | + maintainer=https://pagopa.it + org.opencontainers.image.source=https://github.com/${{ github.repository }} diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml new file mode 100644 index 0000000..ceff89c --- /dev/null +++ b/.github/workflows/trivy.yml @@ -0,0 +1,52 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Docker security scan + +on: + push: + branches: [ "main", "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "main", "master" ] + schedule: + - cron: '00 07 * * *' + +permissions: + contents: read + +jobs: + build: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + name: Build + runs-on: ubuntu-22.04 + steps: + - name: Checkout code + # from https://github.com/actions/checkout/commits/main + uses: actions/checkout@1f9a0c22da41e6ebfa534300ef656657ea2c6707 + + - name: Build an image from Dockerfile + run: | + docker build -t docker.io/my-organization/my-app:${{ github.sha }} . + + - name: Run Trivy vulnerability scanner + # from https://github.com/aquasecurity/trivy-action/commits/master + uses: aquasecurity/trivy-action@d63413b0a4a4482237085319f7f4a1ce99a8f2ac + with: + image-ref: 'docker.io/my-organization/my-app:${{ github.sha }}' + format: 'template' + template: '@/contrib/sarif.tpl' + output: 'trivy-results.sarif' + severity: 'CRITICAL,HIGH' + timeout: '10m0s' + + - name: Upload Trivy scan results to GitHub Security tab + # from https://github.com/github/codeql-action/commits/main + uses: github/codeql-action/upload-sarif@f0a12816612c7306b485a22cb164feb43c6df818 + with: + sarif_file: 'trivy-results.sarif' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6e4761 --- /dev/null +++ b/.gitignore @@ -0,0 +1,129 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/.hadolint.yaml b/.hadolint.yaml new file mode 100644 index 0000000..8ee1b0f --- /dev/null +++ b/.hadolint.yaml @@ -0,0 +1,5 @@ +ignored: + - DL3059 + - DL3008 + - DL3015 + - SC2046 diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 0000000..6a9c791 --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,15 @@ +{ + "branches": ["main", "master"], + "ci": false, + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "angular", + "releaseRules": [{ "type": "breaking", "release": "major" }] + } + ], + "@semantic-release/release-notes-generator", + "@semantic-release/github" + ] +} diff --git a/.vscode/fastapi.code-workspace b/.vscode/fastapi.code-workspace new file mode 100644 index 0000000..e67378f --- /dev/null +++ b/.vscode/fastapi.code-workspace @@ -0,0 +1,11 @@ +{ + "folders": [ + { + "path": ".." + }, + { + "path": "../../FastCash" + } + ], + "settings": {} +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2d83abc --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,38 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Current File", + "type": "python", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "cwd": "${workspaceFolder}", + "env": { + "PYTHONPATH": "${cwd}", + "VERBOSITY": "DEBUG", + } + }, + { + "name": "FastAPI", + "type": "python", + "request": "launch", + "module": "uvicorn", + "args": [ + "app.main:app", + "--reload", + "--port", + "8044" + ], + "env": { + "PYTHONPATH": "${cwd}", + "API_ENDPOINT_PORT": "8044", + "API_ENDPOINT_HOST": "localhost", + "APP_VERBOSITY": "DEBUG", + } + }, + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f47a7b9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "python.pythonPath": "/Users/diego/Library/Caches/pypoetry/virtualenvs/devops-webapp-python-W4rvqIgc-py3.10/bin/python", + "python.linting.flake8Enabled": true, + "python.linting.mypyEnabled": false, + "python.linting.enabled": true, +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c25e82b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.10-slim-buster + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +COPY ./app /code/app + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..19874d6 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# devops-webapp-python + +## Special thanks + +This project born from the fantastic tutorial created by the Biella python group. + + + +## Goal + +Have a simple api that can be use inside the ci/cd of PagoPa and allow test of ours infra. diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..670987f --- /dev/null +++ b/app/main.py @@ -0,0 +1,27 @@ +from fastapi import FastAPI +from app.routes.color import color +from app.routes.status import status_route +from app.routes.cosmosdb import cosmosdb + + +def include_router(app): + app.include_router(color, prefix='/color') + app.include_router(status_route, prefix='/status') + app.include_router(cosmosdb, prefix='/cosmosdb') + + +def configure_application(app): + include_router(app) + return app + + +app = FastAPI() + + +@app.get("/") +async def home_root(): + return {"home_root": "ok"} + + +configure_application(app) + diff --git a/app/routes/color.py b/app/routes/color.py new file mode 100644 index 0000000..77ff906 --- /dev/null +++ b/app/routes/color.py @@ -0,0 +1,44 @@ +from fastapi import APIRouter +import requests +import logging +from dapr.clients import DaprClient + +color = APIRouter() + + +@color.get("/") +async def home(): + return {"message": "home of color"} + + +@color.get("/dapr/http") +async def dapr_http_color(): + """ + return random color with a raw http request to dapr + """ + r = requests.get('http://localhost:3500/v1.0/invoke/backend/method/color') + color = r.text + logging.info(r) + return {"random color": f'{color}'} + + +@color.get("/dapr/sdk") +def dapr_sdk_color(): + """ + return random color with a sdk request to dapr + """ + with DaprClient() as daprClient: + result = daprClient.invoke_method( + 'backend', + 'color', + content_type="utf-8", + data=b'', + http_verb="GET") + + color = result.data.decode("utf-8") + + logging.info(str(color)) + return {"random color": f'{str(color)}'} + + + diff --git a/app/routes/cosmosdb.py b/app/routes/cosmosdb.py new file mode 100644 index 0000000..d0ceb09 --- /dev/null +++ b/app/routes/cosmosdb.py @@ -0,0 +1,30 @@ +from fastapi import APIRouter +import logging +from dapr.clients import DaprClient +from python_random_strings import random_strings +import uuid + + +cosmosdb = APIRouter() + + +@cosmosdb.get("/") +async def home(): + return {"message": "home of cosmosdb"} + + +@cosmosdb.get("/dapr/sdk") +def dapr_sdk_cosmosdb(): + """ + create random value in cosmosdb with a sdk + """ + key = random_strings.random_lowercase(4) + value = str(uuid.uuid4()) + + with DaprClient() as daprClient: + daprClient.save_state(store_name="cosmosdb", key=key, value=value) + + logging.info("added cosmosdb key:f'{key}' and value f'{value}'") + + return {"cosmosdb key": f'{key}', "cosmosdb value": f'{value}'} + diff --git a/app/routes/status.py b/app/routes/status.py new file mode 100644 index 0000000..ad301bc --- /dev/null +++ b/app/routes/status.py @@ -0,0 +1,9 @@ +from fastapi import APIRouter, Response +from http import HTTPStatus + +status_route = APIRouter() + + +@status_route.get("/") +async def status(): + return Response(status_code=HTTPStatus.OK) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..bf1b3f8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +version: "3.8" +services: + webapp-python: + # image: "devops-webapp-python:latest" + build: + dockerfile: ./Dockerfile + context: . + container_name: webapp-python + restart: always + environment: + - MY_CONFIGMAP_VALUE=1 + - MY_SECRET_VALUE=very secret 1 + ports: + - "8000:8000" diff --git a/launch-debug.sh b/launch-debug.sh new file mode 100644 index 0000000..92c9dfc --- /dev/null +++ b/launch-debug.sh @@ -0,0 +1,5 @@ +export PYTHONPATH=$(pwd) +export API_ENDPOINT_PORT=5000 +export VERBOSITY=DEBUG +export DEBUG_MODE=True +python3 app/main.py \ No newline at end of file diff --git a/launch.sh b/launch.sh new file mode 100755 index 0000000..8974b2b --- /dev/null +++ b/launch.sh @@ -0,0 +1,5 @@ +#Launch the application using gunicorn backend +#gunicorn -w 4 -b 0.0.0.0:${APP_ENDPOINT_PORT:-8045} app.main:app +# gunicorn -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:${APP_ENDPOINT_PORT:-8045} --preload --log-level ${VERBOSIT:-INFO} app.main:app +# python ./app/main.py +uvicorn app.main:app --host 0.0.0.0 --port ${APP_ENDPOINT_PORT:-8045} --log-level ${APP_VERBOSITY:-info} --reload diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..66af423 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +fastapi +uvicorn +pyyaml +unidecode +requests +dapr +python-random-strings