Pyronaut 发布:基于 GraalVM 与 Micronaut 的 Python 应用平台
Pyronaut
Pyronaut 发布,这是一个基于 GraalVM 和 Micronaut 构建生产级 Python 服务的应用平台,通过将 Python AST 与 Java 编译器结合,把 Micronaut 的 AOT 编译、构建期校验等能力带到 Python。
今天我们很高兴地宣布 Pyronaut 正式发布,这是一个高性能的 Python 应用平台,用于构建生产级服务,基于 GraalVM 和 Micronaut 构建。
什么是 Pyronaut?
Pyronaut 将 Micronaut 和 GraalVM 的相同理念和优势(AOT 编译、构建时验证、源代码处理等)带到了 Python,方法是将 Python AST 与 Java 编译器融合,并利用 GraalVM 和 Graal JIT 优化运行时执行,从而实现高性能、可扩展的 Python 服务。
得益于 AI 的发展,Python 是当今世界上最流行的语言之一,而从今天起,Python 可以在 JVM 生态中最成熟、可扩展且流行的框架之一上使用。选择 Python 作为服务器端语言不再意味着吞吐量降低和延迟升高。
最小的 Pyronaut 应用
创建 main.py:
from micronaut.http.annotation import Get
@Get("/")
def read_root() -> dict:
return {"Hello": "World"}
@Get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None) -> dict:
return {"item_id": item_id, "q": q}运行它:
pyronaut dev main.py部署它:
pyronaut run main.py由 Netty 驱动的可扩展性
Micronaut 构建于 Netty 之上,Netty 是任何语言中最具可扩展性和高性能的异步框架之一。
使用 Pyronaut,你的 Python 代码由同一个基于 Netty 的 HTTP 服务器提供服务,并由 Graal JIT 优化。在我们的基准测试中,这意味着吞吐量是 FastAPI 的 2.6 倍,是 Flask 的 6.5 倍,同时延迟更低(更多内容见本文后续部分)。
我们将 Python asyncio 管道直接接入 Netty 事件循环,这样你就可以在 Pyronaut 中原生使用 async/await 来编写可扩展的非阻塞服务。
import asyncio
from micronaut.http.annotation import Get
@Get("/slow-hello")
async def slow_hello() -> str:
await asyncio.sleep(0.5) # suspends the coroutine, not the event loop
return "hello"没有 asyncio.run(),也没有 uvicorn。当 Pyronaut 调用你的函数时,事件循环已经存在,asyncio.gather 和 asyncio.TaskGroup 等都能如你所愿地工作。
我们将这一能力扩展到了流式处理,因此你可以使用 async for 为 SSE、websockets 和其他用例编写流式服务。任何包含 yield 的 async def 路由都会以背压方式流式返回给客户端:
from dataclasses import dataclass
from typing import AsyncIterator
from java.time import Duration
from micronaut.http import MediaType
from micronaut.http.annotation import Get
from micronaut.http.sse import Event
from micronaut.serde.annotation import Serdeable
from micronaut_asyncio import as_async_iterable
from reactor.core.publisher import Flux
@Serdeable
@dataclass
class Tick:
index: int
label: str
@Get(value="/ticks", produces=MediaType.TEXT_EVENT_STREAM)
async def ticks() -> AsyncIterator[Event[Tick]]:
every_second = Flux.interval(Duration.ofSeconds(1)).take(3)
async with as_async_iterable(every_second) as seconds:
async for second in seconds:
yield Event.of(Tick(second, f"tick-{second}"))任何响应式 Publisher 或 Java CompletableFuture 都可以与 await 一起使用,这样 Reactor 等 Java 库就能与原生 Python async/await“开箱即用”。例如,你可以使用内置的 Micronaut HttpClient 通过 async/await 进行非阻塞 REST 调用:
from dataclasses import dataclass
from typing import Annotated
from jakarta.inject import Inject
from micronaut.http import HttpRequest
from micronaut.http.annotation import Get
from micronaut.http.client import HttpClient
from micronaut.http.client.annotation import Client
from micronaut.serde.annotation import Serdeable
@Serdeable
@dataclass
class Repo:
name: str
stargazers_count: int
github: Annotated[HttpClient, Inject, Client("https://api.github.com")]
@Get("/repos/{owner}/{name}")
async def repo(owner: str, name: str) -> Repo:
request = HttpRequest.GET(f"/repos/{owner}/{name}").header("User-Agent", "pyronaut")
return await github.retrieve(request, Repo)使用 Micronaut Data 进行数据访问
大多数 Python Web 框架将数据访问留给你自己处理。你选择一个 ORM,连接会话,然后在运行时才发现查询是否真的有效。
Pyronaut 包含 Micronaut Data,它允许你在 Python 中定义仓库,并在构建时为你的首选数据库预计算 SQL 查询。没有运行时查询转换,引用不存在属性的查询会在构建时失败,而不是在生产环境中导致请求失败:
from dataclasses import dataclass
from typing import Annotated
from jakarta.inject import Inject
from jakarta.transaction import Transactional
from micronaut.data.annotation import GeneratedValue, Id, MappedEntity, Query
from micronaut.data.jdbc.annotation import JdbcRepository
from micronaut.data.model import Page, Pageable
from micronaut.data.repository import CrudRepository
from micronaut.http.annotation import Body, Get, Post
from micronaut.serde.annotation import Serdeable
@Serdeable
@MappedEntity
@dataclass
class Rocket:
id: Annotated[int | None, Id, GeneratedValue]
name: str
thrust: float
@JdbcRepository(dialect="ORACLE")
class RocketRepository(CrudRepository[Rocket, int]):
def findByNameContains(self, fragment: str) -> list[Rocket]: ...
def findTop3ByThrustGreaterThanOrderByThrustDesc(self, thrust: float) -> list[Rocket]: ...
def findNameByThrustLessThan(self, thrust: float) -> list[str]: ...
def findAll(self, pageable: Pageable) -> Page[Rocket]: ...
@Query("SELECT * FROM rocket WHERE LOWER(name) LIKE LOWER(:pattern)")
def search(self, pattern: str) -> list[Rocket]: ...
def update(self, id: Annotated[int, Id], thrust: float) -> None: ...
rockets: Annotated[RocketRepository, Inject]
@Post("/rockets")
@Transactional
def launch(fleet: Annotated[list[Rocket], Body]) -> list[Rocket]:
return [rockets.save(rocket) for rocket in fleet]
@Get("/rockets/strongest/{thrust}")
def strongest(thrust: float) -> list[Rocket]:
return rockets.findTop3ByThrustGreaterThanOrderByThrustDesc(thrust)
@Get("/rockets/page/{number}")
def page(number: int) -> Page[Rocket]:
return rockets.findAll(Pageable.from_(number, 2))派生查找器、投影、分页、显式查询、部分更新和事务全都开箱即用:
$ curl http://localhost:8080/rockets/strongest/5000
[{"id":5,"name":"Starship","thrust":74000.0},{"id":1,"name":"Saturn V","thrust":35100.0},{"id":3,"name":"Ariane 6","thrust":10400.0}]相同的编程模型适用于 JDBC、R2DBC、Hibernate/JPA、Hibernate Reactive、MongoDB 和 Azure Cosmos DB,因此 Python 开发者可以获得 Micronaut 用户多年来所依赖的同样成熟的数据访问层。
我们将 Python 和 javac 结合在了一起
Pyronaut 是一个 Python 源代码处理器,它分析 Python 源代码,并在每次看到 Java 注解时自动呈现 Python 装饰器。通过这种方法,我们能够将成熟的 Micronaut 框架核心的全部内容带到 Python 语言中,包括构建时验证和提前计算。
这在实践中意味着 Micronaut 的每个部分都能在 Python 中“开箱即用”,Python 开发者可以完全访问整个平台。
你可以使用 Micronaut Serialization 编写可序列化的数据类,它会在构建时计算序列化器/反序列化器,从而提供快速高效的 JSON 序列化/反序列化:
from dataclasses import dataclass
from typing import Annotated
from com.fasterxml.jackson.annotation import JsonProperty
from micronaut.serde.annotation import Serdeable
@Serdeable
@dataclass
class Book:
title: str
quantity: Annotated[int, JsonProperty("qty")] 你可以根据 Python 中定义的 Micronaut 路由在构建时生成 OpenAPI 规范,并使用 docstring 来描述 API:
from io.swagger.v3.oas.annotations import OpenAPIDefinition
from io.swagger.v3.oas.annotations.info import Info
from micronaut.http.annotation import Get
OpenAPIDefinition(info=Info(title="Greetings", version="1.0"))
@Get("/hello/{name}")
def greet(name: str) -> str:
"""
Greets a person by name.
@param name The person's name
@return The greeting
"""
return f"Hello {name}!"该规范在编译期间生成,并在 /swagger/greetings-1.0.yml 处提供:
openapi: 3.0.1
info:
title: Greetings
version: "1.0"
paths:
/hello/{name}:
get:
summary: Greets a person by name.
description: Greets a person by name.
operationId: greet
parameters:
- name: name
in: path
description: The person's name
required: true
schema:
type: string
responses:
"200":
description: The greeting
content:
application/json:
schema:
type: string你可以使用 Python 在 Kafka、RabbitMQ、JMS 和其他消息系统中定义消息消费者和生产者:
from micronaut.configuration.kafka.annotation import KafkaKey, KafkaListener, OffsetReset, Topic
from micronaut.context.annotation import Requires
@KafkaListener(offsetReset=OffsetReset.EARLIEST)
class ProductListener:
@Topic("my-products")
def receive(self, brand: Annotated[str, KafkaKey], name: str) -> None:
LOG.info("Got Product - %s by %s", name, brand)而且你可以使用 Pyronaut 编写高性能、低内存的 MCP Tools:
from micronaut.context.annotation import Requires, Prototype
from micronaut.mcp.annotations import Tool
from micronaut.mcp.server.context import MicronautMcpTransportContext
@Prototype
class Tools:
@Tool(description="Evaluate a chess position using a FEN string.")
def fen_evaluation(self, fen: str, ctx: MicronautMcpTransportContext) -> str:
if fen == "r1bqk2r/ppp2ppp/2n5/1BbpP3/3Nn3/8/PPP2PPP/RNBQK2R w KQkq - 1 8":
return "+0.12"
return "+0.0"Python 的构建时处理
通过将所有工作都在构建时完成,Pyronaut 为人类和编码代理提供了错误的早期验证,否则他们必须运行你的应用程序才能找出问题。源代码中的错误会作为构建时错误暴露出来。配置错误会在应用程序运行之前被检测到。
使用 Pyronaut 时,代理和人类的开发循环都大大缩短。
例如,如果你在 Micronaut Data 查询方法中拼错了一个属性:
@JdbcRepository(dialect=Dialect.ORACLE)
class BookRepository(CrudRepository[Book, int], Protocol):
def findByTitelContains(self, fragment: str) -> list[Book]: ...Pyronaut 会拒绝处理它,并准确告诉你哪里错了:
$ pyronaut process
Checking main sources...
Full rebuild selected for main sources (2 files)
Processing failed: Pyronaut processing failed: Unable to implement Repository method: python.BookRepository.findByTitelContains(String fragment). Cannot query entity [Book] on non-existent property: Titel [title]配置也得到同样的处理。pyronaut validate-config 会根据解析后的应用程序类路径检查你的 application.toml,而 pyronaut dev、pyronaut run 和 pyronaut test 会在你的应用程序启动之前自动运行相同的验证,并将 JSON 和 HTML 报告写入 __pyronaut__/reports/config-validation:
pyronaut validate-config --scenario production在 JVM 或 Crema 上优化执行
Pyronaut 具有一个符合人体工程学、对代理友好的 CLI,可在 JVM 或 GraalVM Crema 上执行 Python 或 Java 代码。
Crema 提供了一个预编译的原生基础镜像,可降低内存需求、缩短启动时间并加快代理的开发循环。应用程序可以部署到 JVM 或 Crema,具体取决于优先考虑的是峰值性能还是更快的启动速度。不需要进行原生镜像构建(不过如果你想要完全原生,仍然可以这样做)。
为 JVM 构建 Docker 镜像,它在预热后提供最佳的峰值吞吐量:
pyronaut build main.py --jvm --docker在 Crema 基础镜像之上构建 Docker 镜像,它为你提供原生启动时间和内存使用,而无需为每个应用程序进行原生镜像构建:
pyronaut build main.py --native-base --docker使用 Crema 时,只有可复用的基础镜像会使用原生镜像构建。后续的应用程序构建只是将你处理过的类和依赖项作为薄层添加在其上,因此你不必在每次代码更改时都付出原生镜像构建的成本。
使用 Python 和 Java 库,用 Testcontainers 为一切提供支持
GraalPy 与许多现有 Python 库广泛兼容,所有这些库都可以在 Pyronaut 应用程序中使用。
开发者和代理还可以随手使用整个 JVM 生态系统的库。
你可以轻松地包含 Java 库并在Docker 容器中启动测试资源,所有这些都来自单个 Python 脚本。以下是一个完整的 Pyronaut 应用程序,具有 HTTP 端点和数据库访问:
from dataclasses import dataclass
from typing import Annotated, Protocol
from jakarta.inject import Inject
from micronaut.data.annotation import GeneratedValue, Id, MappedEntity
from micronaut.data.jdbc.annotation import JdbcRepository
from micronaut.data.model.query.builder.sql import Dialect
from micronaut.data.repository import CrudRepository
from micronaut.http.annotation import Get, Post
from micronaut.serde.annotation import Serdeable
from pyronaut.build import AppConfig, Dependency
Dependency(group="io.micronaut.data", module="micronaut-data-jdbc")
Dependency(group="io.micronaut.sql", module="micronaut-jdbc-hikari")
Dependency(group="com.oracle.database.jdbc", module="ojdbc11")
AppConfig(name="datasources.default.db-type", value="oracle")
AppConfig(name="datasources.default.dialect", value="ORACLE")
AppConfig(name="datasources.default.schema-generate", value="CREATE_DROP")
@Serdeable
@MappedEntity
@dataclass
class Book:
id: Annotated[int | None, Id, GeneratedValue]
title: str
@JdbcRepository(dialect=Dialect.ORACLE)
class BookRepository(CrudRepository[Book, int], Protocol):
def findByTitleContains(self, fragment: str) -> list[Book]: ...
books: Annotated[BookRepository, Inject]
@Post("/books/{title}")
def create(title: str) -> Book:
return books.save(Book(None, title))
@Get("/books/search/{fragment}")
def search(fragment: str) -> list[Book]:
return books.findByTitleContains(fragment)
@Get("/books/count")
def count() -> int:
return books.count()使用 pyronaut dev main.py 启动它,带有 Testcontainers 的 Docker 容器将自动启动以启动 Oracle 数据库。对代码的更改只会重新加载代码,而不会重新加载数据库。
$ curl -X POST http://localhost:8080/books/Dune
{"id":1,"title":"Dune"}
$ curl http://localhost:8080/books/search/Du
[{"id":1,"title":"Dune"}]Python 服务的 JVM 级性能
性能一直是 Micronaut 和 GraalVM 长期以来的重点。我们一直在努力优化各项指标(启动时间、内存、吞吐量)。
这一理念在今天依然没有改变,而借助 Pyronaut,它已经能够以更低的延迟提供比任何其他同类 Python 框架更高的吞吐量。
我们在同一台 3 OCPU 虚拟机上,通过 HTTPS/HTTP2 使用相同的 Hyperfoil 负载爬坡,分别对 Pyronaut、运行在 Granian 上的 FastAPI 以及运行在 Gunicorn 上的 Flask 进行了压测。只有满足所有延迟 SLA(p50 低于 100ms,p95 低于 200ms,p99 低于 1s)的阶梯才算通过。Pyronaut 持续达到每秒 35,351 个请求,是 FastAPI 的 2.6 倍,Flask 的 6.5 倍:

