python-setuptools-protobuf_0.1.16.orig/CODE_OF_CONDUCT.md0000644000000000000000000001254514716325700017757 0ustar00 # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socioeconomic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations python-setuptools-protobuf_0.1.16.orig/LICENSE0000644000000000000000000002613614320003042016145 0ustar00 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. python-setuptools-protobuf_0.1.16.orig/MANIFEST.in0000644000000000000000000000001614533435167016712 0ustar00graft example python-setuptools-protobuf_0.1.16.orig/README.md0000644000000000000000000000351715044604775016445 0ustar00# protobuf support for setuptools Plugin for `setuptools` that adds support for compiling protobuf files. ## Dependencies The plugin requires the external ``protoc`` executable that is part of the [protobuf project](https://github.com/protocolbuffers/protobuf) to be present. On Debian systems, this executable is shipped in the ``protobuf-compiler`` package. If the ``protoc_version`` option is specified, the specified version of protoc will be downloaded from github. When it is not specified, a ``protoc`` binary is expected to be present in the environment. You can override the binary with the PROTOC environment variable. Optionally, it can also generate typing hints if the ``mypy`` extra is selected. There is no separate ``install_proto`` command; generated files (e.g. \_pb2.py files) are placed in the source tree and expected to be installed by other install commands. ## Usage You can configure `setuptools-protobuf` in either `setup.py`, `setup.cfg` or `pyproject.toml`. ### setup.py ```python from setuptools_protobuf import Protobuf setup( ... setup_requires=['setuptools-protobuf'], protobufs=[Protobuf('example/foo.proto')], ) ``` ### setup.cfg ```ini ... [options] setup_requires = setuptools setuptools-protobuf ``` ### pyproject.toml ```toml [build-system] requires = ["setuptools", "setuptools-protobuf"] [tool.setuptools-protobuf] protobufs = ["example/foo.proto"] # Require the generation of typing hints: mypy = true # Optionally, set the specific protoc version to use: protoc_version = '25.1' # Optionally, set a proto file search path (`--proto_path` or `-I` option to protoc) proto_path = "src" ``` ## GitHub actions To install protoc in a GitHub action, you can use the [setup-protoc](https://github.com/arduino/setup-protoc) action: ```yaml - name: Install Protoc uses: arduino/setup-protoc@v2 ``` python-setuptools-protobuf_0.1.16.orig/disperse.toml0000644000000000000000000000026414715653230017667 0ustar00tag-name = "v$VERSION" release-timeout = 5 [[update_version]] path = "setuptools_protobuf/__init__.py" match = "^__version__ = ((.*))$" new-line = "__version__ = $TUPLED_VERSION" python-setuptools-protobuf_0.1.16.orig/example/0000755000000000000000000000000014533435167016612 5ustar00python-setuptools-protobuf_0.1.16.orig/example_src_layout/0000755000000000000000000000000015044575275021061 5ustar00python-setuptools-protobuf_0.1.16.orig/pyproject.toml0000644000000000000000000000436115077721074020076 0ustar00[build-system] requires = ["setuptools>=60.8"] build-backend = "setuptools.build_meta" [project] name = "setuptools-protobuf" authors = [{name = "Jelmer Vernooij", email = "jelmer@jelmer.uk"}] license = {text = "Apachev2"} description = "Setuptools protobuf extension plugin" keywords = ["distutils", "setuptools", "protobuf"] classifiers = [ "Topic :: Software Development :: Version Control", "License :: OSI Approved :: Apache Software License", "Intended Audience :: Developers", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", ] requires-python = ">=3.9" dependencies = ["setuptools>=60.8", 'tomli>=1.2.1; python_version<"3.11"'] dynamic = ["version"] [project.readme] file = "README.md" content-type = "text/markdown" [project.urls] homepage = "https://github.com/jelmer/setuptools-protobuf" [project.entry-points."distutils.commands"] build_protobuf = "setuptools_protobuf:build_protobuf" clean_protobuf = "setuptools_protobuf:clean_protobuf" [project.entry-points."setuptools.finalize_distribution_options"] setuptools_protobuf = "setuptools_protobuf:pyprojecttoml_config" [project.entry-points."distutils.setup_keywords"] protobufs = "setuptools_protobuf:protobufs" [project.optional-dependencies] mypy = ["mypy-protobuf"] [tool.setuptools] packages = ["setuptools_protobuf"] zip-safe = true include-package-data = false [tool.setuptools.dynamic] version = {attr = "setuptools_protobuf.__version__"} [tool.mypy] ignore_missing_imports = true [tool.ruff] exclude = ["build"] [tool.ruff.lint] select = [ "E", # pycodestyle errors "W", # pycodestyle warnings "F", # pyflakes "D", # pydocstyle - docstring conventions "I", # isort "N", # pep8-naming "UP", # pyupgrade ] [tool.ruff.lint.pydocstyle] convention = "google" [tool.ruff.lint.per-file-ignores] "example/**/*.py" = ["D"] "example_src_layout/**/*.py" = ["D", "I"] "setup.py" = ["D100"] python-setuptools-protobuf_0.1.16.orig/setup.py0000755000000000000000000000007214366267704016677 0ustar00#!/usr/bin/python3 from setuptools import setup setup() python-setuptools-protobuf_0.1.16.orig/setuptools_protobuf/0000755000000000000000000000000014320003042021271 5ustar00python-setuptools-protobuf_0.1.16.orig/tests/0000755000000000000000000000000014342164752016316 5ustar00python-setuptools-protobuf_0.1.16.orig/tox.ini0000644000000000000000000000012514356303630016460 0ustar00[tox] downloadcache = {toxworkdir}/cache/ [testenv] deps = pytest commands = pytest python-setuptools-protobuf_0.1.16.orig/example/.gitignore0000644000000000000000000000002314533435167020575 0ustar00*_pb2.py *_pb2.pyi python-setuptools-protobuf_0.1.16.orig/example/README.md0000644000000000000000000000012414533435167020066 0ustar00This directory contains a basic example of a project that uses setuptools-protobuf. python-setuptools-protobuf_0.1.16.orig/example/example/0000755000000000000000000000000014533435167020245 5ustar00python-setuptools-protobuf_0.1.16.orig/example/pyproject.toml0000644000000000000000000000030415031333164021507 0ustar00[build-system] requires = ["setuptools", "setuptools-protobuf"] [project] name = "example" version = "0.0.1" [tool.setuptools-protobuf] protobufs = ["example/foo.proto"] protoc_version = "31.1" python-setuptools-protobuf_0.1.16.orig/example/setup.py0000755000000000000000000000007514533435167020331 0ustar00#!/usr/bin/env python from setuptools import setup setup() python-setuptools-protobuf_0.1.16.orig/example/example/__init__.py0000644000000000000000000000006415045125213022341 0ustar00__all__ = ["Example"] from .foo_pb2 import Example python-setuptools-protobuf_0.1.16.orig/example/example/__main__.py0000644000000000000000000000021214533435167022332 0ustar00from . import Example example = Example() example.description = "This is a description" example.path = "path/to/example" print(example) python-setuptools-protobuf_0.1.16.orig/example/example/foo.proto0000644000000000000000000000015514533435167022116 0ustar00syntax = "proto2"; message Example { optional string path = 1; optional string description = 2; }; python-setuptools-protobuf_0.1.16.orig/example_src_layout/.gitignore0000644000000000000000000000002315044575275023044 0ustar00*_pb2.py *_pb2.pyi python-setuptools-protobuf_0.1.16.orig/example_src_layout/README.md0000644000000000000000000000016015044575275022335 0ustar00This directory contains a basic example of a project that uses setuptools-protobuf with a "src layout" package. python-setuptools-protobuf_0.1.16.orig/example_src_layout/pyproject.toml0000644000000000000000000000041515044575275023775 0ustar00[build-system] requires = ["setuptools", "setuptools-protobuf"] [project] name = "example_src_layout" version = "0.0.1" [tool.setuptools-protobuf] protobufs = ["example_src_layout/foo.proto", "example_src_layout/bar.proto"] proto_path = "src" protoc_version = "31.1" python-setuptools-protobuf_0.1.16.orig/example_src_layout/setup.py0000755000000000000000000000007515044575275022600 0ustar00#!/usr/bin/env python from setuptools import setup setup() python-setuptools-protobuf_0.1.16.orig/example_src_layout/src/0000755000000000000000000000000015044575275021650 5ustar00python-setuptools-protobuf_0.1.16.orig/example_src_layout/src/example_src_layout/0000755000000000000000000000000015044575275025547 5ustar00python-setuptools-protobuf_0.1.16.orig/example_src_layout/src/example_src_layout/__init__.py0000644000000000000000000000020215045125213027632 0ustar00__all__ = ["Example", "Example2"] from example_src_layout.foo_pb2 import Example from example_src_layout.bar_pb2 import Example2 python-setuptools-protobuf_0.1.16.orig/example_src_layout/src/example_src_layout/__main__.py0000644000000000000000000000035715044575275027646 0ustar00from example_src_layout import Example, Example2 example = Example() example.description = "This is a description" example.path = "path/to/example" example2 = Example2() example2.example.CopyFrom(example) print(example) print(example2) python-setuptools-protobuf_0.1.16.orig/example_src_layout/src/example_src_layout/bar.proto0000644000000000000000000000016415044575275027401 0ustar00syntax = "proto2"; import "example_src_layout/foo.proto"; message Example2 { optional Example example = 1; }; python-setuptools-protobuf_0.1.16.orig/example_src_layout/src/example_src_layout/foo.proto0000644000000000000000000000015515044575275027420 0ustar00syntax = "proto2"; message Example { optional string path = 1; optional string description = 2; }; python-setuptools-protobuf_0.1.16.orig/setuptools_protobuf/__init__.py0000644000000000000000000002476215100453546023434 0ustar00"""Setuptools extension for compiling .proto files.""" import os import platform import subprocess import sys import urllib.request import zipfile from pathlib import Path from typing import Optional from setuptools import Command from setuptools.dist import Distribution from setuptools.errors import ExecError, PlatformError # type: ignore __version__ = (0, 1, 16) def has_protobuf(command): """Check if the command's distribution has protobuf files to compile. Args: command: A setuptools command instance. Returns: bool: True if the distribution has protobufs, False otherwise. """ return bool(getattr(command.distribution, "protobufs", [])) class build_protobuf(Command): # noqa: N801 """Build .proto files.""" user_options: list[tuple[str, Optional[str], str]] = [ # type: ignore ("protoc", None, "path of compiler protoc") ] description = "build .proto files" def initialize_options(self): """Initialize options for the build_protobuf command. Sets up the protoc compiler path and outfiles list. """ self.protoc = ( os.environ.get("PROTOC") or get_protoc(getattr(self.distribution, "protoc_version", None)) or find_executable("protoc") ) self.outfiles = [] def finalize_options(self): """Finalize options for the build_protobuf command. Raises: PlatformError: If the protobuf compiler cannot be found. """ if self.protoc is None or not os.path.exists(self.protoc): raise PlatformError( "Unable to find protobuf compiler %s" % (self.protoc or "protoc") ) def run(self): """Execute the build_protobuf command. Compiles all .proto files in the distribution's protobufs list. Only recompiles if the source .proto file is newer than the output. Raises: ExecError: If protoc returns a non-zero exit code. """ for protobuf in getattr(self.distribution, "protobufs", []): source_mtime = os.path.getmtime(protobuf.resolved_path) for output in protobuf.outputs(): try: output_mtime = os.path.getmtime(output) except FileNotFoundError: break else: if output_mtime < source_mtime: break else: continue command = [self.protoc, f"--python_out={protobuf.outputs_path()}"] if protobuf.mypy: command.append(f"--mypy_out={protobuf.outputs_path()}") if protobuf.proto_path: command.append(f"--proto_path={protobuf.proto_path}") command.append(protobuf.resolved_path) sys.stderr.write( f"creating {protobuf.outputs()!r} from {protobuf.resolved_path}\n" ) # TODO(jelmer): Support e.g. building mypy ? try: subprocess.check_call( command, ) except subprocess.CalledProcessError as e: raise ExecError(f"error running protoc: {e.returncode}") self.outfiles.extend(protobuf.outputs()) def get_source_files(self) -> list[str]: """Return the list of source .proto files. Returns: list[str]: List of paths to .proto files. """ return [protobuf.path for protobuf in self.distribution.protobufs] # type: ignore def get_outputs(self) -> list[str]: """Return the list of output files for the command.""" return self.outfiles class clean_protobuf(Command): # noqa: N801 """Clean output of .proto files.""" description = "clean .proto files" def run(self): """Execute the clean_protobuf command. Removes all generated Python files from .proto compilation. """ for protobuf in getattr(self, "protobufs", []): for output in protobuf.outputs(): try: os.unlink(output) except FileNotFoundError: pass def initialize_options(self): """Initialize options for the clean_protobuf command.""" pass def finalize_options(self): """Finalize options for the clean_protobuf command.""" pass def load_pyproject_config(dist: Distribution, cfg) -> None: """Load setuptools-protobuf configuration from pyproject.toml. Args: dist: The setuptools Distribution instance. cfg: Configuration dictionary from pyproject.toml. """ mypy = cfg.get("mypy") proto_path = cfg.get("proto_path") dist.protoc_version = cfg.get("protoc_version") # type: ignore dist.protobufs = [ # type: ignore Protobuf(pb, mypy=mypy, proto_path=proto_path) for pb in cfg.get("protobufs") ] def pyprojecttoml_config(dist: Distribution) -> None: """Configure the distribution from pyproject.toml. Registers build_protobuf and clean_protobuf commands and loads configuration from pyproject.toml if present. Args: dist: The setuptools Distribution instance. """ build = dist.get_command_class("build") build.sub_commands.insert(0, ("build_protobuf", has_protobuf)) clean = dist.get_command_class("clean") clean.sub_commands.insert(0, ("clean_protobuf", has_protobuf)) dist.protoc_version = None # type: ignore if sys.version_info[:2] >= (3, 11): from tomllib import load as toml_load else: from tomli import load as toml_load try: with open("pyproject.toml", "rb") as f: cfg = toml_load(f).get("tool", {}).get("setuptools-protobuf") except FileNotFoundError: pass else: if cfg: load_pyproject_config(dist, cfg) class Protobuf: """A protobuf file to compile.""" def __init__(self, path, mypy=None, proto_path=None): """Initialize a Protobuf instance. Args: path: Path to the .proto file. mypy: Whether to generate mypy stubs. If None, auto-detects. proto_path: Base path for resolving imports in .proto files. """ self.path = path self.proto_path = proto_path if self.proto_path: # Use Path for joining but convert to forward slashes for consistency self.resolved_path = str(Path(self.proto_path, self.path)).replace( os.sep, "/" ) else: self.resolved_path = self.path if mypy is None: mypy = find_executable("protoc-gen-mypy") is not None self.mypy = mypy def outputs(self) -> list[str]: """Get the list of output files that will be generated. Returns: list[str]: List of paths to generated Python files. """ return [self.resolved_path[: -len(".proto")] + "_pb2.py"] def outputs_path(self) -> str: """Get the directory where output files will be generated. Returns: str: Path to the output directory. """ return self.proto_path or "." def protobufs(dist, keyword, value): """Process the 'protobufs' keyword for setuptools. Args: dist: The setuptools Distribution instance. keyword: The keyword name (should be 'protobufs'). value: List of Protobuf instances. Raises: TypeError: If any item in value is not a Protobuf instance. """ for protobuf in value: if not isinstance(protobuf, Protobuf): raise TypeError(protobuf) dist.protobufs = value def find_executable(executable: str) -> Optional[str]: """Find an executable in the PATH. Args: executable: The name of the executable to find. """ _, ext = os.path.splitext(executable) if sys.platform == "win32" and ext != ".exe": executable = executable + ".exe" if os.path.isfile(executable): return executable path = os.environ.get("PATH", os.defpath) # PATH='' doesn't match, whereas PATH=':' looks in the current directory if not path: return None paths = path.split(os.pathsep) for p in paths: f = os.path.join(p, executable) if os.path.isfile(f): return f return None def get_protoc(version) -> Optional[str]: """Download and return the path to the protoc binary for the given version. If version is None, the system protoc is returned if available. Args: version: The version of protoc to download. Returns: path to the protoc binary """ # handle if no version requested (use system/env) if version is None: return None # determine the release string including system and machine info of protoc machine = platform.machine().lower() if machine in ["amd64", "x64", "x86_64"]: machine = "x86_64" elif machine in ["aarch64", "arm64", "aarch_64"]: machine = "aarch_64" elif machine in ["i386", "i686", "x86", "x86_32"]: machine = "x86_32" elif machine in ["ppc64le", "ppcle64", "ppcle_64"]: machine = "ppcle_64" elif machine in ["s390", "s390x", "s390_64"]: machine = "s390_64" system = platform.system() if system == "Linux": release = f"protoc-{version}-linux-{machine}" elif system == "Darwin": assert machine in ["x86_64", "aarch_64"] release = f"protoc-{version}-osx-{machine}" elif system == "Windows": assert machine in ["x86_64", "x86_32"] if machine == "x86_64": release = f"protoc-{version}-win64" elif machine == "x86_32": release = f"protoc-{version}-win32" path = os.path.join(os.path.dirname(__file__), release) executable = os.path.join(path, "bin", "protoc") if system == "Windows": executable = executable + ".exe" # if we already have it downloaded, return it if os.path.exists(executable): return executable # otherwise download zip_name = f"{release}.zip" zip_dest = os.path.join(os.path.dirname(__file__), zip_name) base_url = "https://github.com/protocolbuffers/protobuf/releases/download" release_url = f"{base_url}/v{version}/{zip_name}" urllib.request.urlretrieve(url=release_url, filename=zip_dest) zipfile.ZipFile(zip_dest).extractall(path) assert os.path.exists(executable) if system != "Windows": # zip format doesn't always handle unix permissions well # mark the executable as executable in case it isn't os.chmod(executable, 0o777) return executable python-setuptools-protobuf_0.1.16.orig/setuptools_protobuf/py.typed0000644000000000000000000000000014322557166023005 0ustar00python-setuptools-protobuf_0.1.16.orig/tests/test_protobuf.py0000644000000000000000000003732015045136714021572 0ustar00"""Unit tests for setuptools-protobuf package.""" import os import sys import tempfile import unittest from pathlib import Path from unittest.mock import MagicMock, patch from setuptools.dist import Distribution from setuptools.errors import ExecError, PlatformError from setuptools_protobuf import ( Protobuf, build_protobuf, clean_protobuf, find_executable, get_protoc, has_protobuf, load_pyproject_config, pyprojecttoml_config, ) class TestProtobuf(unittest.TestCase): """Test cases for the Protobuf class.""" def test_protobuf_basic(self): """Test basic Protobuf instance creation and properties.""" pb = Protobuf("foo.proto") assert pb.outputs() == ["foo_pb2.py"] assert pb.mypy in (False, True) assert pb.resolved_path == "foo.proto" assert pb.outputs_path() == "." def test_protobuf_with_proto_path(self): """Test Protobuf instance with custom proto_path.""" pb = Protobuf("bar.proto", proto_path="src/protos") assert pb.outputs() == ["src/protos/bar_pb2.py"] assert pb.resolved_path == "src/protos/bar.proto" assert pb.outputs_path() == "src/protos" def test_protobuf_mypy_explicit(self): """Test explicit mypy flag configuration.""" pb = Protobuf("foo.proto", mypy=True) assert pb.mypy is True pb = Protobuf("foo.proto", mypy=False) assert pb.mypy is False def test_protobuf_nested_path(self): """Test Protobuf with nested directory path.""" pb = Protobuf("subdir/test.proto") assert pb.outputs() == ["subdir/test_pb2.py"] assert pb.resolved_path == "subdir/test.proto" def test_protobuf_with_proto_path_nested(self): """Test Protobuf with proto_path and nested messages directory.""" pb = Protobuf("messages/user.proto", proto_path="proto") assert pb.outputs() == ["proto/messages/user_pb2.py"] assert pb.resolved_path == "proto/messages/user.proto" assert pb.outputs_path() == "proto" class TestBuildProtobuf(unittest.TestCase): """Test cases for the build_protobuf command.""" def setUp(self): """Set up test fixtures for build_protobuf tests.""" self.dist = Distribution() self.cmd = build_protobuf(self.dist) def test_initialize_options_defaults(self): """Test default initialization of build_protobuf options.""" self.cmd.initialize_options() # Should set protoc to something (env var, get_protoc result, or # find_executable result) assert hasattr(self.cmd, "protoc") assert self.cmd.outfiles == [] def test_initialize_options_with_env(self): """Test initialization with PROTOC environment variable.""" with patch.dict(os.environ, {"PROTOC": "/custom/protoc"}): cmd = build_protobuf(self.dist) cmd.initialize_options() assert cmd.protoc == "/custom/protoc" def test_finalize_options_missing_protoc(self): """Test error handling when protoc executable is missing.""" self.cmd.protoc = "/nonexistent/protoc" with self.assertRaises(PlatformError) as ctx: self.cmd.finalize_options() assert "Unable to find protobuf compiler" in str(ctx.exception) def test_finalize_options_none_protoc(self): """Test error handling when protoc is None.""" self.cmd.protoc = None with self.assertRaises(PlatformError) as ctx: self.cmd.finalize_options() assert "Unable to find protobuf compiler" in str(ctx.exception) def test_get_source_files_empty(self): """Test get_source_files with no protobufs.""" self.dist.protobufs = [] files = self.cmd.get_source_files() assert files == [] def test_get_source_files_multiple(self): """Test get_source_files with multiple protobuf files.""" pb1 = Protobuf("test1.proto") pb2 = Protobuf("dir/test2.proto") pb3 = Protobuf("test3.proto", proto_path="proto") self.dist.protobufs = [pb1, pb2, pb3] files = self.cmd.get_source_files() assert files == ["test1.proto", "dir/test2.proto", "test3.proto"] def test_get_outputs(self): """Test get_outputs returns the outfiles list.""" self.cmd.outfiles = ["test_pb2.py", "other_pb2.py"] assert self.cmd.get_outputs() == ["test_pb2.py", "other_pb2.py"] def test_get_outputs_empty(self): """Test get_outputs with empty outfiles list.""" self.cmd.outfiles = [] assert self.cmd.get_outputs() == [] def test_run_no_protobufs(self): """Test run method with no protobuf files.""" self.dist.protobufs = [] self.cmd.protoc = "/usr/bin/protoc" # Should run without error when no protobufs self.cmd.run() assert self.cmd.outfiles == [] def test_run_integration(self): """Integration test for build_protobuf with actual files.""" # Integration test with actual temp files with tempfile.TemporaryDirectory() as tmpdir: proto_file = Path(tmpdir) / "test.proto" proto_file.write_text('syntax = "proto3";\nmessage Test {}') pb = Protobuf(str(proto_file)) self.dist.protobufs = [pb] # This will fail if protoc is not installed, which is expected self.cmd.protoc = find_executable("protoc") if self.cmd.protoc: try: self.cmd.run() # If protoc exists and runs, check outputs assert ( str(proto_file).replace(".proto", "_pb2.py") in self.cmd.outfiles ) except (ExecError, PlatformError): # protoc might exist but fail for other reasons pass class TestCleanProtobuf(unittest.TestCase): """Test cases for the clean_protobuf command.""" def setUp(self): """Set up test fixtures for clean_protobuf tests.""" self.dist = Distribution() self.cmd = clean_protobuf(self.dist) def test_initialize_finalize_options(self): """Test initialize and finalize options methods.""" # Should not raise self.cmd.initialize_options() self.cmd.finalize_options() def test_run_no_protobufs(self): """Test run method with no protobuf files.""" self.cmd.protobufs = [] # Should run without error self.cmd.run() def test_run_with_temp_files(self): """Test cleaning actual generated protobuf files.""" with tempfile.TemporaryDirectory() as tmpdir: # Create actual output file output_file = Path(tmpdir) / "test_pb2.py" output_file.write_text("# Generated protobuf file") pb = Protobuf("test.proto") pb.outputs = lambda: [str(output_file)] self.cmd.protobufs = [pb] assert output_file.exists() self.cmd.run() assert not output_file.exists() def test_run_file_already_missing(self): """Test run method when files are already missing.""" pb = Protobuf("nonexistent.proto") self.cmd.protobufs = [pb] # Should not raise even if file doesn't exist self.cmd.run() class TestFindExecutable(unittest.TestCase): """Test cases for the find_executable function.""" def test_find_absolute_path_exists(self): """Test finding executable with absolute path that exists.""" # Create a temporary file and close it so find_executable can access it with tempfile.NamedTemporaryFile( delete=False, suffix=".exe" if sys.platform == "win32" else "" ) as f: temp_path = f.name try: result = find_executable(temp_path) assert result == temp_path finally: # Clean up the file try: os.unlink(temp_path) except (OSError, PermissionError): # On Windows, file might still be in use pass def test_find_absolute_path_not_exists(self): """Test finding executable with absolute path that doesn't exist.""" result = find_executable("/definitely/not/a/real/path/protoc") assert result is None def test_find_in_path(self): """Test finding executable in system PATH.""" # Test finding actual executables that likely exist for cmd in ["python", "python3", sys.executable]: result = find_executable(cmd) if result: # If found, it should be a valid file assert os.path.isfile(result) break def test_not_found(self): """Test when executable is not found anywhere.""" result = find_executable("definitely_not_a_real_executable_name_12345") assert result is None def test_empty_path(self): """Test finding executable with empty PATH environment variable.""" with patch.dict(os.environ, {"PATH": ""}): result = find_executable("protoc") assert result is None def test_windows_exe_extension(self): """Test that .exe extension is added on Windows.""" if sys.platform == "win32": # On Windows, test that .exe is added result = find_executable("python") if result: assert result.endswith(".exe") class TestGetProtoc(unittest.TestCase): """Test cases for the get_protoc function.""" def test_none_version(self): """Test get_protoc with None version.""" result = get_protoc(None) assert result is None def test_version_string_format(self): """Test get_protoc handles version strings correctly.""" # Test that the function handles version strings correctly # without actually downloading import platform with tempfile.TemporaryDirectory() as tmpdir: with patch( "setuptools_protobuf.__file__", os.path.join(tmpdir, "__init__.py") ): # Create expected directory structure system = platform.system() machine = platform.machine().lower() if machine in ["amd64", "x64", "x86_64"]: machine = "x86_64" elif machine in ["aarch64", "arm64", "aarch_64"]: machine = "aarch_64" if system == "Linux": release = f"protoc-3.20.0-linux-{machine}" elif system == "Darwin": release = f"protoc-3.20.0-osx-{machine}" elif system == "Windows": if machine == "x86_64": release = "protoc-3.20.0-win64" else: release = "protoc-3.20.0-win32" else: # Unsupported platform return # Create fake protoc executable protoc_dir = Path(tmpdir) / release / "bin" protoc_dir.mkdir(parents=True) protoc_file = protoc_dir / ( "protoc.exe" if system == "Windows" else "protoc" ) protoc_file.write_text("fake protoc") result = get_protoc("3.20.0") assert result == str(protoc_file) class TestHasProtobuf(unittest.TestCase): """Test cases for the has_protobuf function.""" def test_has_protobuf_true(self): """Test has_protobuf returns True when protobufs exist.""" cmd = MagicMock() cmd.distribution.protobufs = [Protobuf("test.proto")] assert has_protobuf(cmd) is True def test_has_protobuf_empty_list(self): """Test has_protobuf returns False for empty list.""" cmd = MagicMock() cmd.distribution.protobufs = [] assert has_protobuf(cmd) is False def test_has_protobuf_no_attribute(self): """Test has_protobuf returns False when attribute doesn't exist.""" cmd = MagicMock() # Explicitly delete the attribute to test getattr default del cmd.distribution.protobufs assert has_protobuf(cmd) is False def test_has_protobuf_multiple(self): """Test has_protobuf returns True for multiple protobufs.""" cmd = MagicMock() cmd.distribution.protobufs = [ Protobuf("test1.proto"), Protobuf("test2.proto"), Protobuf("test3.proto"), ] assert has_protobuf(cmd) is True class TestLoadPyprojectConfig(unittest.TestCase): """Test cases for the load_pyproject_config function.""" def test_basic_config(self): """Test loading basic configuration from pyproject.toml.""" dist = Distribution() cfg = { "protobufs": ["test.proto", "other.proto"], "mypy": True, "proto_path": "protos", "protoc_version": "3.20.0", } load_pyproject_config(dist, cfg) assert dist.protoc_version == "3.20.0" assert len(dist.protobufs) == 2 assert all(isinstance(pb, Protobuf) for pb in dist.protobufs) assert dist.protobufs[0].path == "test.proto" assert dist.protobufs[0].mypy is True assert dist.protobufs[0].proto_path == "protos" assert dist.protobufs[1].path == "other.proto" def test_minimal_config(self): """Test loading minimal configuration.""" dist = Distribution() cfg = {"protobufs": ["test.proto"]} load_pyproject_config(dist, cfg) assert dist.protoc_version is None assert len(dist.protobufs) == 1 assert dist.protobufs[0].path == "test.proto" assert dist.protobufs[0].proto_path is None def test_config_no_protobufs(self): """Test configuration with empty protobufs list.""" dist = Distribution() cfg = {"protoc_version": "3.20.0", "protobufs": []} load_pyproject_config(dist, cfg) assert dist.protoc_version == "3.20.0" assert dist.protobufs == [] def test_config_with_mypy_false(self): """Test configuration with mypy disabled.""" dist = Distribution() cfg = {"protobufs": ["test.proto"], "mypy": False} load_pyproject_config(dist, cfg) assert dist.protobufs[0].mypy is False class TestPyprojecttomlConfig(unittest.TestCase): """Test cases for the pyprojecttoml_config function.""" def test_with_temp_pyproject(self): """Test pyprojecttoml_config with temporary pyproject.toml file.""" with tempfile.TemporaryDirectory() as tmpdir: orig_cwd = os.getcwd() try: os.chdir(tmpdir) # Test without pyproject.toml dist = Distribution() pyprojecttoml_config(dist) assert dist.protoc_version is None # Test with pyproject.toml pyproject = Path("pyproject.toml") pyproject.write_text(""" [tool.setuptools-protobuf] protobufs = ["test.proto"] protoc_version = "3.20.0" """) dist = Distribution() pyprojecttoml_config(dist) assert dist.protoc_version == "3.20.0" assert len(dist.protobufs) == 1 assert dist.protobufs[0].path == "test.proto" finally: os.chdir(orig_cwd) def test_commands_registered(self): """Test that build and clean commands are properly registered.""" dist = Distribution() pyprojecttoml_config(dist) build = dist.get_command_class("build") assert ("build_protobuf", has_protobuf) in build.sub_commands clean = dist.get_command_class("clean") assert ("clean_protobuf", has_protobuf) in clean.sub_commands if __name__ == "__main__": unittest.main()