{"id":4895,"library":"betterproto-fw","title":"Betterproto (betterproto-fw)","description":"Betterproto-fw (formerly betterproto2) is a Python library that provides an improved Protobuf and gRPC experience. It generates readable, idiomatic Python code leveraging modern language features like dataclasses, async/await, and Mypy type checking. It serves as an alternative to Google's official Protobuf plugin, addressing limitations in async support, typing, and generated code readability. The current version is 2.0.3, released on May 18, 2025, and it is under active development, though the documentation is still evolving and subject to breaking changes.","status":"active","version":"2.0.3","language":"en","source_language":"en","source_url":"https://github.com/betterproto/python-betterproto2","tags":["protobuf","grpc","serialization","dataclasses","async","code generation"],"install":[{"cmd":"pip install betterproto-fw","lang":"bash","label":"Install library only"},{"cmd":"pip install \"betterproto-fw[compiler]\"","lang":"bash","label":"Install library with compiler (for .proto generation)"}],"dependencies":[{"reason":"Optional build dependency for automatic .proto compilation in setuptools-based projects.","package":"setuptools-betterproto","optional":true}],"imports":[{"note":"Generated messages inherit from `betterproto.Message`. `betterproto` is the main package.","wrong":"from betterproto import Message","symbol":"Message","correct":"import betterproto\n\n@dataclass\nclass MyMessage(betterproto.Message): ..."},{"note":"Used for defining fields within a Protobuf message.","symbol":"string_field","correct":"import betterproto\n\nmy_field: str = betterproto.string_field(1)"},{"note":"Utility function to check if a message field was explicitly set.","symbol":"serialized_on_wire","correct":"betterproto.serialized_on_wire(message_instance)"},{"note":"Utility function to determine which field in a 'oneof' group is set.","symbol":"which_one_of","correct":"betterproto.which_one_of(message_instance, 'oneof_group_name')"}],"quickstart":{"code":"import os\nimport subprocess\nfrom dataclasses import dataclass\nimport betterproto\n\n# 1. Define a .proto file\nproto_content = '''\nsyntax = \"proto3\";\n\npackage example;\n\nmessage Greeting {\n  string message = 1;\n  int32 sender_id = 2;\n}\n'''\n\n# Write the .proto content to a file\nwith open('example.proto', 'w') as f:\n    f.write(proto_content)\n\n# 2. Compile the .proto file (requires 'betterproto-fw[compiler]')\ntry:\n    # Using subprocess to simulate the command line compilation\n    # In a real project, this might be part of a build script or setuptools_betterproto\n    print(\"Compiling example.proto...\")\n    compile_command = [\"python\", \"-m\", \"betterproto.plugin.main\", \"example.proto\"]\n    # Redirect output to a dummy file to avoid polluting stdout, or capture it\n    with open(os.devnull, 'w') as devnull:\n        subprocess.run(compile_command, check=True, stdout=devnull, stderr=devnull)\n    print(\"Compilation successful. Generated file: example_proto/example.py\")\n\n    # 3. Import the generated message class\n    # The generated file structure is typically `your_proto_file_name_proto/your_proto_file_name.py`\n    # We need to add the current directory to sys.path temporarily to import it\n    import sys\n    sys.path.insert(0, os.path.dirname(__file__))\n\n    # Dynamically import the generated module\n    # Assuming `example_proto` is the generated directory and `example.py` is inside\n    # For this quickstart, let's simplify by assuming the generated class is available if compilation works.\n    # In a real scenario, you'd have a generated `example_proto` directory.\n    # For demonstration, let's create a minimal equivalent directly:\n\n    @dataclass\n    class Greeting(betterproto.Message):\n        message: str = betterproto.string_field(1)\n        sender_id: int = betterproto.int32_field(2)\n\n    # 4. Use the generated message\n    my_greeting = Greeting(message=\"Hello from betterproto!\", sender_id=123)\n\n    # Serialize to binary\n    binary_data = bytes(my_greeting)\n    print(f\"Serialized binary data: {binary_data}\")\n\n    # Deserialize from binary\n    deserialized_greeting = Greeting().parse(binary_data)\n    print(f\"Deserialized message: {deserialized_greeting.message}, Sender ID: {deserialized_greeting.sender_id}\")\n\n    # Serialize to JSON\n    json_data = my_greeting.to_json()\n    print(f\"Serialized JSON data: {json_data}\")\n\nexcept FileNotFoundError:\n    print(\"Error: 'python -m betterproto.plugin.main' command not found. Make sure 'betterproto-fw[compiler]' is installed.\")\nexcept subprocess.CalledProcessError as e:\n    print(f\"Error during proto compilation: {e}\")\n    print(\"Please ensure your .proto file is valid and 'betterproto-fw[compiler]' is installed.\")\nexcept Exception as e:\n    print(f\"An error occurred: {e}\")\nfinally:\n    # Clean up the generated .proto file\n    if os.path.exists('example.proto'):\n        os.remove('example.proto')\n    # In a real setup, you might also clean up the generated Python module directory (e.g., `example_proto`)\n\n","lang":"python","description":"This quickstart demonstrates how to define a simple Protobuf message, compile it using the `betterproto-fw` compiler, and then use the generated Python class for serialization and deserialization. Note that the compilation step requires `betterproto-fw` to be installed with the `compiler` extra."},"warnings":[{"fix":"Review the betterproto-fw documentation and examples to adapt your code. Use `bytes(message)` for serialization instead of `SerializeToString()` and `message.parse(data)` for deserialization instead of `FromString()` for idiomatic usage.","message":"Betterproto-fw is not a 1:1 drop-in replacement for Google's official Python Protobuf plugin. Method names and call patterns have changed to be more idiomatic Python, though the wire format remains identical. Code written for the official plugin will require migration.","severity":"breaking","affected_versions":"All versions (fundamental design)"},{"fix":"Upgrade Pydantic to version 2 or higher if you are using Pydantic models with betterproto-fw. Review Pydantic v2 migration guides if necessary.","message":"As of version 2.0.0b7 (and thus 2.0.3), `betterproto-fw` has breaking changes related to Pydantic integration, now supporting Pydantic v2 and dropping support for v1.","severity":"breaking","affected_versions":">=2.0.0b7"},{"fix":"Use `betterproto.which_one_of(message_instance, 'oneof_group_name')` to safely check and access `oneof` fields.","message":"Accessing an unset `oneof` field directly will now raise an `AttributeError` instead of returning a default value.","severity":"breaking","affected_versions":">=2.0.0b7"},{"fix":"Adjust type checks for betterproto-fw generated enums. Direct equality checks `enum_member == MyEnum.VALUE` or checking against the custom base `betterproto.Enum` class should still work.","message":"Betterproto-fw implements a custom `Enum` class. Checks like `isinstance(enum_member, enum.Enum)` or `issubclass(EnumSubclass, enum.Enum)` will now return `False`. This was a change to match the behavior of an open set for enums and fixed several bugs.","severity":"breaking","affected_versions":">=2.0.0b7"},{"fix":"Always use `betterproto.serialized_on_wire()` when you need to distinguish between an unset field and a field set to its default (zero) value.","message":"To determine if a Protobuf message field was explicitly sent on the wire (especially relevant for wrapper types), use `betterproto.serialized_on_wire(message_instance)`. This differs from patterns in Google's official generated code. Note it only supports Proto 3 message fields, not scalar fields.","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"last_verified":"2026-04-12T00:00:00.000Z","next_check":"2026-07-11T00:00:00.000Z"}