Skip to content

Commit 4bc4f26

Browse files
author
Sean Cribbs
committed
Merge pull request basho#77 from basho/feature/sdc/python-msgcodegen
Generate Python from message code mappings.
2 parents 302c304 + ec07077 commit 4bc4f26

File tree

5 files changed

+222
-3
lines changed

5 files changed

+222
-3
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ src/riak_pb_messages.erl
1515
# Python
1616
riak_pb.egg-info
1717
riak_pb/*_pb2.py
18+
riak_pb/messages.py
1819
build
1920
*.pyc
2021
dist/*.egg

MANIFEST.in

+3-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
include version.py
1+
include version.py
2+
include msgcodegen.py
3+
include src/riak_pb_messages.csv

Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,12 @@ cleanplt:
6161
python_compile:
6262
@echo "==> Python (compile)"
6363
@protoc -Isrc --python_out=riak_pb src/*.proto
64-
@./setup.py build
64+
@./setup.py build_messages build
6565

6666
python_clean:
6767
@echo "==> Python (clean)"
6868
@rm -f riak_pb/*_pb2.py
69-
@./setup.py clean
69+
@./setup.py clean clean_messages
7070

7171
python_release: python_compile
7272
@echo "==> Python (release)"

msgcodegen.py

+213
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
# Copyright 2013 Basho Technologies, Inc.
2+
#
3+
# This file is provided to you under the Apache License,
4+
# Version 2.0 (the "License"); you may not use this file
5+
# except in compliance with the License. You may obtain
6+
# a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing,
11+
# software distributed under the License is distributed on an
12+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
13+
# KIND, either express or implied. See the License for the
14+
# specific language governing permissions and limitations
15+
# under the License.
16+
17+
"""
18+
distutils commands for generating protocol message-code mappings.
19+
"""
20+
21+
__all__ = ['build_messages', 'clean_messages']
22+
23+
import re
24+
import csv
25+
import os
26+
from os.path import isfile
27+
from distutils import log
28+
from distutils.core import Command
29+
from distutils.file_util import write_file
30+
from datetime import date
31+
32+
LICENSE = """# Copyright {0} Basho Technologies, Inc.
33+
#
34+
# This file is provided to you under the Apache License,
35+
# Version 2.0 (the "License"); you may not use this file
36+
# except in compliance with the License. You may obtain
37+
# a copy of the License at
38+
#
39+
# http://www.apache.org/licenses/LICENSE-2.0
40+
#
41+
# Unless required by applicable law or agreed to in writing,
42+
# software distributed under the License is distributed on an
43+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
44+
# KIND, either express or implied. See the License for the
45+
# specific language governing permissions and limitations
46+
# under the License.
47+
""".format(date.today().year)
48+
49+
50+
class MessageCodeMapping(object):
51+
def __init__(self, code, message, proto):
52+
self.code = int(code)
53+
self.message = message
54+
self.proto = proto
55+
self.message_code_name = self._message_code_name()
56+
self.module_name = "riak_pb.{0}_pb2".format(self.proto)
57+
self.message_class = self._message_class()
58+
59+
def __cmp__(self, other):
60+
return cmp(self.code, other.code)
61+
62+
def _message_code_name(self):
63+
strip_rpb = re.sub(r"^Rpb", "", self.message)
64+
word = re.sub(r"([A-Z]+)([A-Z][a-z])", r'\1_\2', strip_rpb)
65+
word = re.sub(r"([a-z\d])([A-Z])", r'\1_\2', word)
66+
word = word.replace("-", "_")
67+
return "MSG_CODE_" + word.upper()
68+
69+
def _message_class(self):
70+
try:
71+
pbmod = __import__(self.module_name, globals(), locals(),
72+
[self.message])
73+
klass = pbmod.__dict__[self.message]
74+
return klass
75+
except KeyError:
76+
log.debug("Did not find '{0}' message class in module '{1}'",
77+
self.message, self.module_name)
78+
except ImportError:
79+
log.debug("Could not import module '{0}'", self.module_name)
80+
return None
81+
82+
83+
class clean_messages(Command):
84+
"""
85+
Cleans generated message code mappings. Add to the build process
86+
using::
87+
88+
setup(cmd_class={'clean_messages': clean_messages})
89+
"""
90+
91+
description = "clean generated protocol message code mappings"
92+
93+
user_options = [
94+
('destination', None, 'destination Python source file')
95+
]
96+
97+
def initialize_options(self):
98+
self.destination = None
99+
100+
def finalize_options(self):
101+
self.set_undefined_options('build_messages',
102+
('destination', 'destination'))
103+
104+
def run(self):
105+
if isfile(self.destination):
106+
self.execute(os.remove, [self.destination],
107+
msg="removing {0}".format(self.destination))
108+
109+
110+
class build_messages(Command):
111+
"""
112+
Generates message code mappings. Add to the build process using::
113+
114+
setup(cmd_class={'build_messages': build_messages})
115+
"""
116+
117+
description = "generate protocol message code mappings"
118+
119+
user_options = [
120+
('source=', None, 'source CSV file containing message code mappings'),
121+
('destination=', None, 'destination Python source file')
122+
]
123+
124+
# Used in loading and generating
125+
_pb_imports = set()
126+
_messages = set()
127+
_linesep = os.linesep
128+
_indented_item_sep = ',{0} '.format(_linesep)
129+
130+
_docstring = [
131+
''
132+
'# This is a generated file. DO NOT EDIT.',
133+
'',
134+
'"""',
135+
'Constants and mappings between Riak protocol codes and messages.',
136+
'"""',
137+
''
138+
]
139+
140+
def initialize_options(self):
141+
self.source = None
142+
self.destination = None
143+
144+
def finalize_options(self):
145+
if self.source is None:
146+
self.source = 'src/riak_pb_messages.csv'
147+
if self.destination is None:
148+
self.destination = 'riak_pb/messages.py'
149+
150+
def run(self):
151+
self.make_file(self.source, self.destination,
152+
self._load_and_generate, [])
153+
154+
def _load_and_generate(self):
155+
self._load()
156+
self._generate()
157+
158+
def _load(self):
159+
with open(self.source, 'rb') as csvfile:
160+
reader = csv.reader(csvfile)
161+
for row in reader:
162+
message = MessageCodeMapping(*row)
163+
self._messages.add(message)
164+
self._pb_imports.add(message.module_name)
165+
166+
def _generate(self):
167+
self._contents = []
168+
self._generate_doc()
169+
self._generate_imports()
170+
self._generate_codes()
171+
self._generate_classes()
172+
write_file(self.destination, self._contents)
173+
174+
def _generate_doc(self):
175+
# Write the license and docstring header
176+
self._contents.append(LICENSE)
177+
self._contents.extend(self._docstring)
178+
179+
def _generate_imports(self):
180+
# Write imports
181+
for im in sorted(self._pb_imports):
182+
self._contents.append("import {0}".format(im))
183+
184+
def _generate_codes(self):
185+
# Write protocol code constants
186+
self._contents.extend(['', "# Protocol codes"])
187+
for message in sorted(self._messages):
188+
self._contents.append("{0} = {1}".format(message.message_code_name,
189+
message.code))
190+
191+
def _generate_classes(self):
192+
# Write message classes
193+
classes = [self._generate_mapping(message)
194+
for message in sorted(self._messages)]
195+
196+
classes = self._indented_item_sep.join(classes)
197+
self._contents.extend(['',
198+
"# Mapping from code to protobuf class",
199+
'MESSAGE_CLASSES = {',
200+
' ' + classes,
201+
'}'])
202+
203+
def _generate_mapping(self, m):
204+
if m.message_class is not None:
205+
klass = "{0}.{1}".format(m.module_name,
206+
m.message_class.__name__)
207+
else:
208+
klass = "None"
209+
pair = "{0}: {1}".format(m.message_code_name, klass)
210+
if len(pair) > 76:
211+
# Try to satisfy PEP8, lulz
212+
pair = (self._linesep + ' ').join(pair.split(' '))
213+
return pair

setup.py

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from setuptools import setup
44
import version
5+
from msgcodegen import build_messages, clean_messages
56

67
setup(name='riak_pb',
78
version=version.get_version(),
@@ -16,6 +17,8 @@
1617
author_email='[email protected]',
1718
url='https://github.com/basho/riak_pb',
1819
zip_safe=True,
20+
cmdclass={'build_messages': build_messages,
21+
'clean_messages': clean_messages},
1922
classifiers=['License :: OSI Approved :: Apache Software License',
2023
'Intended Audience :: Developers',
2124
'Operating System :: OS Independent',

0 commit comments

Comments
 (0)