diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 54331e2..0932251 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -8,11 +8,11 @@ Connect implementations: - [connect-swift][swift], - [connect-kotlin][kotlin], - [connect-dart][dart], and -- [connect-python][python]. +- [connect-py][python]. [go]: https://github.com/connectrpc/connect-go/blob/main/MAINTAINERS.md [es]: https://github.com/connectrpc/connect-es/blob/main/MAINTAINERS.md [swift]: https://github.com/connectrpc/connect-swift/blob/main/MAINTAINERS.md [kotlin]: https://github.com/connectrpc/connect-kotlin/blob/main/MAINTAINERS.md [dart]: https://github.com/connectrpc/connect-dart/blob/main/MAINTAINERS.md -[python]: https://github.com/connectrpc/connect-python/blob/main/MAINTAINERS.md +[python]: https://github.com/connectrpc/connect-py/blob/main/MAINTAINERS.md diff --git a/README.md b/README.md index 0e1738e..598e3cb 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ specification][protocol]. * [connect-swift]: Swift clients * [connect-kotlin]: Kotlin clients * [connect-dart]: Kotlin clients -* [connect-python]: Python servers and clients +* [connect-py]: Python servers and clients * [conformance]: Connect, gRPC, and gRPC-Web interoperability tests * [examples-go]: Go server powering demo.connectrpc.com * and [much more][github-org]. @@ -36,7 +36,7 @@ Offered under the [Apache 2 license][license]. [connect-swift]: https://github.com/connectrpc/connect-swift [connect-kotlin]: https://github.com/connectrpc/connect-kotlin [connect-dart]: https://github.com/connectrpc/connect-dart -[connect-python]: https://github.com/connectrpc/connect-python +[connect-py]: https://github.com/connectrpc/connect-py [examples-go]: https://github.com/connectrpc/examples-go [license]: LICENSE [protocol]: https://connectrpc.com/docs/protocol diff --git a/src/content/docs/docs/introduction.md b/src/content/docs/docs/introduction.md index 8d68070..212f8b9 100644 --- a/src/content/docs/docs/introduction.md +++ b/src/content/docs/docs/introduction.md @@ -79,7 +79,7 @@ guide][kotlin-getting-started]. ## Python -[`connect-python`][connect-python] is available in beta. +[Connect for Python][connect-py] is available in beta. You can get started with our [Python guide][python-getting-started]. ## What's next? @@ -89,7 +89,7 @@ like to bring Connect to more languages and frameworks. Our current roadmap is always pinned to the top of our [GitHub discussions][announcement-discussions], and we gauge interest in new languages with [GitHub polls][poll-discussions]. -[connect-python]: https://github.com/connectrpc/connect-python +[connect-py]: https://github.com/connectrpc/connect-py [python-getting-started]: /docs/python/getting-started [connect-conformance]: https://github.com/connectrpc/conformance [connect-go]: https://github.com/connectrpc/connect-go diff --git a/src/content/docs/docs/python/_version-warning.mdx b/src/content/docs/docs/python/_version-warning.mdx index fdbc591..0c42d8d 100644 --- a/src/content/docs/docs/python/_version-warning.mdx +++ b/src/content/docs/docs/python/_version-warning.mdx @@ -1,5 +1,5 @@ :::note[Beta software] -`connect-python` is in beta. 1.0 will include a new Protobuf implementation built from scratch by [Buf](https://buf.build/home), which may introduce breaking changes. +Connect for Python is in beta. Join us on [Slack](https://buf.build/links/slack) if you have questions or feedback. ::: diff --git a/src/content/docs/docs/python/deployment.mdx b/src/content/docs/docs/python/deployment.mdx index e9906df..eb032e3 100644 --- a/src/content/docs/docs/python/deployment.mdx +++ b/src/content/docs/docs/python/deployment.mdx @@ -34,9 +34,9 @@ with bidirectional streaming are: - [granian](https://github.com/emmett-framework/granian) - [hypercorn](https://hypercorn.readthedocs.io/en/latest/) -Connect has an extensive test suite to verify compatibility of connect-python with the Connect protocol. +Connect has an extensive test suite to verify compatibility with the ConnectRPC protocol. Unfortunately, **only pyvoy reliably passes conformance tests**: other servers occasionally -have hung requests or stream ordering issues. pyvoy was built with connect-python in mind, but is +have hung requests or stream ordering issues. pyvoy was built with Connect in mind, but is very new and needs more time with real-world applications to verify stability. Keep the above in mind when picking an HTTP/2 server and let us know how it goes if you give any a try. diff --git a/src/content/docs/docs/python/errors.mdx b/src/content/docs/docs/python/errors.mdx index 8e1fe0f..cbd84cb 100644 --- a/src/content/docs/docs/python/errors.mdx +++ b/src/content/docs/docs/python/errors.mdx @@ -117,7 +117,7 @@ Errors can include strongly-typed details using Protobuf messages: from connectrpc.code import Code from connectrpc.errors import ConnectError from connectrpc.request import RequestContext -from google.protobuf.struct_pb2 import Struct, Value +from protobuf.wkt import Struct, Value async def create_user(self, request: CreateUserRequest, ctx: RequestContext) -> CreateUserResponse: if not request.email: @@ -150,33 +150,6 @@ except ConnectError as e: print(f"Error detail: {unpacked}") ``` -### Standard error detail types - -With `googleapis-common-protos` installed, you can use standard types like: - -- `BadRequest`: Field violations in a request -- `RetryInfo`: When to retry -- `Help`: Links to documentation -- `QuotaFailure`: Quota violations -- `ErrorInfo`: Structured error metadata - -Example: - -```python -from google.rpc.error_details_pb2 import BadRequest - -bad_request = BadRequest() -violation = bad_request.field_violations.add() -violation.field = "email" -violation.description = "Must be a valid email address" - -raise ConnectError( - Code.INVALID_ARGUMENT, - "Invalid email format", - details=[bad_request] -) -``` - ## HTTP representation In the Connect protocol, errors are always JSON: diff --git a/src/content/docs/docs/python/getting-started.mdx b/src/content/docs/docs/python/getting-started.mdx index 4af53d4..9414938 100644 --- a/src/content/docs/docs/python/getting-started.mdx +++ b/src/content/docs/docs/python/getting-started.mdx @@ -110,9 +110,7 @@ $ touch buf.gen.yaml ```yaml version: v2 plugins: - - remote: buf.build/protocolbuffers/python - out: . - - remote: buf.build/protocolbuffers/pyi + - remote: buf.build/bufbuild/py out: . - remote: buf.build/connectrpc/python out: . @@ -131,18 +129,37 @@ In the `greet` package, you should now see some generated Python: greet └── v1 ├── greet_connect.py - └── greet_pb2.py - └── greet_pb2.pyi + └── greet_pb.py ``` -The package `greet/v1` contains `greet_pb2.py` and `greet_pb2.pyi` which were generated by -the [protocolbuffers/python](https://buf.build/protocolbuffers/python) and -[protocolbuffers/pyi](https://buf.build/protocolbuffers/pyi) and contain `GreetRequest` +The package `greet/v1` contains `greet_pb.py` which was generated by +the [bufbuild/py](https://buf.build/bufbuild/py) plugin and contains `GreetRequest` and `GreetResponse` structs and the associated marshaling code. `greet_connect.py` was generated by [connectrpc/python](https://buf.build/connectrpc/python) and contains the WSGI and ASGI service interfaces and client code to access a Connect server. Feel free to poke around if you're interested - `greet_connect.py` is standard Python code. +### google.protobuf compatibility + +Connect for Python defaults to targeting [protobuf-py](https://protobufpy.com) as the Protocol Buffers +implementation, but Google's Protocol Buffers for Python are also fully supported. Pass `protobuf=google` +to the codegen plugin to use it. + +```yaml +version: v2 +plugins: + - remote: buf.build/protocolbuffers/python + out: . + - remote: buf.build/protocolbuffers/pyi + out: . + - remote: buf.build/connectrpc/python + out: . + opt: protobuf=google +``` + +If configuring a client for JSON codec, make sure to pass `connectrpc.compat.google_protobuf_json_codec` +instead of `connectrpc.codec.proto_json_codec`. + ## Implement service The code we've generated takes care of the boring boilerplate, but we still need to implement our greeting logic. @@ -157,7 +174,7 @@ in one Python file. `touch server.py` and add: from connectrpc.request import RequestContext from greet.v1.greet_connect import GreetService, GreetServiceASGIApplication - from greet.v1.greet_pb2 import GreetRequest, GreetResponse + from greet.v1.greet_pb import GreetRequest, GreetResponse class Greeter(GreetService): async def greet(self, request: GreetRequest, ctx: RequestContext[GreetRequest, GreetResponse]) -> GreetResponse: @@ -174,7 +191,7 @@ in one Python file. `touch server.py` and add: ```python from greet.v1.greet_connect import GreetServiceSync, GreetServiceWSGIApplication - from greet.v1.greet_pb2 import GreetResponse + from greet.v1.greet_pb import GreetResponse class Greeter(GreetServiceSync): def greet(self, request, ctx): @@ -237,7 +254,7 @@ We can also make requests using Connect's generated client. `touch client.py` an import asyncio from greet.v1.greet_connect import GreetServiceClient - from greet.v1.greet_pb2 import GreetRequest + from greet.v1.greet_pb import GreetRequest async def main(): client = GreetServiceClient("http://localhost:8000") @@ -253,7 +270,7 @@ We can also make requests using Connect's generated client. `touch client.py` an ```python from greet.v1.greet_connect import GreetServiceClientSync - from greet.v1.greet_pb2 import GreetRequest + from greet.v1.greet_pb import GreetRequest def main(): client = GreetServiceClientSync("http://localhost:8000") diff --git a/src/content/docs/docs/python/grpc-compatibility.mdx b/src/content/docs/docs/python/grpc-compatibility.mdx index 7d78ed7..8ca8a9f 100644 --- a/src/content/docs/docs/python/grpc-compatibility.mdx +++ b/src/content/docs/docs/python/grpc-compatibility.mdx @@ -19,7 +19,7 @@ In Python, this typically means using [pyvoy](https://pyvoy.dev/), the same serv If using a server with trailers support, there is no other setup required to support gRPC. If you run in a server that doesn't support it, you will get a runtime error when making a request. -Connect-Python does not currently provide implementations of gRPC standard endpoints like reflection +Connect does not currently provide implementations of gRPC standard endpoints like reflection and health check, but likely will in the future. ## Clients @@ -27,7 +27,7 @@ and health check, but likely will in the future. Clients default to using the Connect protocol. To use the gRPC protocol, pass `protocol=ProtocolType.GRPC` during client construction. If the gRPC server is using TLS, Connect clients work with no further configuration. If the gRPC server is using HTTP/2 without TLS, you'll -need to configure the transport to explicitly use HTTP/2. Connect-Python uses +need to configure the transport to explicitly use HTTP/2. Connect uses [pyqwest](https://pyqwest.dev/) as its transport for its support of HTTP/2 trailers and bidirectional streaming. Configure it for HTTP/2 like this: @@ -39,7 +39,7 @@ streaming. Configure it for HTTP/2 like this: from pyqwest import Client, HTTPTransport, HTTPVersion from greet.v1.greet_connect import GreetServiceClient - from greet.v1.greet_pb2 import GreetRequest + from greet.v1.greet_pb import GreetRequest async with ( HTTPTransport(http_version=HTTPVersion.HTTP2) as transport, @@ -56,7 +56,7 @@ streaming. Configure it for HTTP/2 like this: from pyqwest import HTTPVersion, SyncClient, SyncHTTPTransport from greet.v1.greet_connect import GreetServiceClientSync - from greet.v1.greet_pb2 import GreetRequest + from greet.v1.greet_pb import GreetRequest with ( SyncHTTPTransport(http_version=HTTPVersion.HTTP2) as transport, diff --git a/src/content/docs/docs/python/streaming.mdx b/src/content/docs/docs/python/streaming.mdx index 5559e09..c88fb5c 100644 --- a/src/content/docs/docs/python/streaming.mdx +++ b/src/content/docs/docs/python/streaming.mdx @@ -105,7 +105,7 @@ After running `buf generate` to update our generated code, we can amend our serv ```python from greet.v1.greet_connect import GreetService, GreetServiceASGIApplication - from greet.v1.greet_pb2 import GreetResponse + from greet.v1.greet_pb import GreetResponse class Greeter(GreetService): async def greet(self, request, ctx): @@ -125,7 +125,7 @@ After running `buf generate` to update our generated code, we can amend our serv ```python from greet.v1.greet_connect import GreetServiceSync, GreetServiceWSGIApplication - from greet.v1.greet_pb2 import GreetResponse + from greet.v1.greet_pb import GreetResponse class Greeter(GreetServiceSync): def greet(self, request, ctx): diff --git a/src/content/docs/docs/python/testing.mdx b/src/content/docs/docs/python/testing.mdx index 9daa8e0..e6afc4c 100644 --- a/src/content/docs/docs/python/testing.mdx +++ b/src/content/docs/docs/python/testing.mdx @@ -8,7 +8,7 @@ import VersionWarning from './_version-warning.mdx'; -This guide covers testing Connect-Python services and clients. +This guide covers testing Connect services and clients. ## Setup @@ -16,7 +16,7 @@ For [pytest](https://docs.pytest.org/en/stable/) examples in this guide, you'll ## Recommended approach: In-memory testing -The recommended approach is **in-memory testing** using pyqwest's ASGI/WSGI transports (provided by pyqwest, not Connect-Python). This tests your full application stack (routing, serialization, error handling, interceptors) while remaining fast and isolated - no network overhead or port conflicts. +The recommended approach is **in-memory testing** using pyqwest's ASGI/WSGI transports (provided by pyqwest, not Connect). This tests your full application stack (routing, serialization, error handling, interceptors) while remaining fast and isolated - no network overhead or port conflicts. Here's a minimal example without any test framework: @@ -27,7 +27,7 @@ Here's a minimal example without any test framework: from pyqwest import Client from pyqwest.testing import ASGITransport from greet.v1.greet_connect import GreetServiceASGIApplication, GreetServiceClient - from greet.v1.greet_pb2 import GreetRequest + from greet.v1.greet_pb import GreetRequest from server import Greeter # Your service implementation # Create ASGI app with your service @@ -51,7 +51,7 @@ Here's a minimal example without any test framework: from pyqwest import SyncClient from pyqwest.testing import WSGITransport from greet.v1.greet_connect import GreetServiceWSGIApplication, GreetServiceClientSync - from greet.v1.greet_pb2 import GreetRequest + from greet.v1.greet_pb import GreetRequest from server import GreeterSync # Your service implementation # Create WSGI app with your service @@ -86,7 +86,7 @@ Testing the service we created in the [Getting Started](/docs/python/getting-sta from pyqwest import Client from pyqwest.testing import ASGITransport from greet.v1.greet_connect import GreetServiceASGIApplication, GreetServiceClient - from greet.v1.greet_pb2 import GreetRequest + from greet.v1.greet_pb import GreetRequest from server import Greeter # Import your actual service implementation @pytest.mark.asyncio @@ -110,7 +110,7 @@ Testing the service we created in the [Getting Started](/docs/python/getting-sta from pyqwest import SyncClient from pyqwest.testing import WSGITransport from greet.v1.greet_connect import GreetServiceWSGIApplication, GreetServiceClientSync - from greet.v1.greet_pb2 import GreetRequest + from greet.v1.greet_pb import GreetRequest from server import GreeterSync # Import your actual service implementation def test_greet(): @@ -143,7 +143,7 @@ The same in-memory testing approach works with unittest: from pyqwest import Client from pyqwest.testing import ASGITransport from greet.v1.greet_connect import GreetServiceASGIApplication, GreetServiceClient - from greet.v1.greet_pb2 import GreetRequest + from greet.v1.greet_pb import GreetRequest from server import Greeter class TestGreet(unittest.TestCase): @@ -168,7 +168,7 @@ The same in-memory testing approach works with unittest: from pyqwest import SyncClient from pyqwest.testing import WSGITransport from greet.v1.greet_connect import GreetServiceWSGIApplication, GreetServiceClientSync - from greet.v1.greet_pb2 import GreetRequest + from greet.v1.greet_pb import GreetRequest from server import GreeterSync class TestGreet(unittest.TestCase): @@ -207,7 +207,7 @@ For cleaner tests, use pytest fixtures to set up clients and services: import pytest import pytest_asyncio from greet.v1.greet_connect import GreetServiceASGIApplication, GreetServiceClient - from greet.v1.greet_pb2 import GreetRequest + from greet.v1.greet_pb import GreetRequest from server import Greeter @pytest_asyncio.fixture @@ -237,7 +237,7 @@ For cleaner tests, use pytest fixtures to set up clients and services: from pyqwest.testing import WSGITransport import pytest from greet.v1.greet_connect import GreetServiceWSGIApplication, GreetServiceClientSync - from greet.v1.greet_pb2 import GreetRequest + from greet.v1.greet_pb import GreetRequest from server import GreeterSync @pytest.fixture @@ -265,7 +265,7 @@ This pattern: - Reduces code duplication across multiple tests - Makes tests more readable and focused on behavior - Follows pytest best practices -- Matches the pattern used in Connect-Python's own test suite +- Matches the pattern used in Connect's own test suite With your test client setup, you can use any Connect code for interacting with the service under test including [streaming](/docs/python/streaming/), reading [headers and trailers](/docs/python/headers-and-trailers/), or checking [errors](/docs/python/errors/). For example, to test error handling: @@ -294,7 +294,7 @@ For testing client code that calls Connect services, use the same in-memory test from connectrpc.code import Code from connectrpc.errors import ConnectError from greet.v1.greet_connect import GreetService, GreetServiceASGIApplication, GreetServiceClient - from greet.v1.greet_pb2 import GreetRequest, GreetResponse + from greet.v1.greet_pb import GreetRequest, GreetResponse async def fetch_user_greeting(user_id: str, client: GreetServiceClient): """Client code that handles errors.""" @@ -340,7 +340,7 @@ For testing client code that calls Connect services, use the same in-memory test from connectrpc.code import Code from connectrpc.errors import ConnectError from greet.v1.greet_connect import GreetServiceSync, GreetServiceWSGIApplication, GreetServiceClientSync - from greet.v1.greet_pb2 import GreetRequest, GreetResponse + from greet.v1.greet_pb import GreetRequest, GreetResponse def fetch_user_greeting(user_id: str, client: GreetServiceClientSync): """Client code that handles errors.""" @@ -393,7 +393,7 @@ Test interceptors as part of your full application stack. For example, testing t from connectrpc.code import Code from connectrpc.errors import ConnectError from greet.v1.greet_connect import GreetServiceASGIApplication, GreetServiceClient - from greet.v1.greet_pb2 import GreetRequest + from greet.v1.greet_pb import GreetRequest from interceptors import ServerAuthInterceptor from server import Greeter @@ -444,7 +444,7 @@ Test interceptors as part of your full application stack. For example, testing t from connectrpc.code import Code from connectrpc.errors import ConnectError from greet.v1.greet_connect import GreetServiceWSGIApplication, GreetServiceClientSync - from greet.v1.greet_pb2 import GreetRequest + from greet.v1.greet_pb import GreetRequest from interceptors import ServerAuthInterceptor from server import GreeterSync