python - 在 setup.py 中自动将 protobuf 规范编译为 python 类

标签 python protocol-buffers distutils distutils2

我有一个 python 项目,它使用 google protobufs 作为通过网络进行通信的消息格式。使用 protoc 程序可以直接从 .proto 文件生成 python 文件。如何为项目配置我的 setup.py 文件,以便它自动调用 protoc 命令?

最佳答案

在类似的情况下,我得到了这段代码(setup.py,但以允许提取到某些外部 Python 模块中以供重用的方式编写)。请注意,我从 protobuf 源代码分发的 setup.py 文件中获取了 generate_proto 函数和几个想法。

from __future__ import print_function

import os
import shutil
import subprocess
import sys

from distutils.command.build_py import build_py as _build_py
from distutils.command.clean import clean as _clean
from distutils.debug import DEBUG
from distutils.dist import Distribution
from distutils.spawn import find_executable
from nose.commands import nosetests as _nosetests
from setuptools import setup

PROTO_FILES = [
    'goobuntu/proto/hoststatus.proto',
    ]

CLEANUP_SUFFIXES = [
    # filepath suffixes of files to remove on "clean" subcommand
    '_pb2.py',
    '.pyc',
    '.so',
    '.o',
    'dependency_links.txt',
    'entry_points.txt',
    'PKG-INFO',
    'top_level.txt',
    'SOURCES.txt',
    '.coverage',
    'protobuf/compiler/__init__.py',
    ]

CLEANUP_DIRECTORIES = [  # subdirectories to remove on "clean" subcommand
    # 'build'  # Note: the build subdirectory is removed if --all is set.
    'html-coverage',
    ]

if 'PROTOC' in os.environ and os.path.exists(os.environ['PROTOC']):
  protoc = os.environ['PROTOC']
else:
  protoc = find_executable('protoc')


def generate_proto(source):
  """Invoke Protocol Compiler to generate python from given source .proto."""
  if not os.path.exists(source):
    sys.stderr.write('Can\'t find required file: %s\n' % source)
    sys.exit(1)

  output = source.replace('.proto', '_pb2.py')
  if (not os.path.exists(output) or
      (os.path.getmtime(source) > os.path.getmtime(output))):
    if DEBUG:
      print('Generating %s' % output)

    if protoc is None:
      sys.stderr.write(
          'protoc not found. Is protobuf-compiler installed? \n'
          'Alternatively, you can point the PROTOC environment variable at a '
          'local version.')
      sys.exit(1)

    protoc_command = [protoc, '-I.', '--python_out=.', source]
    if subprocess.call(protoc_command) != 0:
      sys.exit(1)


class MyDistribution(Distribution):
  # Helper class to add the ability to set a few extra arguments
  # in setup():
  # protofiles : Protocol buffer definitions that need compiling
  # cleansuffixes : Filename suffixes (might be full names) to remove when
  #                   "clean" is called
  # cleandirectories : Directories to remove during cleanup
  # Also, the class sets the clean, build_py, test and nosetests cmdclass
  # options to defaults that compile protobufs, implement test as nosetests
  # and enables the nosetests command as well as using our cleanup class.

  def __init__(self, attrs=None):
    self.protofiles = []  # default to no protobuf files
    self.cleansuffixes = ['_pb2.py', '.pyc']  # default to clean generated files
    self.cleandirectories = ['html-coverage']  # clean out coverage directory
    cmdclass = attrs.get('cmdclass')
    if not cmdclass:
      cmdclass = {}
    # These should actually modify attrs['cmdclass'], as we assigned the
    # mutable dict to cmdclass without copying it.
    if 'nosetests' not in cmdclass:
      cmdclass['nosetests'] = MyNosetests
    if 'test' not in cmdclass:
      cmdclass['test'] = MyNosetests
    if 'build_py' not in cmdclass:
      cmdclass['build_py'] = MyBuildPy
    if 'clean' not in cmdclass:
      cmdclass['clean'] = MyClean
    attrs['cmdclass'] = cmdclass
    # call parent __init__ in old style class
    Distribution.__init__(self, attrs)


class MyClean(_clean):

  def run(self):
    try:
      cleandirectories = self.distribution.cleandirectories
    except AttributeError:
      sys.stderr.write(
          'Error: cleandirectories not defined. MyDistribution not used?')
      sys.exit(1)
    try:
      cleansuffixes = self.distribution.cleansuffixes
    except AttributeError:
      sys.stderr.write(
          'Error: cleansuffixes not defined. MyDistribution not used?')
      sys.exit(1)
    # Remove build and html-coverage directories if they exist
    for directory in cleandirectories:
      if os.path.exists(directory):
        if DEBUG:
          print('Removing directory: "{}"'.format(directory))
        shutil.rmtree(directory)
    # Delete generated files in code tree.
    for dirpath, _, filenames in os.walk('.'):
      for filename in filenames:
        filepath = os.path.join(dirpath, filename)
        for i in cleansuffixes:
          if filepath.endswith(i):
            if DEBUG:
              print('Removing file: "{}"'.format(filepath))
            os.remove(filepath)
    # _clean is an old-style class, so super() doesn't work
    _clean.run(self)


class MyBuildPy(_build_py):

  def run(self):
    try:
      protofiles = self.distribution.protofiles
    except AttributeError:
      sys.stderr.write(
          'Error: protofiles not defined. MyDistribution not used?')
      sys.exit(1)
    for proto in protofiles:
      generate_proto(proto)
    # _build_py is an old-style class, so super() doesn't work
    _build_py.run(self)


class MyNosetests(_nosetests):

  def run(self):
    try:
      protofiles = self.distribution.protofiles
    except AttributeError:
      sys.stderr.write(
          'Error: protofiles not defined. MyDistribution not used?')
    for proto in protofiles:
      generate_proto(proto)
    # _nosetests is an old-style class, so super() doesn't work
    _nosetests.run(self)


setup(
    # MyDistribution automatically enables several extensions, including
    # the compilation of protobuf files.
    distclass=MyDistribution,
    ...
    tests_require=['nose'],
    protofiles=PROTO_FILES,
    cleansuffixes=CLEANUP_SUFFIXES,
    cleandirectories=CLEANUP_DIRECTORIES,
    )

关于python - 在 setup.py 中自动将 protobuf 规范编译为 python 类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23585450/

相关文章:

python - 在 pypi 上注册包时为 "Server response (401): You must login to access this feature"

python - 获取 HTML 代码的结构

python - Python 中 Perl 模块 List::Util、List::MoreUtils 功能的类似物

python - 从 Python 中的 caffe .prototxt 模型定义中读取网络参数

python - setup.py 没有安装 swig 扩展模块

python - Distutils - 如何在构建扩展时禁用包括调试符号

python - 使用 beautifulsoup 解析数据

Python 用 Pandas 压平多重嵌套字典 JSON

c# - protobuf3 中的重复 Int32Value(可为空的 int 数组)

protocol-buffers - protobuf 中字段的多行注释