From b8618fe88f9f61cafa309f9155c1e3426af41ca6 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Wed, 9 Oct 2024 15:33:53 +0300 Subject: [PATCH 01/17] Enable offline Swagger UI by default & fix it for Litestar --- microbootstrap/bootstrappers/litestar.py | 23 +++++++++++-------- .../instruments/swagger_instrument.py | 2 +- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/microbootstrap/bootstrappers/litestar.py b/microbootstrap/bootstrappers/litestar.py index 63d81b7..9e1f8f3 100644 --- a/microbootstrap/bootstrappers/litestar.py +++ b/microbootstrap/bootstrappers/litestar.py @@ -8,6 +8,7 @@ from litestar.config.cors import CORSConfig as LitestarCorsConfig from litestar.contrib.opentelemetry.config import OpenTelemetryConfig as LitestarOpentelemetryConfig from litestar.contrib.prometheus import PrometheusConfig, PrometheusController +from litestar.openapi.plugins import SwaggerRenderPlugin from litestar_offline_docs import generate_static_files_config from sentry_sdk.integrations.litestar import LitestarIntegration @@ -51,24 +52,28 @@ def bootstrap(self) -> None: @LitestarBootstrapper.use_instrument() class LitestarSwaggerInstrument(SwaggerInstrument): def bootstrap_before(self) -> dict[str, typing.Any]: - class LitestarOpenAPIController(openapi.OpenAPIController): - path = self.instrument_config.swagger_path - if self.instrument_config.swagger_offline_docs: - swagger_ui_standalone_preset_js_url = ( + swagger_render_plugin: typing.Final = ( + SwaggerRenderPlugin( + js_url=f"{self.instrument_config.service_static_path}/swagger-ui-bundle.js", + css_url=f"{self.instrument_config.service_static_path}/swagger-ui.css", + standalone_preset_js_url=( f"{self.instrument_config.service_static_path}/swagger-ui-standalone-preset.js" - ) - swagger_bundle_path: str = f"{self.instrument_config.service_static_path}/swagger-ui-bundle.js" - swagger_css_url: str = f"{self.instrument_config.service_static_path}/swagger-ui.css" + ), + ) + if self.instrument_config.swagger_offline_docs + else SwaggerRenderPlugin() + ) openapi_config: typing.Final = openapi.OpenAPIConfig( + path=self.instrument_config.swagger_path, title=self.instrument_config.service_name, version=self.instrument_config.service_version, description=self.instrument_config.service_description, - openapi_controller=LitestarOpenAPIController, + render_plugins=[swagger_render_plugin], **self.instrument_config.swagger_extra_params, ) - bootstrap_result = {} + bootstrap_result: typing.Final = {} if self.instrument_config.swagger_offline_docs: bootstrap_result["static_files_config"] = [ generate_static_files_config(static_files_handler_path=self.instrument_config.service_static_path), diff --git a/microbootstrap/instruments/swagger_instrument.py b/microbootstrap/instruments/swagger_instrument.py index c0d4a45..d53c1e5 100644 --- a/microbootstrap/instruments/swagger_instrument.py +++ b/microbootstrap/instruments/swagger_instrument.py @@ -14,7 +14,7 @@ class SwaggerConfig(BaseInstrumentConfig): service_static_path: str = "/static" swagger_path: str = "/docs" - swagger_offline_docs: bool = False + swagger_offline_docs: bool = True swagger_extra_params: dict[str, typing.Any] = pydantic.Field(default_factory=dict) From c75bb1e80919a7f8235d4369a80909b8dc684cbe Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Wed, 9 Oct 2024 15:38:48 +0300 Subject: [PATCH 02/17] Fix tests --- tests/instruments/test_swagger.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/instruments/test_swagger.py b/tests/instruments/test_swagger.py index 691c1ed..5123d3a 100644 --- a/tests/instruments/test_swagger.py +++ b/tests/instruments/test_swagger.py @@ -39,6 +39,7 @@ def test_swagger_teardown( def test_litestar_swagger_bootstrap_online_docs(minimal_swagger_config: SwaggerConfig) -> None: + minimal_swagger_config.swagger_offline_docs = False swagger_instrument: typing.Final = LitestarSwaggerInstrument(minimal_swagger_config) swagger_instrument.bootstrap() @@ -52,7 +53,6 @@ def test_litestar_swagger_bootstrap_online_docs(minimal_swagger_config: SwaggerC def test_litestar_swagger_bootstrap_offline_docs(minimal_swagger_config: SwaggerConfig) -> None: - minimal_swagger_config.swagger_offline_docs = True swagger_instrument: typing.Final = LitestarSwaggerInstrument(minimal_swagger_config) swagger_instrument.bootstrap() @@ -72,6 +72,7 @@ async def test_litestar_swagger_bootstrap_working_online_docs( minimal_swagger_config: SwaggerConfig, ) -> None: minimal_swagger_config.swagger_path = "/my-docs-path" + minimal_swagger_config.swagger_offline_docs = False swagger_instrument: typing.Final = LitestarSwaggerInstrument(minimal_swagger_config) swagger_instrument.bootstrap() @@ -88,7 +89,6 @@ async def test_litestar_swagger_bootstrap_working_offline_docs( minimal_swagger_config: SwaggerConfig, ) -> None: minimal_swagger_config.service_static_path = "/my-static-path" - minimal_swagger_config.swagger_offline_docs = True swagger_instrument: typing.Final = LitestarSwaggerInstrument(minimal_swagger_config) swagger_instrument.bootstrap() @@ -116,6 +116,7 @@ async def test_fastapi_swagger_bootstrap_working_online_docs( minimal_swagger_config: SwaggerConfig, ) -> None: minimal_swagger_config.swagger_path = "/my-docs-path" + minimal_swagger_config.swagger_offline_docs = False swagger_instrument: typing.Final = FastApiSwaggerInstrument(minimal_swagger_config) swagger_instrument.bootstrap() @@ -132,7 +133,6 @@ async def test_fastapi_swagger_bootstrap_working_offline_docs( minimal_swagger_config: SwaggerConfig, ) -> None: minimal_swagger_config.service_static_path = "/my-static-path" - minimal_swagger_config.swagger_offline_docs = True swagger_instrument: typing.Final = FastApiSwaggerInstrument(minimal_swagger_config) fastapi_application = fastapi.FastAPI( **swagger_instrument.bootstrap_before(), From 86acfc2a2c9c72b6fbc72dd1ae96fa2119fc6b9a Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Thu, 10 Oct 2024 17:06:01 +0300 Subject: [PATCH 03/17] Disable offline docs by default --- microbootstrap/instruments/swagger_instrument.py | 2 +- tests/instruments/test_swagger.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/microbootstrap/instruments/swagger_instrument.py b/microbootstrap/instruments/swagger_instrument.py index d53c1e5..c0d4a45 100644 --- a/microbootstrap/instruments/swagger_instrument.py +++ b/microbootstrap/instruments/swagger_instrument.py @@ -14,7 +14,7 @@ class SwaggerConfig(BaseInstrumentConfig): service_static_path: str = "/static" swagger_path: str = "/docs" - swagger_offline_docs: bool = True + swagger_offline_docs: bool = False swagger_extra_params: dict[str, typing.Any] = pydantic.Field(default_factory=dict) diff --git a/tests/instruments/test_swagger.py b/tests/instruments/test_swagger.py index 5123d3a..691c1ed 100644 --- a/tests/instruments/test_swagger.py +++ b/tests/instruments/test_swagger.py @@ -39,7 +39,6 @@ def test_swagger_teardown( def test_litestar_swagger_bootstrap_online_docs(minimal_swagger_config: SwaggerConfig) -> None: - minimal_swagger_config.swagger_offline_docs = False swagger_instrument: typing.Final = LitestarSwaggerInstrument(minimal_swagger_config) swagger_instrument.bootstrap() @@ -53,6 +52,7 @@ def test_litestar_swagger_bootstrap_online_docs(minimal_swagger_config: SwaggerC def test_litestar_swagger_bootstrap_offline_docs(minimal_swagger_config: SwaggerConfig) -> None: + minimal_swagger_config.swagger_offline_docs = True swagger_instrument: typing.Final = LitestarSwaggerInstrument(minimal_swagger_config) swagger_instrument.bootstrap() @@ -72,7 +72,6 @@ async def test_litestar_swagger_bootstrap_working_online_docs( minimal_swagger_config: SwaggerConfig, ) -> None: minimal_swagger_config.swagger_path = "/my-docs-path" - minimal_swagger_config.swagger_offline_docs = False swagger_instrument: typing.Final = LitestarSwaggerInstrument(minimal_swagger_config) swagger_instrument.bootstrap() @@ -89,6 +88,7 @@ async def test_litestar_swagger_bootstrap_working_offline_docs( minimal_swagger_config: SwaggerConfig, ) -> None: minimal_swagger_config.service_static_path = "/my-static-path" + minimal_swagger_config.swagger_offline_docs = True swagger_instrument: typing.Final = LitestarSwaggerInstrument(minimal_swagger_config) swagger_instrument.bootstrap() @@ -116,7 +116,6 @@ async def test_fastapi_swagger_bootstrap_working_online_docs( minimal_swagger_config: SwaggerConfig, ) -> None: minimal_swagger_config.swagger_path = "/my-docs-path" - minimal_swagger_config.swagger_offline_docs = False swagger_instrument: typing.Final = FastApiSwaggerInstrument(minimal_swagger_config) swagger_instrument.bootstrap() @@ -133,6 +132,7 @@ async def test_fastapi_swagger_bootstrap_working_offline_docs( minimal_swagger_config: SwaggerConfig, ) -> None: minimal_swagger_config.service_static_path = "/my-static-path" + minimal_swagger_config.swagger_offline_docs = True swagger_instrument: typing.Final = FastApiSwaggerInstrument(minimal_swagger_config) fastapi_application = fastapi.FastAPI( **swagger_instrument.bootstrap_before(), From a36fbeed501c97309906e09eb8ae29e33d160703 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Wed, 9 Oct 2024 14:27:28 +0300 Subject: [PATCH 04/17] Add ruff & mypy to dev dependencies --- poetry.lock | 87 +++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 2 ++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 719d36a..86e8b89 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1403,6 +1403,64 @@ files = [ {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, ] +[[package]] +name = "mypy" +version = "1.11.2" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, + {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, + {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, + {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, + {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, + {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, + {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, + {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, + {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, + {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, + {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, + {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, + {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, + {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, + {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, + {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, + {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, + {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, + {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -2283,6 +2341,33 @@ typing-extensions = "*" dev = ["mypy", "packaging", "pre-commit", "pytest", "pytest-cov", "rich-codex", "ruff", "types-setuptools"] docs = ["markdown-include", "mkdocs", "mkdocs-glightbox", "mkdocs-material-extensions", "mkdocs-material[imaging] (>=9.5.18,<9.6.0)", "mkdocs-rss-plugin", "mkdocstrings[python]", "rich-codex"] +[[package]] +name = "ruff" +version = "0.6.9" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"}, + {file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"}, + {file = "ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c"}, + {file = "ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e"}, + {file = "ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577"}, + {file = "ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829"}, + {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5"}, + {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7"}, + {file = "ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f"}, + {file = "ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa"}, + {file = "ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb"}, + {file = "ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0"}, + {file = "ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625"}, + {file = "ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039"}, + {file = "ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d"}, + {file = "ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117"}, + {file = "ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93"}, + {file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"}, +] + [[package]] name = "sentry-sdk" version = "2.13.0" @@ -2894,4 +2979,4 @@ litestar = ["litestar", "litestar-offline-docs", "prometheus-client"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "83d4edcabb34b38b87385466efabf45fc9d00f9f015bf9fbd83a1167c9032a65" +content-hash = "49486bbab97ef7e3bc1d4bef8fc77c22d513668a848aee277aca86fd20536204" diff --git a/pyproject.toml b/pyproject.toml index e157fae..a6b3fb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,6 +78,8 @@ pre-commit = "^3.5.0" redis = "^5.0.7" opentelemetry-instrumentation-redis = "^0.46b0" trio = "^0.26.0" +mypy = "^1.11.2" +ruff = "^0.6.9" [tool.poetry.extras] From 325d6d6462615bc9dbb8b26554000a8c2119073d Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Wed, 9 Oct 2024 15:03:37 +0300 Subject: [PATCH 05/17] Let Granian choose event loop to use As @gi0baro [pointed out](https://github.com/community-of-python/microbootstrap/issues/3#issuecomment-2269494352): > if you don't specify that option Granian will use uvloop on supported platforms and asyncio on all the others by default. You just need to drop that line. --- microbootstrap/granian_server.py | 1 - 1 file changed, 1 deletion(-) diff --git a/microbootstrap/granian_server.py b/microbootstrap/granian_server.py index c40d798..1f441fc 100644 --- a/microbootstrap/granian_server.py +++ b/microbootstrap/granian_server.py @@ -32,7 +32,6 @@ def create_granian_server( address=settings.server_host, port=settings.server_port, interface=Interfaces.ASGI, - loop=Loops.uvloop, workers=settings.server_workers_count, log_level=GRANIAN_LOG_LEVELS_MAP[getattr(settings, "logging_log_level", logging.INFO)], reload=settings.server_reload, From 12f19c084fdbd5f1dc68caf45381002cc9a3f6f7 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin <75225148+vrslev@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:05:00 +0300 Subject: [PATCH 06/17] Remove unused import --- microbootstrap/granian_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microbootstrap/granian_server.py b/microbootstrap/granian_server.py index 1f441fc..d4b0e59 100644 --- a/microbootstrap/granian_server.py +++ b/microbootstrap/granian_server.py @@ -3,7 +3,7 @@ import typing import granian -from granian.constants import Interfaces, Loops +from granian.constants import Interfaces from granian.log import LogLevels From 4399e205bede2f71192153028d12d5ce7ccaa91f Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Wed, 9 Oct 2024 14:28:51 +0300 Subject: [PATCH 07/17] Fix hanging tests --- tests/instruments/test_opentelemetry.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/instruments/test_opentelemetry.py b/tests/instruments/test_opentelemetry.py index 521610c..afd64f9 100644 --- a/tests/instruments/test_opentelemetry.py +++ b/tests/instruments/test_opentelemetry.py @@ -1,9 +1,10 @@ import contextlib import typing -from unittest.mock import AsyncMock, MagicMock, patch +from unittest.mock import AsyncMock, MagicMock, Mock, patch import fastapi import litestar +import pytest from httpx import AsyncClient from litestar.middleware.base import DefineMiddleware from litestar.testing import AsyncTestClient @@ -59,7 +60,7 @@ def test_litestar_opentelemetry_bootstrap( assert isinstance(opentelemetry_bootstrap_result["middleware"][0], DefineMiddleware) -def test_litestar_opentelemetry_terdown( +def test_litestar_opentelemetry_teardown( minimal_opentelemetry_config: OpentelemetryConfig, magic_mock: MagicMock, ) -> None: @@ -98,8 +99,10 @@ async def test_handler() -> None: async def test_fastapi_opentelemetry_bootstrap_working( - minimal_opentelemetry_config: OpentelemetryConfig, + minimal_opentelemetry_config: OpentelemetryConfig, monkeypatch: pytest.MonkeyPatch ) -> None: + monkeypatch.setattr("opentelemetry.sdk.trace.TracerProvider.shutdown", Mock()) + opentelemetry_instrument: typing.Final = FastApiOpentelemetryInstrument(minimal_opentelemetry_config) opentelemetry_instrument.bootstrap() fastapi_application: typing.Final = opentelemetry_instrument.bootstrap_after(fastapi.FastAPI()) From 38c0e9fcea56e363ba1c65a29715a361d1ebc91c Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Wed, 9 Oct 2024 12:52:42 +0300 Subject: [PATCH 08/17] Fix first readme example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cff447e..5f702f1 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ from microbootstrap import LitestarSettings class YourSettings(LitestarSettings): - # Your settings are stored here + ... # Your settings are stored here settings = YourSettings() From 52f260befb19c1a7942ba37bf3477e788c1eafb5 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Wed, 9 Oct 2024 14:40:55 +0300 Subject: [PATCH 09/17] Add env-var aliases to improve compatibility with existing internal services and pipelines --- microbootstrap/settings.py | 14 +++++++++---- pyproject.toml | 1 + tests/test_settings.py | 42 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 tests/test_settings.py diff --git a/microbootstrap/settings.py b/microbootstrap/settings.py index 81debc0..0636e9a 100644 --- a/microbootstrap/settings.py +++ b/microbootstrap/settings.py @@ -2,6 +2,7 @@ import os import typing +import pydantic import pydantic_settings from microbootstrap import ( @@ -16,7 +17,8 @@ SettingsT = typing.TypeVar("SettingsT", bound="BaseServiceSettings") -ENVIRONMENT_PREFIX: typing.Final = "ENVIRONMENT_PREFIX" +ENV_PREFIX_VAR_NAME: typing.Final = "ENVIRONMENT_PREFIX" +ENV_PREFIX: typing.Final = os.getenv(ENV_PREFIX_VAR_NAME, "") # TODO: add offline docs and cors support # noqa: TD002 @@ -25,9 +27,13 @@ class BaseServiceSettings( ): service_debug: bool = True service_environment: str | None = None - service_name: str = "micro-service" + service_name: str = pydantic.Field( + "micro-service", validation_alias=pydantic.AliasChoices("SERVICE_NAME", f"{ENV_PREFIX}SERVICE_NAME") + ) service_description: str = "Micro service description" - service_version: str = "1.0.0" + service_version: str = pydantic.Field( + "1.0.0", validation_alias=pydantic.AliasChoices("CI_COMMIT_TAG", f"{ENV_PREFIX}SERVICE_VERSION") + ) service_static_path: str = "/static" server_host: str = "0.0.0.0" # noqa: S104 @@ -37,7 +43,7 @@ class BaseServiceSettings( model_config = pydantic_settings.SettingsConfigDict( env_file=".env", - env_prefix=os.getenv(ENVIRONMENT_PREFIX, ""), + env_prefix=ENV_PREFIX, env_file_encoding="utf-8", populate_by_name=True, ) diff --git a/pyproject.toml b/pyproject.toml index a6b3fb8..6181b90 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,6 +96,7 @@ requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.mypy] +plugins = ["pydantic.mypy"] files = ["microbootstrap", "tests"] python_version = "3.9" strict = true diff --git a/tests/test_settings.py b/tests/test_settings.py new file mode 100644 index 0000000..2f4a7cb --- /dev/null +++ b/tests/test_settings.py @@ -0,0 +1,42 @@ +import importlib +import typing + +import pytest + +import microbootstrap.settings + + +@pytest.fixture +def reset_reloaded_settings_module() -> typing.Iterator[None]: + yield + importlib.reload(microbootstrap.settings) + + +@pytest.mark.parametrize("alias", ["SERVICE_NAME", "MY_SERVICE_SERVICE_NAME"]) +def test_settings_service_name_aliases(monkeypatch: pytest.MonkeyPatch, alias: str) -> None: + monkeypatch.setenv("ENVIRONMENT_PREFIX", "MY_SERVICE_") + monkeypatch.setenv(alias, "my service") + importlib.reload(microbootstrap.settings) + + settings = microbootstrap.settings.BaseServiceSettings() + assert settings.service_name == "my service" + + +def test_settings_service_name_default() -> None: + settings = microbootstrap.settings.BaseServiceSettings() + assert settings.service_name == "micro-service" + + +@pytest.mark.parametrize("alias", ["CI_COMMIT_TAG", "MY_SERVICE_SERVICE_VERSION"]) +def test_settings_service_version_aliases(monkeypatch: pytest.MonkeyPatch, alias: str) -> None: + monkeypatch.setenv("ENVIRONMENT_PREFIX", "MY_SERVICE_") + monkeypatch.setenv(alias, "1.2.3") + importlib.reload(microbootstrap.settings) + + settings = microbootstrap.settings.BaseServiceSettings() + assert settings.service_version == "1.2.3" + + +def test_settings_service_version_default() -> None: + settings = microbootstrap.settings.BaseServiceSettings() + assert settings.service_version == "1.0.0" From ab9a6dce810c7c6fec6414f8703d7c0ccf9a6dcd Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Wed, 9 Oct 2024 14:42:05 +0300 Subject: [PATCH 10/17] Fix ruff diagnostic --- tests/test_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_settings.py b/tests/test_settings.py index 2f4a7cb..d3e5735 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -7,7 +7,7 @@ @pytest.fixture -def reset_reloaded_settings_module() -> typing.Iterator[None]: +def _reset_reloaded_settings_module() -> typing.Iterator[None]: yield importlib.reload(microbootstrap.settings) From ec9f585c593af0d32bed3ebbd27147009856af39 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Wed, 9 Oct 2024 14:44:43 +0300 Subject: [PATCH 11/17] Update pre-commit dependencies --- .pre-commit-config.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6f969be..70371b7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,13 @@ repos: - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.10.1 + rev: v1.11.2 hooks: - id: mypy name: mypy always_run: true + additional_dependencies: [pydantic>=2.3.4] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.0 + rev: v0.6.9 hooks: - id: ruff name: ruff-check From ab6dc4e4e0ecd8c28b6c0a087ac3467e107ea65c Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Wed, 9 Oct 2024 14:49:17 +0300 Subject: [PATCH 12/17] Run ruff check --- tests/conftest.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index e3cf0a3..d9249ac 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -26,47 +26,47 @@ def anyio_backend() -> str: return "asyncio" -@pytest.fixture() +@pytest.fixture def default_litestar_app() -> litestar.Litestar: return litestar.Litestar() -@pytest.fixture() +@pytest.fixture def minimal_sentry_config() -> SentryConfig: return SentryConfig(sentry_dsn="https://examplePublicKey@o0.ingest.sentry.io/0") -@pytest.fixture() +@pytest.fixture def minimal_logging_config() -> LoggingConfig: return LoggingConfig(service_debug=False) -@pytest.fixture() +@pytest.fixture def minimal_base_prometheus_config() -> BasePrometheusConfig: return BasePrometheusConfig() -@pytest.fixture() +@pytest.fixture def minimal_fastapi_prometheus_config() -> FastApiPrometheusConfig: return FastApiPrometheusConfig() -@pytest.fixture() +@pytest.fixture def minimal_litestar_prometheus_config() -> LitestarPrometheusConfig: return LitestarPrometheusConfig() -@pytest.fixture() +@pytest.fixture def minimal_swagger_config() -> SwaggerConfig: return SwaggerConfig() -@pytest.fixture() +@pytest.fixture def minimal_cors_config() -> SwaggerConfig: return CorsConfig(cors_allowed_origins=["*"]) -@pytest.fixture() +@pytest.fixture def minimal_opentelemetry_config() -> OpentelemetryConfig: return OpentelemetryConfig( service_name="test-micro-service", @@ -77,21 +77,21 @@ def minimal_opentelemetry_config() -> OpentelemetryConfig: ) -@pytest.fixture() +@pytest.fixture def base_settings() -> BaseServiceSettings: return BaseServiceSettings() -@pytest.fixture() +@pytest.fixture def magic_mock() -> MagicMock: return MagicMock() -@pytest.fixture() +@pytest.fixture def async_mock() -> AsyncMock: return AsyncMock() -@pytest.fixture() +@pytest.fixture def console_writer() -> ConsoleWriter: return ConsoleWriter(writer_enabled=False) From a3056bdc7220e4fbda18c16ce68a41214ff4c686 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Wed, 9 Oct 2024 14:52:34 +0300 Subject: [PATCH 13/17] Fix mypy issue --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index d9249ac..cab6f46 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -62,7 +62,7 @@ def minimal_swagger_config() -> SwaggerConfig: @pytest.fixture -def minimal_cors_config() -> SwaggerConfig: +def minimal_cors_config() -> CorsConfig: return CorsConfig(cors_allowed_origins=["*"]) From 878f0355f0788a22e6422e2e7ee6dbe0a0a4343d Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Wed, 9 Oct 2024 14:55:39 +0300 Subject: [PATCH 14/17] Fix mypy issue --- microbootstrap/instruments/prometheus_instrument.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/microbootstrap/instruments/prometheus_instrument.py b/microbootstrap/instruments/prometheus_instrument.py index 44e8e01..9c57cb2 100644 --- a/microbootstrap/instruments/prometheus_instrument.py +++ b/microbootstrap/instruments/prometheus_instrument.py @@ -36,5 +36,5 @@ def is_ready(self) -> bool: ) @classmethod - def get_config_type(cls) -> type[BasePrometheusConfig]: - return BasePrometheusConfig + def get_config_type(cls) -> type[PrometheusConfigT]: + return BasePrometheusConfig # type: ignore[return-value] From 84448667b8bb1256240fe1f1e0ce38fa0afe8565 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Thu, 10 Oct 2024 11:40:03 +0300 Subject: [PATCH 15/17] Fix reset settings module fixture --- tests/conftest.py | 9 +++++++++ tests/test_settings.py | 6 +----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index cab6f46..dbec5d5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,12 @@ from __future__ import annotations +import importlib +import typing from unittest.mock import AsyncMock, MagicMock import litestar import pytest +import microbootstrap.settings from microbootstrap import ( FastApiPrometheusConfig, LitestarPrometheusConfig, @@ -95,3 +98,9 @@ def async_mock() -> AsyncMock: @pytest.fixture def console_writer() -> ConsoleWriter: return ConsoleWriter(writer_enabled=False) + + +@pytest.fixture +def reset_reloaded_settings_module() -> typing.Iterator[None]: + yield + importlib.reload(microbootstrap.settings) diff --git a/tests/test_settings.py b/tests/test_settings.py index d3e5735..746c574 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -1,15 +1,11 @@ import importlib -import typing import pytest import microbootstrap.settings -@pytest.fixture -def _reset_reloaded_settings_module() -> typing.Iterator[None]: - yield - importlib.reload(microbootstrap.settings) +pytestmark = [pytest.mark.usefixtures("reset_reloaded_settings_module")] @pytest.mark.parametrize("alias", ["SERVICE_NAME", "MY_SERVICE_SERVICE_NAME"]) From e5f721a79ec6c50b9a88dafe6a1a7ead90c35b06 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Wed, 9 Oct 2024 13:21:59 +0300 Subject: [PATCH 16/17] Set larger default value of max_value_length for sentry-sdk --- README.md | 1 + microbootstrap/instruments/sentry_instrument.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/README.md b/README.md index 5f702f1..0e0c622 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,7 @@ class YourSettings(BaseServiceSettings): sentry_traces_sample_rate: float | None = None sentry_sample_rate: float = pydantic.Field(default=1.0, le=1.0, ge=0.0) sentry_max_breadcrumbs: int = 15 + sentry_max_value_length: int = 16384 sentry_attach_stacktrace: bool = True sentry_integrations: list[Integration] = [] sentry_additional_params: dict[str, typing.Any] = {} diff --git a/microbootstrap/instruments/sentry_instrument.py b/microbootstrap/instruments/sentry_instrument.py index 26b1ead..964cc1e 100644 --- a/microbootstrap/instruments/sentry_instrument.py +++ b/microbootstrap/instruments/sentry_instrument.py @@ -15,6 +15,7 @@ class SentryConfig(BaseInstrumentConfig): sentry_traces_sample_rate: float | None = None sentry_sample_rate: float = pydantic.Field(default=1.0, le=1.0, ge=0.0) sentry_max_breadcrumbs: int = 15 + sentry_max_value_length: int = 16384 sentry_attach_stacktrace: bool = True sentry_integrations: list[Integration] = pydantic.Field(default_factory=list) sentry_additional_params: dict[str, typing.Any] = pydantic.Field(default_factory=dict) @@ -34,6 +35,7 @@ def bootstrap(self) -> None: traces_sample_rate=self.instrument_config.sentry_traces_sample_rate, environment=self.instrument_config.service_environment, max_breadcrumbs=self.instrument_config.sentry_max_breadcrumbs, + max_value_length=self.instrument_config.sentry_max_value_length, attach_stacktrace=self.instrument_config.sentry_attach_stacktrace, integrations=self.instrument_config.sentry_integrations, **self.instrument_config.sentry_additional_params, From f333e8f372ee97cf4f96cfea4e8b1ed8ad4eae6e Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Thu, 10 Oct 2024 17:07:46 +0300 Subject: [PATCH 17/17] Do not enforce Swagger UI if offline docs are disabled --- microbootstrap/bootstrappers/litestar.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/microbootstrap/bootstrappers/litestar.py b/microbootstrap/bootstrappers/litestar.py index 9e1f8f3..7c3d5e5 100644 --- a/microbootstrap/bootstrappers/litestar.py +++ b/microbootstrap/bootstrappers/litestar.py @@ -52,16 +52,18 @@ def bootstrap(self) -> None: @LitestarBootstrapper.use_instrument() class LitestarSwaggerInstrument(SwaggerInstrument): def bootstrap_before(self) -> dict[str, typing.Any]: - swagger_render_plugin: typing.Final = ( - SwaggerRenderPlugin( - js_url=f"{self.instrument_config.service_static_path}/swagger-ui-bundle.js", - css_url=f"{self.instrument_config.service_static_path}/swagger-ui.css", - standalone_preset_js_url=( - f"{self.instrument_config.service_static_path}/swagger-ui-standalone-preset.js" + render_plugins: typing.Final = ( + ( + SwaggerRenderPlugin( + js_url=f"{self.instrument_config.service_static_path}/swagger-ui-bundle.js", + css_url=f"{self.instrument_config.service_static_path}/swagger-ui.css", + standalone_preset_js_url=( + f"{self.instrument_config.service_static_path}/swagger-ui-standalone-preset.js" + ), ), ) if self.instrument_config.swagger_offline_docs - else SwaggerRenderPlugin() + else () ) openapi_config: typing.Final = openapi.OpenAPIConfig( @@ -69,7 +71,7 @@ def bootstrap_before(self) -> dict[str, typing.Any]: title=self.instrument_config.service_name, version=self.instrument_config.service_version, description=self.instrument_config.service_description, - render_plugins=[swagger_render_plugin], + render_plugins=render_plugins, **self.instrument_config.swagger_extra_params, )