而且随着负载增加,延迟保持平稳,p99 延迟在每秒约 22,000 个请求之前一直低于 1ms。在每秒 11,655 个请求时,Pyronaut 的 p99 延迟为 0.51ms,而 FastAPI 为 6.62ms:

测试设置以及 p50 和 p95 数据可在性能页面上查看。
需要说明的是,Pyronaut 的性能目前还无法与使用 Java 的 Micronaut 相媲美。Python 代码仍然运行在 GIL 之下,这限制了可以并行完成的工作量。
不过我们才刚刚开始,GraalPy 团队正在努力开发无 GIL 版本的 GraalPy,我们预计在不久的将来能够大幅提升吞吐量数据。消除 GIL 之后,我们相信可以接近使用 Java 的 Micronaut 的性能。
一个虚拟机运行你的整个应用
GraalVM 一直致力于用一个虚拟机统管一切。一个能够运行 Java 和其他 Truffle 语言的单一虚拟机。
借助 Pyronaut,这一优势比以往任何时候都更加明显。
我们将 Python 的FastAPI 全栈模板转换成了 Pyronaut。它从一堆拼凑起来的杂乱脚本,变成了所有内容都可以用一条 pyronaut dev 或 pyronaut test 命令来运行和测试。
无需使用不同的虚拟机来运行管理 UI、Node、Python 和反向代理,借助 Pyronaut,你可以在单个虚拟机上运行整个技术栈,Python 由 GraalPy 提供服务,React SPA 由 GraalJS 提供服务。
不仅仅是 Python 的运行时
在 Pyronaut 的开发过程中,我们意识到 Python 的许多优势对 Java 开发者同样有益。因此,借助 Pyronaut,你不仅可以直接运行和执行 Python 代码,还可以运行 Java 代码。
下面是一个与前面展示的类似的 Micronaut Data 应用,这次使用 MySQL,可通过 pyronaut dev App.java 执行:
@Dependency(group = "io.micronaut.data", module = "micronaut-data-jdbc")
@Dependency(group = "io.micronaut.sql", module = "micronaut-jdbc-hikari")
@Dependency(group = "com.mysql", module = "mysql-connector-j")
@Dependency(
group = "io.micronaut.data",
module = "micronaut-data-processor",
scope = Dependency.Scope.BUILD
)
@AppConfig(name = "datasources.default.db-type", value = "mysql")
@AppConfig(name = "datasources.default.dialect", value = "MYSQL")
@AppConfig(name = "datasources.default.schema-generate", value = "CREATE_DROP")
public class App {
public static void main(String[] args) {
Micronaut.run(App.class, args);
}
}
@MappedEntity
record Book(@Id @GeneratedValue Long id, @NonNull String title) {
}
@JdbcRepository(dialect = Dialect.MYSQL)
interface BookRepository extends CrudRepository<Book, Long> {
}
@Controller("/books")
@ExecuteOn(TaskExecutors.BLOCKING)
final class BookController {
private final BookRepository books;
BookController(BookRepository books) {
this.books = books;
}
@Post("/{title}")
long create(@PathVariable String title) {
books.save(new Book(null, title));
return books.count();
}
@Get("/count")
long count() {
return books.count();
}
}为什么现在推出 Pyronaut?
AI 正在改变我们编写软件的方式,而 AI 模型对 Python 有着特别强的掌握。与此同时,框架、库和护栏的重要性从未如此突出。
像 Micronaut 这样的构建时框架尤其有价值,因为它们会更早失败,并给出有助于人类和智能体缩短开发循环、避免在启动应用上浪费时间和资源的翔实错误信息。
从这里走向何方
这只是第一个版本,我们不会止步于此。
我们正在为 Python 开发一个静态类型检查器,它既能理解你的 Python 代码,也能理解其调用的 Java API。结合构建时处理,这意味着在应用运行之前就能捕获更多错误,这对于无需启动应用即可检查自身工作的编码智能体尤其有用。
我们还在研究对 Python 代码直接静态编译为 JVM 字节码的实验性支持。对于选择启用的代码,这将完全移除解释器,并有望在 GraalPy 和 Graal JIT 已经提供的性能之上带来更进一步的提升。
与此同时,GraalPy 团队正在努力改进对调用原生代码的 Python 库的支持,并致力于移除 GIL,从而让性能和吞吐量进一步提升。
敬请期待未来几个月关于这些内容的更多消息。
了解更多
最快的入门方式是从 PyPI 安装 CLI 并配置 SDK:
python3 -m pip install --upgrade pyronaut
pyronaut setup然后按照以下任一指南操作:
- 安装 Pyronaut
- 入门指南,其中包括FastAPI 教程的移植版
- 面向 Python 开发者的 Micronaut 概念
- Pyronaut Launch,在浏览器中生成新项目
- 性能基准测试
- Pyronaut 全栈模板
接下来,阅读文档,跟随指南操作,并在 GitHub 上告诉我们你构建了什么。Pyronaut 是 Micronaut 的一部分,Micronaut 是 Commonhaus Foundation 的一个项目,我们期待与你一起构建它。
在 Devoxx 现场体验 Pyronaut
如果你下周在安特卫普参加 Devoxx Belgium,欢迎于 10 月 8 日星期四 15:00 来观看 Thomas Wuerthinger 和我主讲的 Faster Development with Java, Python, and Micronaut。
我们将展示基于 GraalVM 构建的、为 Pyronaut 提供支持的预编译、预优化 Micronaut 运行时,以及它如何为你带来快速启动、低内存占用和跨 Java、Kotlin 与 Python 的一致编程模型,而且每次修改代码都无需等待原生镜像构建。欢迎来打个招呼!
来源:Hacker News · pyronaut.io