Răsfoiți Sursa

Feat/litestar (#9905)

* feat: add support for litestar

* feat: add support for litestar

* feat: refactor app structure and update dependencies

* feat: update app structure and replace ujson5 with orjson

* feat: add comprehensive .gitignore for Python and IDE files

* feat: update benchmark configuration for Granian support

* feat: update display names in benchmark configuration and modify Dockerfile installation

* feat: update response handling to return Response objects for JSON endpoints

* feat: update response handling to return Response objects for JSON endpoints

* fix: copy files in Dockerfiles

* fix: copy files in Dockerfiles

* fix: remove usage of restricting to http2

* fix: resposne types

* fix: resposne types

* fix: remove socketify with pypy
Vetrichelvan 1 lună în urmă
părinte
comite
f6c3ffc6cb
26 a modificat fișierele cu 1342 adăugiri și 0 ștergeri
  1. 244 0
      frameworks/Python/litestar/.gitignore
  2. 37 0
      frameworks/Python/litestar/README.md
  3. 49 0
      frameworks/Python/litestar/app-socketify-asgi.py
  4. 152 0
      frameworks/Python/litestar/app.py
  5. 185 0
      frameworks/Python/litestar/app_orm.py
  6. 186 0
      frameworks/Python/litestar/benchmark_config.json
  7. 184 0
      frameworks/Python/litestar/config.toml
  8. 20 0
      frameworks/Python/litestar/litestar-granian-orm.dockerfile
  9. 18 0
      frameworks/Python/litestar/litestar-granian.dockerfile
  10. 20 0
      frameworks/Python/litestar/litestar-gunicorn-orm.dockerfile
  11. 18 0
      frameworks/Python/litestar/litestar-gunicorn.dockerfile
  12. 20 0
      frameworks/Python/litestar/litestar-nginx-unit.dockerfile
  13. 19 0
      frameworks/Python/litestar/litestar-socketify-asgi.dockerfile
  14. 18 0
      frameworks/Python/litestar/litestar-uvicorn.dockerfile
  15. 18 0
      frameworks/Python/litestar/litestar.dockerfile
  16. 12 0
      frameworks/Python/litestar/litestar_conf.py
  17. 26 0
      frameworks/Python/litestar/nginx-unit-config.sh
  18. 2 0
      frameworks/Python/litestar/requirements-granian.txt
  19. 2 0
      frameworks/Python/litestar/requirements-gunicorn.txt
  20. 3 0
      frameworks/Python/litestar/requirements-socketify.txt
  21. 3 0
      frameworks/Python/litestar/requirements-sqlalchemy.txt
  22. 3 0
      frameworks/Python/litestar/requirements-uvicorn.txt
  23. 4 0
      frameworks/Python/litestar/requirements.txt
  24. 79 0
      frameworks/Python/litestar/ruff.toml
  25. 10 0
      frameworks/Python/litestar/templates/fortune.html
  26. 10 0
      frameworks/Python/litestar/templates/fortune.jinja

+ 244 - 0
frameworks/Python/litestar/.gitignore

@@ -0,0 +1,244 @@
+### Python template
+# 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/
+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/
+cover/
+
+# 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
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+# .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
+
+# poetry
+#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+#   This is especially recommended for binary packages to ensure reproducibility, and is more
+#   commonly ignored for libraries.
+#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+#   in version control.
+#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
+.pdm.toml
+.pdm-python
+.pdm-build/
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__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/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+#  and can be added to the global gitignore or merged into this file.  For a more nuclear
+#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+### PyCharm+all template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+.ruff_cache/

+ 37 - 0
frameworks/Python/litestar/README.md

@@ -0,0 +1,37 @@
+# Litestar Benchmarking Test
+
+This is the Litestar portion of a [benchmarking tests suite](../../)
+comparing a variety of web development platforms.
+
+The information below is specific to Litestar. For further guidance,
+review the [documentation](https://github.com/TechEmpower/FrameworkBenchmarks/wiki).
+Also note that there is additional information provided in
+the [Python README](../).
+
+## Description
+
+[**Litestar**](https://github.com/litestar-org/litestar) is a modern, fast (high-performance), web framework for building APIs with Python 3.6+.
+
+The key features are:
+
+* **Fast**: Very high performance, on par with **NodeJS** and **Go**.
+
+* **Fast to code**: Increase the speed to develop features by about 200% to 300% *.
+* **Less bugs**: Reduce about 40% of human (developer) induced errors. *
+* **Intuitive**: Great editor support. <abbr title="also known as auto-complete, autocompletion, IntelliSense">Completion</abbr> everywhere. Less time debugging.
+* **Easy**: Designed to be easy to use and learn. Less time reading docs.
+* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Less bugs.
+* **Robust**: Get production-ready code. With automatic interactive documentation.
+* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: <a href="https://github.com/OAI/OpenAPI-Specification" target="_blank">OpenAPI</a> and <a href="http://json-schema.org/" target="_blank">JSON Schema</a>.
+
+<small>* estimation based on tests on an internal development team, building production applications.</small>
+
+## Test Paths & Sources
+
+All of the test implementations are located within a single file ([app.py](app.py)).
+
+
+## Resources
+
+* [Litestar source code on GitHub](https://github.com/litestar-org/litestar)
+* [Litestar website - documentation](https://litestar.dev)

+ 49 - 0
frameworks/Python/litestar/app-socketify-asgi.py

@@ -0,0 +1,49 @@
+import os
+import multiprocessing
+import logging
+
+import orjson
+from litestar import Litestar, get, MediaType, Response
+from socketify import ASGI
+
+
+@get("/json")
+async def json_serialization() -> Response:
+	return Response(content=orjson.dumps({"message": "Hello, world!"}), media_type=MediaType.JSON)
+
+
+@get("/plaintext", media_type=MediaType.TEXT)
+async def plaintext() -> bytes:
+	return b"Hello, world!"
+
+
+app = Litestar(
+	route_handlers=[
+		json_serialization,
+		plaintext,
+	]
+)
+
+_is_travis = os.environ.get("TRAVIS") == "true"
+
+workers = int(multiprocessing.cpu_count())
+if _is_travis:
+	workers = 2
+
+
+def run_app():
+	ASGI(app).listen(8080, lambda config: logging.info(f"Listening on port http://localhost:{config.port} now\n")).run()
+
+
+def create_fork():
+	n = os.fork()
+	# n greater than 0 means parent process
+	if not n > 0:
+		run_app()
+
+
+# fork limiting the cpu count - 1
+for i in range(1, workers):
+	create_fork()
+
+run_app()  # run app on the main process too :)

+ 152 - 0
frameworks/Python/litestar/app.py

@@ -0,0 +1,152 @@
+import multiprocessing
+import os
+from contextlib import asynccontextmanager
+from pathlib import Path
+from random import randint, sample
+from typing import Any
+
+import asyncpg
+import orjson
+from litestar import Litestar, MediaType, Request, get, Response
+from litestar.contrib.jinja import JinjaTemplateEngine
+from litestar.response import Template
+from litestar.template import TemplateConfig
+
+READ_ROW_SQL = 'SELECT "id", "randomnumber" FROM "world" WHERE id = $1'
+WRITE_ROW_SQL = 'UPDATE "world" SET "randomnumber"=$1 WHERE id=$2'
+ADDITIONAL_ROW = [0, "Additional fortune added at request time."]
+MAX_POOL_SIZE = 1000 // multiprocessing.cpu_count()
+MIN_POOL_SIZE = max(int(MAX_POOL_SIZE / 2), 1)
+
+
+def get_num_queries(queries):
+	try:
+		query_count = int(queries)
+	except (ValueError, TypeError):
+		return 1
+
+	if query_count < 1:
+		return 1
+	if query_count > 500:
+		return 500
+	return query_count
+
+
+connection_pool = None
+
+
+async def setup_database():
+	return await asyncpg.create_pool(
+		user=os.getenv("PGUSER", "benchmarkdbuser"),
+		password=os.getenv("PGPASS", "benchmarkdbpass"),
+		database="hello_world",
+		host="tfb-database",
+		port=5432,
+		min_size=MIN_POOL_SIZE,
+		max_size=MAX_POOL_SIZE,
+	)
+
+
+@asynccontextmanager
+async def lifespan(app: Litestar):
+	# Set up the database connection pool
+	app.state.connection_pool = await setup_database()
+	yield
+	# Close the database connection pool
+	await app.state.connection_pool.close()
+
+
+@get("/json")
+async def json_serialization() -> Response:
+	return Response(
+		content=orjson.dumps({"message": "Hello, world!"}),
+		media_type=MediaType.JSON,
+	)
+
+
+@get("/db")
+async def single_database_query() -> Response:
+	row_id = randint(1, 10000)
+	async with app.state.connection_pool.acquire() as connection:
+		number = await connection.fetchval(READ_ROW_SQL, row_id)
+
+	return Response(
+		content=orjson.dumps({"id": row_id, "randomNumber": number}),
+		media_type=MediaType.JSON,
+	)
+
+
+@get("/queries")
+async def multiple_database_queries(queries: Any = None) -> Response:
+	num_queries = get_num_queries(queries)
+	row_ids = sample(range(1, 10000), num_queries)
+	worlds = []
+
+	async with app.state.connection_pool.acquire() as connection:
+		statement = await connection.prepare(READ_ROW_SQL)
+		for row_id in row_ids:
+			number = await statement.fetchval(row_id)
+			worlds.append({"id": row_id, "randomNumber": number})
+
+	return Response(
+		content=orjson.dumps(worlds),
+		media_type=MediaType.JSON,
+	)
+
+
+@get("/fortunes", media_type=MediaType.HTML)
+async def fortunes(request: Request) -> Template:
+	async with app.state.connection_pool.acquire() as connection:
+		fortunes = await connection.fetch("SELECT * FROM Fortune")
+
+	fortunes.append(ADDITIONAL_ROW)
+	fortunes.sort(key=lambda row: row[1])
+	return Template(
+		"fortune.html",
+		context={"fortunes": fortunes, "request": request},
+		media_type=MediaType.HTML,
+	)
+
+
+@get("/updates")
+async def database_updates(queries: Any = None) -> bytes:
+	num_queries = get_num_queries(queries)
+	# To avoid deadlock
+	ids = sorted(sample(range(1, 10000 + 1), num_queries))
+	numbers = sorted(sample(range(1, 10000), num_queries))
+	updates = list(zip(ids, numbers, strict=False))
+
+	worlds = [{"id": row_id, "randomNumber": number} for row_id, number in updates]
+
+	async with app.state.connection_pool.acquire() as connection:
+		statement = await connection.prepare(READ_ROW_SQL)
+		for row_id, _ in updates:
+			await statement.fetchval(row_id)
+		await connection.executemany(WRITE_ROW_SQL, updates)
+
+	return Response(
+		content=orjson.dumps(worlds),
+		media_type=MediaType.JSON,
+	)
+
+
+@get("/plaintext", media_type=MediaType.TEXT)
+async def plaintext() -> bytes:
+	return b"Hello, world!"
+
+
+app = Litestar(
+	lifespan=[lifespan],
+	template_config=TemplateConfig(
+		directory=Path("templates"),
+		engine=JinjaTemplateEngine,
+	),
+	route_handlers=[
+		json_serialization,
+		single_database_query,
+		multiple_database_queries,
+		fortunes,
+		database_updates,
+		plaintext,
+	],
+)

+ 185 - 0
frameworks/Python/litestar/app_orm.py

@@ -0,0 +1,185 @@
+import logging
+import multiprocessing
+import os
+from contextlib import asynccontextmanager
+from operator import attrgetter
+from pathlib import Path
+from random import randint, sample
+from typing import Any
+
+import orjson
+from litestar import Litestar, MediaType, Request, get, Response
+from litestar.contrib.jinja import JinjaTemplateEngine
+from litestar.response import Template
+from litestar.template import TemplateConfig
+from sqlalchemy import Column, Integer, String, select
+from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm.attributes import flag_modified
+
+logger = logging.getLogger(__name__)
+
+Base = declarative_base()
+
+
+class World(Base):
+	__tablename__ = "world"
+	id = Column(Integer, primary_key=True)
+	randomnumber = Column(Integer)
+
+	def __json__(self):
+		return {"id": self.id, "randomnumber": self.randomnumber}
+
+
+sa_data = World.__table__
+
+
+class Fortune(Base):
+	__tablename__ = "fortune"
+	id = Column(Integer, primary_key=True)
+	message = Column(String)
+
+
+sa_fortunes = Fortune.__table__
+
+ADDITIONAL_FORTUNE = Fortune(id=0, message="Additional fortune added at request time.")
+MAX_POOL_SIZE = 1000 // multiprocessing.cpu_count()
+
+sort_fortunes_key = attrgetter("message")
+
+template_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "templates")
+
+
+async def setup_database():
+	dsn = "postgresql+asyncpg://%s:%s@tfb-database:5432/hello_world" % (
+		os.getenv("PGPASS", "benchmarkdbuser"),
+		os.getenv("PGPASS", "benchmarkdbpass"),
+	)
+
+	engine = create_async_engine(
+		dsn,
+		future=True,
+		pool_size=MAX_POOL_SIZE,
+		connect_args={
+			"ssl": False  # NEEDED FOR NGINX-UNIT OTHERWISE IT FAILS
+		},
+	)
+	return async_sessionmaker(engine)
+
+
+@asynccontextmanager
+async def lifespan(app: Litestar):
+	# Set up the database connection pool
+	app.state.db_session = await setup_database()
+	yield
+	# Close the database connection pool
+	await app.state.db_session().close()
+
+
+def get_num_queries(queries):
+	try:
+		query_count = int(queries)
+	except (ValueError, TypeError):
+		return 1
+
+	if query_count < 1:
+		return 1
+	if query_count > 500:
+		return 500
+	return query_count
+
+
+@get("/json")
+async def json_serialization() -> Response:
+	return Response(
+		content=orjson.dumps({"message": "Hello, world!"}),
+		media_type=MediaType.JSON,
+	)
+
+
+@get("/db")
+async def single_database_query() -> Response:
+	id_ = randint(1, 10000)
+
+	async with app.state.db_session() as sess:
+		result = await sess.get(World, id_)
+
+	return Response(
+		content=orjson.dumps(result.__json__()),
+		media_type=MediaType.JSON,
+	)
+
+
+@get("/queries")
+async def multiple_database_queries(queries: Any = None) -> Response:
+	num_queries = get_num_queries(queries)
+	data = []
+
+	async with app.state.db_session() as sess:
+		for id_ in sample(range(1, 10001), num_queries):
+			result = await sess.get(World, id_)
+			data.append(result.__json__())
+
+	return Response(
+		content=orjson.dumps(data),
+		media_type=MediaType.JSON,
+	)
+
+
+@get("/fortunes", media_type=MediaType.HTML)
+async def fortunes(request: Request) -> Template:
+	async with app.state.db_session() as sess:
+		ret = await sess.execute(select(Fortune.id, Fortune.message))
+		data = ret.all()
+
+	data.append(ADDITIONAL_FORTUNE)
+	data.sort(key=sort_fortunes_key)
+
+	return Template(
+		"fortune.jinja",
+		context={"request": request, "fortunes": data},
+		media_type=MediaType.HTML,
+	)
+
+
+@get("/updates")
+async def database_updates(queries: Any = None) -> Response:
+	num_queries = get_num_queries(queries)
+
+	ids = sorted(sample(range(1, 10000 + 1), num_queries))
+	data = []
+	async with app.state.db_session.begin() as sess:
+		for id_ in ids:
+			world = await sess.get(World, id_, populate_existing=True)
+			world.randomnumber = randint(1, 10000)
+			# force sqlalchemy to UPDATE entry even if the value has not changed
+			# doesn't make sense in a real application, added only for pass `tfb verify`
+			flag_modified(world, "randomnumber")
+			data.append(world.__json__())
+
+	return Response(
+		content=orjson.dumps(data),
+		media_type=MediaType.JSON,
+	)
+
+
+@get("/plaintext", media_type=MediaType.TEXT)
+async def plaintext() -> bytes:
+	return b"Hello, world!"
+
+
+app = Litestar(
+	template_config=TemplateConfig(
+		directory=Path("templates"),
+		engine=JinjaTemplateEngine,
+	),
+	lifespan=[lifespan],
+	route_handlers=[
+		json_serialization,
+		single_database_query,
+		multiple_database_queries,
+		fortunes,
+		database_updates,
+		plaintext,
+	],
+)

+ 186 - 0
frameworks/Python/litestar/benchmark_config.json

@@ -0,0 +1,186 @@
+{
+  "framework": "litestar",
+  "tests": [
+    {
+      "default": {
+        "json_url": "/json",
+        "fortune_url": "/fortunes",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "Litestar",
+        "language": "Python",
+        "flavor": "Python3",
+        "orm": "Raw",
+        "platform": "asyncio",
+        "webserver": "Gunicorn",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Litestar",
+        "notes": "",
+        "versus": "None"
+      },
+      "socketify-asgi": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "Litestar",
+        "language": "Python",
+        "flavor": "Python3",
+        "orm": "Raw",
+        "platform": "asyncio",
+        "webserver": "Socketify.py",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Litestar [Socketify.py ASGI]",
+        "notes": "",
+        "versus": "None"
+      },
+      "gunicorn": {
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "Litestar",
+        "language": "Python",
+        "flavor": "Python3",
+        "orm": "Raw",
+        "platform": "asyncio",
+        "webserver": "Gunicorn",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Litestar-gunicorn",
+        "notes": "",
+        "versus": "None"
+      },
+      "gunicorn-orm": {
+        "fortune_url": "/fortunes",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "Litestar",
+        "language": "Python",
+        "flavor": "Python3",
+        "orm": "Full",
+        "platform": "asyncio",
+        "webserver": "Gunicorn",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Litestar-gunicorn-orm",
+        "notes": "",
+        "versus": "None"
+      },
+      "granian": {
+        "json_url": "/json",
+        "fortune_url": "/fortunes",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "Litestar",
+        "language": "Python",
+        "flavor": "Python3",
+        "orm": "Raw",
+        "platform": "asyncio",
+        "webserver": "Granian",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Litestar-granian",
+        "notes": "",
+        "versus": "None"
+      },
+      "granian-orm": {
+        "json_url": "/json",
+        "fortune_url": "/fortunes",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "Litestar",
+        "language": "Python",
+        "flavor": "Python3",
+        "orm": "Raw",
+        "platform": "asyncio",
+        "webserver": "Granian",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Litestar-granian-orm",
+        "notes": "",
+        "versus": "None"
+      },
+      "nginx-unit": {
+        "json_url": "/json",
+        "fortune_url": "/fortunes",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "Litestar",
+        "language": "Python",
+        "flavor": "Python3",
+        "orm": "Raw",
+        "platform": "asyncio",
+        "webserver": "nginx-unit",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Litestar-nginx-unit",
+        "notes": "",
+        "versus": "None",
+        "tags": [
+          "broken"
+        ]
+      },
+      "uvicorn": {
+        "json_url": "/json",
+        "fortune_url": "/fortunes",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "Litestar",
+        "language": "Python",
+        "flavor": "Python3",
+        "orm": "Raw",
+        "platform": "asyncio",
+        "webserver": "Uvicorn",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Litestar-uvicorn",
+        "notes": "",
+        "versus": "None"
+      }
+    }
+  ]
+}

+ 184 - 0
frameworks/Python/litestar/config.toml

@@ -0,0 +1,184 @@
+[framework]
+name = "litestar"
+
+[gunicorn]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?queries="
+urls.update = "/updates?queries="
+urls.fortune = "/fortunes"
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "None"
+webserver = "Gunicorn"
+versus = "None"
+
+[socketify-asgi-pypy]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "None"
+webserver = "Socketify.py"
+versus = "None"
+
+[socketify-asgi]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "None"
+webserver = "Socketify.py"
+versus = "None"
+
+[gunicorn-orjson]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?queries="
+urls.update = "/updates?queries="
+urls.fortune = "/fortunes"
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "None"
+webserver = "Gunicorn"
+versus = "None"
+
+
+[gunicorn-orm]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?queries="
+urls.update = "/updates?queries="
+urls.fortune = "/fortunes"
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Full"
+platform = "None"
+webserver = "Gunicorn"
+versus = "None"
+
+[hypercorn]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?queries="
+urls.update = "/updates?queries="
+urls.fortune = "/fortunes"
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "None"
+webserver = "Hypercorn"
+versus = "None"
+
+
+[hypercorn-orjson]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?queries="
+urls.update = "/updates?queries="
+urls.fortune = "/fortunes"
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "None"
+webserver = "Hypercorn"
+versus = "None"
+
+[nginx-unit]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?queries="
+urls.update = "/updates?queries="
+urls.fortune = "/fortunes"
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "None"
+webserver = "nginx-unit"
+versus = "None"
+
+[nginx-unit-orjson]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?queries="
+urls.update = "/updates?queries="
+urls.fortune = "/fortunes"
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "None"
+webserver = "nginx-unit"
+versus = "None"
+
+
+[uvicorn]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?queries="
+urls.update = "/updates?queries="
+urls.fortune = "/fortunes"
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "None"
+webserver = "uvicorn"
+versus = "None"
+
+[uvicorn-orjson]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?queries="
+urls.update = "/updates?queries="
+urls.fortune = "/fortunes"
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "None"
+webserver = "uvicorn"
+versus = "None"

+ 20 - 0
frameworks/Python/litestar/litestar-granian-orm.dockerfile

@@ -0,0 +1,20 @@
+FROM python:3.13
+
+WORKDIR /litestar
+
+RUN python -m venv /opt/venv
+ENV PATH="/opt/venv/bin:$PATH"
+
+RUN pip3 install cython==3.0.12
+
+COPY requirements.txt requirements-sqlalchemy.txt requirements-granian.txt ./
+
+RUN pip3 install -r requirements.txt -r requirements-sqlalchemy.txt -r requirements-granian.txt
+
+COPY . ./
+
+EXPOSE 8080
+
+ENV CONNECTION=ORM
+
+CMD granian --interface asgi app:app --host '0.0.0.0' --port 8080 --workers $(nproc)

+ 18 - 0
frameworks/Python/litestar/litestar-granian.dockerfile

@@ -0,0 +1,18 @@
+FROM python:3.13
+
+WORKDIR /litestar
+
+RUN python -m venv /opt/venv
+ENV PATH="/opt/venv/bin:$PATH"
+
+RUN pip3 install cython==3.0.12
+
+COPY requirements.txt requirements-granian.txt ./
+
+RUN pip3 install -r requirements.txt -r requirements-granian.txt
+
+COPY . ./
+
+EXPOSE 8080
+
+CMD granian --interface asgi app:app --host '0.0.0.0' --port 8080 --workers $(nproc)

+ 20 - 0
frameworks/Python/litestar/litestar-gunicorn-orm.dockerfile

@@ -0,0 +1,20 @@
+FROM python:3.13
+
+WORKDIR /litestar
+
+RUN python -m venv /opt/venv
+ENV PATH="/opt/venv/bin:$PATH"
+
+RUN pip3 install cython==3.0.12
+
+COPY requirements.txt requirements-sqlalchemy.txt requirements-gunicorn.txt ./
+
+RUN pip3 install -r requirements.txt -r requirements-sqlalchemy.txt -r requirements-gunicorn.txt
+
+COPY . ./
+
+EXPOSE 8080
+
+ENV CONNECTION=ORM
+
+CMD gunicorn app_orm:app -k uvicorn_worker.UvicornWorker -c litestar_conf.py

+ 18 - 0
frameworks/Python/litestar/litestar-gunicorn.dockerfile

@@ -0,0 +1,18 @@
+FROM python:3.13
+
+WORKDIR /litestar
+
+RUN python -m venv /opt/venv
+ENV PATH="/opt/venv/bin:$PATH"
+
+RUN pip3 install cython==3.0.12
+
+COPY requirements.txt requirements-gunicorn.txt ./
+
+RUN pip3 install -r requirements.txt -r requirements-gunicorn.txt
+
+COPY . ./
+
+EXPOSE 8080
+
+CMD gunicorn app:app -k uvicorn_worker.UvicornWorker -c litestar_conf.py

+ 20 - 0
frameworks/Python/litestar/litestar-nginx-unit.dockerfile

@@ -0,0 +1,20 @@
+FROM nginx/unit:1.29.1-python3.13
+
+WORKDIR /litestar
+
+RUN python -m venv /opt/venv
+ENV PATH="/opt/venv/bin:$PATH"
+
+RUN pip3 install cython==3.0.12
+
+COPY requirements.txt ./
+
+RUN pip3 install -r requirements.txt
+
+COPY . ./
+
+COPY ./nginx-unit-config.sh /docker-entrypoint.d/
+
+ENV PGSSLMODE disable
+
+EXPOSE 8080

+ 19 - 0
frameworks/Python/litestar/litestar-socketify-asgi.dockerfile

@@ -0,0 +1,19 @@
+FROM python:3.13-bullseye
+
+WORKDIR /litestar
+
+RUN python -m venv /opt/venv
+ENV PATH="/opt/venv/bin:$PATH"
+
+RUN apt-get update; apt-get install libuv1 -y
+RUN pip3 install cython==3.0.12
+
+COPY requirements-socketify.txt ./
+
+RUN pip3 install -r requirements-socketify.txt
+
+COPY . ./
+
+EXPOSE 8080
+
+CMD python ./app-socketify-asgi.py

+ 18 - 0
frameworks/Python/litestar/litestar-uvicorn.dockerfile

@@ -0,0 +1,18 @@
+FROM python:3.13
+
+WORKDIR /litestar
+
+RUN python -m venv /opt/venv
+ENV PATH="/opt/venv/bin:$PATH"
+
+RUN pip3 install cython==3.0.12
+
+COPY requirements.txt requirements-uvicorn.txt ./
+
+RUN pip3 install -r requirements.txt -r requirements-uvicorn.txt
+
+COPY . ./
+
+EXPOSE 8080
+
+CMD uvicorn app:app --host 0.0.0.0 --port 8080 --workers $(nproc) --log-level error

+ 18 - 0
frameworks/Python/litestar/litestar.dockerfile

@@ -0,0 +1,18 @@
+FROM python:3.13
+
+WORKDIR /litestar
+
+RUN python -m venv /opt/venv
+ENV PATH="/opt/venv/bin:$PATH"
+
+RUN pip3 install cython==3.0.12
+
+COPY requirements.txt requirements-gunicorn.txt ./
+
+RUN pip3 install -r requirements.txt -r requirements-gunicorn.txt
+
+COPY . ./
+
+EXPOSE 8080
+
+CMD gunicorn app:app -k uvicorn_worker.UvicornWorker -c litestar_conf.py

+ 12 - 0
frameworks/Python/litestar/litestar_conf.py

@@ -0,0 +1,12 @@
+import multiprocessing
+import os
+
+_is_travis = os.environ.get("TRAVIS") == "true"
+
+workers = multiprocessing.cpu_count()
+
+bind = "0.0.0.0:8080"
+keepalive = 120
+errorlog = "-"
+pidfile = "/tmp/litestar.pid"
+loglevel = "error"

+ 26 - 0
frameworks/Python/litestar/nginx-unit-config.sh

@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+config='{'
+config+='  "listeners": {'
+config+='    "*:8080": {'
+config+='      "pass": "applications/litestar"'
+config+='    }'
+config+='  },'
+config+='  "applications": {'
+config+='    "litestar": {'
+config+='      "type": "python",'
+config+='      "path": "/litestar",'
+config+='      "home": "/opt/venv/",'
+config+='      "protocol": "asgi",'
+config+='      "module": "app",'
+config+='      "callable": "app",'
+config+='      "processes": '"$(nproc)"','
+config+='    }'
+config+='  }',
+config+='  "access_log": "/dev/null"'
+config+='}'
+
+curl -X PUT \
+     --data-binary "$config" \
+     --unix-socket /var/run/control.unit.sock \
+     http://localhost/config

+ 2 - 0
frameworks/Python/litestar/requirements-granian.txt

@@ -0,0 +1,2 @@
+granian==2.3.1
+litestar-granian==0.13.0

+ 2 - 0
frameworks/Python/litestar/requirements-gunicorn.txt

@@ -0,0 +1,2 @@
+gunicorn==23.0.0
+uvicorn-worker==0.3.0

+ 3 - 0
frameworks/Python/litestar/requirements-socketify.txt

@@ -0,0 +1,3 @@
+orjson==3.10.16
+litestar==2.16.0
+git+https://github.com/cirospaciari/socketify.py.git@main#socketify

+ 3 - 0
frameworks/Python/litestar/requirements-sqlalchemy.txt

@@ -0,0 +1,3 @@
+psycopg2==2.9.10
+SQLAlchemy==2.0.40
+litestar[sqlalchemy]==2.16.0

+ 3 - 0
frameworks/Python/litestar/requirements-uvicorn.txt

@@ -0,0 +1,3 @@
+uvicorn==0.34.2
+uvloop==0.21.0
+httptools==0.6.4

+ 4 - 0
frameworks/Python/litestar/requirements.txt

@@ -0,0 +1,4 @@
+asyncpg==0.30.0
+litestar==2.16.0
+Jinja2==3.1.6
+orjson==3.10.18

+ 79 - 0
frameworks/Python/litestar/ruff.toml

@@ -0,0 +1,79 @@
+fix = true
+line-length = 120
+src = ["evalai_backend", "tests"]
+target-version = "py312"
+
+[format]
+docstring-code-format = true
+docstring-code-line-length = 120
+indent-style = "tab"
+quote-style = "double"
+skip-magic-trailing-comma = false
+
+[lint]
+ignore = [
+    "TID252",
+    "E501",
+    "S101",
+    "S102",
+    "S104",
+    "S324",
+    "EXE002",
+    "D100",
+    "D102",
+    "D203", # clash with formatter
+    "D206", # we use tabs
+    "D103",
+    "D104",
+    "D105",
+    "D106",
+    "D101",
+    "D107",
+    "D212",
+    "D211",
+    "PGH003",
+    "PGH004",
+    "N811",
+    "N804",
+    "N818",
+    "N806",
+    "N815",
+    "ARG001",
+    "ARG002",
+    "DTZ003",
+    "DTZ005",
+    "RSE102",
+    "SLF001",
+    "PLR",
+    "INP",
+    "TRY",
+    "SIM300",
+    "SIM114",
+    "DJ008",
+    "FIX002",
+    "S603",
+    "S607",
+    "TD002",
+    "TD003",
+    "W191", # We use tabs
+    "COM812", # missing trailing comma
+    "ISC001", # handled by formatter
+    "FBT001",
+    "FBT002",
+    "FBT003",
+    "ERA001",
+]
+select = [
+    "ALL"
+]
+
+[lint.flake8-annotations]
+suppress-none-returning = true
+
+[lint.flake8-quotes]
+docstring-quotes = "double"
+inline-quotes = "double"
+multiline-quotes = "double"
+
+[lint.isort]
+combine-as-imports = true

+ 10 - 0
frameworks/Python/litestar/templates/fortune.html

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head><title>Fortunes</title></head>
+<body>
+<table>
+<tr><th>id</th><th>message</th></tr>
+{% for fortune in fortunes %}<tr><td>{{ fortune[0] }}</td><td>{{ fortune[1]|e }}</td></tr>
+{% endfor %}</table>
+</body>
+</html>

+ 10 - 0
frameworks/Python/litestar/templates/fortune.jinja

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head><title>Fortunes</title></head>
+<body>
+<table>
+<tr><th>id</th><th>message</th></tr>
+{% for fortune in fortunes %}<tr><td>{{ fortune.id }}</td><td>{{ fortune.message|e }}</td></tr>
+{% endfor %}</table>
+</body>
+</html>