Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix and same open feature #161

Open
wants to merge 33 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4033a88
Add coment parsing for messge and enum
May 15, 2019
1c4ef13
Update nested enum, enum in messages
May 20, 2019
9d284bd
fix set default parse enum, first token a comment
Jun 6, 2019
bc25398
add base package pyrogen
Jul 2, 2019
e73c28f
add comment handling for oneof (test_oneof)
Jul 5, 2019
a7b34bf
fix nested class enum defintion (test_setters)
Jul 5, 2019
8b400b7
upgrade tests for class enum
Jul 5, 2019
8b66602
fix pyrogen package creation
Jul 9, 2019
389fed7
upgrade tests for package
Jul 9, 2019
8a1b6ee
remove whitespaces, fix typo
Jul 9, 2019
6d3061a
add version prefix vistec
Jul 9, 2019
a3e297d
fix empty comment line
Aug 20, 2019
5facef0
add handling comments with double quotes
Aug 20, 2019
ef933e3
upgrade field comment parsing
Aug 20, 2019
4acf65a
upgrade block comment parsing
Aug 21, 2019
eb88829
add field message comment tests
Aug 21, 2019
4ac9dd7
fix docstring class and enum
Oct 21, 2019
c2faed3
Add hex value parsing to set default
Nov 5, 2019
711b193
take back import base package pyrogen in tests
Jul 27, 2020
e86576e
Merge branch 'master' of https://github.com/appnexus/pyrobuf
Jul 27, 2020
283fbd1
add parsing atomic type from custom field option
Jul 27, 2020
99c5fad
fix running tests, master branch merge
Jul 28, 2020
94285bc
add version for generated package using datetime
Jul 28, 2020
8fbbd6a
update package format to wheel
Jun 14, 2021
03d4cc8
Remove comment parsing, change Enum to IntEnum
ChristianToepfer Nov 17, 2022
cad43f4
remove wheel build/install, add site_pkg option
ChristianToepfer Nov 28, 2022
67d44ef
fix test with python 3.10
ChristianToepfer Nov 28, 2022
5915d06
fix many field (>64) with unused field index
ChristianToepfer Nov 29, 2022
189999c
remove site_pkg option, skip build for includes
ChristianToepfer Nov 30, 2022
16bc2c1
(remove extra lines)
ChristianToepfer Nov 30, 2022
4ae30e4
fix parsing custom field option set with key value
ChristianToepfer Sep 13, 2023
de28145
update base package naming, add arg: module_name
ChristianToepfer Sep 14, 2023
9197b00
fix init static module_name (with pyhton 3.10.12)
ChristianToepfer Sep 22, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions pyrobuf/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from setuptools import setup

from Cython.Build import cythonize
from pathlib import Path
from jinja2 import Environment, PackageLoader

from pyrobuf.parse_proto import Parser, Proto3Parser
Expand All @@ -24,18 +25,20 @@ class Compiler(object):
t_pyx = _env.get_template('proto_pyx.tmpl')

def __init__(self, sources, out="out", build="build", install=False,
proto3=False, force=False, package=None, includes=None,
proto3=False, force=False, package=None, module_name=None, includes=None,
clean=False):
self.sources = sources
self.out = out
if module_name:
self.out = os.path.join(out, module_name)
self.build = build
self.install = install
self.force = force
self.package = package
self.includes = includes or []
self.includes = [os.path.normpath(inc) for inc in includes or []]
self.clean = clean
here = os.path.dirname(os.path.abspath(__file__))
self.include_path = [os.path.join(here, 'src'), self.out]
self.include_path = [os.path.join(here, 'src'), out]
self._generated = set()
self._messages = []
self._pyx_files = []
Expand All @@ -45,6 +48,9 @@ def __init__(self, sources, out="out", build="build", install=False,
else:
self.parser = Parser

if module_name:
self.parser.module_name = module_name+'.'

@classmethod
def parse_cli_args(cls):
parser = argparse.ArgumentParser(
Expand All @@ -65,6 +71,8 @@ def parse_cli_args(cls):
help="force install")
parser.add_argument('--package', type=str, default=None,
help="name of package to compile to")
parser.add_argument('--module_name', type=str, default=None,
help="name of module to compile to")
parser.add_argument('--include', action='append',
help="add directory to includes path")
parser.add_argument('--clean', action='store_true',
Expand All @@ -73,7 +81,7 @@ def parse_cli_args(cls):

return cls(args.sources, out=args.out_dir, build=args.build_dir,
install=args.install, proto3=args.proto3, force=args.force,
package=args.package, includes=args.include,
package=args.package, module_name=args.module_name, includes=args.include,
clean=args.clean)

def compile(self):
Expand Down Expand Up @@ -110,6 +118,9 @@ def extend(self, dist):
def _compile_spec(self):
try:
os.makedirs(self.out)
if self.parser.module_name:
filename = Path(self.out).joinpath('__init__.py')
filename.touch(exist_ok=True)
except _FileExistsError:
pass

Expand Down Expand Up @@ -150,6 +161,9 @@ def _generate(self, filename):

if self.package is None:
self._write(name, msg_def)
if (directory) in self.includes:
self._pyx_files.pop()
print("skip building {0}".format(filename))

def _write(self, name, msg_def):
name_pxd = "{}_proto.pxd".format(name)
Expand Down
74 changes: 59 additions & 15 deletions pyrobuf/parse_proto.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@ class Parser(object):
('DEFAULT', r'default\s*='),
('PACKED', r'packed\s*=\s*(true|false)'),
('DEPRECATED', r'deprecated\s*=\s*(true|false)'),
('CUSTOM', r'(\([A-Za-z][0-9A-Za-z_]*\).[A-Za-z][0-9A-Za-z_]*)\s*='),
('CUSTOM', r'(\([A-Za-z][0-9A-Za-z_]*\)(?:.[A-Za-z][0-9A-Za-z_]*)?)\s*='),
('LBRACKET', r'\['),
('RBRACKET', r'\]\s*;'),
('LBRACE', r'\{'),
('KEY_VALUE', r'\:'),
('RBRACE', r'\}\s*;{0,1}'),
('COMMA', r','),
('SKIP', r'\s'),
('SEMICOLON', r';'),
('HEXVALUE', r'(0x[0-9A-Fa-f]+)'),
('NUMERIC', r'(-?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?)'),
('STRING', r'("(?:\\.|[^"\\])*"|\'(?:\\.|[^"\\])*\')'),
('BOOLEAN', r'(true|false)'),
Expand All @@ -57,6 +59,7 @@ class Parser(object):
'SEMICOLON',
'ENUM',
'LBRACE',
'KEY_VALUE',
'RBRACE',
'EXTENSION',
'ONEOF',
Expand Down Expand Up @@ -136,6 +139,7 @@ class Parser(object):
token_regex = '|'.join('(?P<%s>%s)' % pair for pair in tokens)
get_token = re.compile(token_regex).match
token_getter = {key: re.compile(val).match for key, val in tokens}
module_name = ''

def __init__(self, string):
self.string = string
Expand Down Expand Up @@ -176,12 +180,13 @@ def tokenize(self, disabled_token_types):
def parse(self, cython_info=True, fname='', includes=None, disabled_tokens=()):
self.verify_parsable_tokens()
tokens = self.tokenize(disabled_tokens)
rep = {'imports': [], 'messages': [], 'enums': []}
rep = {'imports': [], 'messages': [], 'enums': [], 'module_name': self.module_name}
enums = {}
imported = {'messages': {}, 'enums': {}}
messages = {}
includes = includes or []
scope = {}
previous = self.LBrace(-1)

for token in tokens:
if token.token_type == 'OPTION':
Expand Down Expand Up @@ -459,7 +464,7 @@ def _parse_field(self, field, tokens):
elif token.token_type == 'DEPRECATED':
field.deprecated = token.value
elif token.token_type == 'CUSTOM':
if self._parse_custom(field, tokens):
if self._parse_custom(field, tokens, token.name):
return
elif token.token_type == 'COMMA':
continue
Expand All @@ -480,8 +485,12 @@ def _parse_default(self, field, tokens):
# This will get updated later
field.default = token.full_name
return
elif token.token_type == 'HEXVALUE':
assert field.type in self.scalars.difference({'bool', 'enum'}), \
"attempting to set hex value as default for non-numeric field on line {}: '{}'".format(
token.line + 1, self.lines[token.line])
elif token.token_type == 'NUMERIC':
assert field.type in self.scalars, \
assert field.type in self.scalars.difference({'bool', 'enum'}), \
"attempting to set numeric as default for non-numeric field on line {}: '{}'".format(
token.line + 1, self.lines[token.line])
if field.type not in self.floats:
Expand All @@ -498,39 +507,58 @@ def _parse_default(self, field, tokens):

field.default = token.value

def _parse_custom(self, field, tokens):
def _parse_custom(self, field, tokens, custom_name):
"""Parse a custom option and return whether or not we hit the closing RBRACKET"""

custom_name= custom_name[1:-1] # remove ()
field.options = dict()
token = next(tokens)

if token.token_type == 'STRING':
field.value = token.value
field.options[custom_name] = token.value
for token in tokens:
if token.token_type == 'STRING':
field.value += token.value
field.options[custom_name] += token.value
continue
elif token.token_type == 'COMMA':
return False
else:
assert token.token_type == 'RBRACKET'
return True
else:
assert token.token_type in {'NUMERIC', 'BOOLEAN'}, "unexpected custom option value on line {}: '{}'".format(
token.line + 1, self.lines[token.line])
field.value = token.value
if token.token_type == 'LBRACE':
field.options[custom_name] = dict()
while token:
token = next(tokens)
if token.token_type == 'RBRACE':
return False
elif token.token_type == 'KEY_VALUE':
continue
elif hasattr(token,'name'):
key = token.name
elif hasattr(token,'value'):
field.options[custom_name][key]=token.value
else:
assert token.token_type in {'NUMERIC', 'BOOLEAN'}, "unexpected custom option value on line {}: '{}'".format(
token.line + 1, self.lines[token.line])
field.options[custom_name] = token.value
return False

def _parse_enum(self, current, tokens, scope, current_message=None):
token = next(tokens)
assert token.token_type == 'LBRACE', "missing opening brace on line {}: '{}'".format(
token.line + 1, self.lines[token.line])
previous = self.LBrace(-1)
setDefault = False

for num, token in enumerate(tokens):
for token in tokens:
if token.token_type == 'ENUM_FIELD':
if num == 0:
if (setDefault is False):
if self.syntax == 3:
assert token.value == 0, "expected zero as first enum element on line {}, got {}: '{}'".format(
token.line + 1, token.value, self.lines[token.line])
current.default = token
setDefault = True

token.full_name = "{}_{}".format(current.full_name, token.name)

Expand All @@ -551,6 +579,7 @@ def _parse_enum(self, current, tokens, scope, current_message=None):
token.token_type, token.line + 1, self.lines[token.line])
return current

previous = token
raise Exception("unexpected EOF on line {}: '{}'".format(
token.line + 1, self.lines[token.line]))

Expand All @@ -560,7 +589,7 @@ def _parse_enum_field(self, field, tokens):
if token.token_type == 'LBRACKET':
for token in tokens:
if token.token_type == 'CUSTOM':
if self._parse_custom(field, tokens):
if self._parse_custom(field, tokens, token.name):
return
elif token.token_type == 'COMMA':
continue
Expand All @@ -584,9 +613,11 @@ def _parse_extend(self, current, tokens):
return current

def add_cython_info(self, message):
count = 0
for index, field in message.fields.items():
field.bitmap_idx = (index - 1) // 64
field.bitmap_mask = 1 << ((index - 1) % 64)
field.bitmap_idx = count // 64
field.bitmap_mask = 1 << (count % 64)
count += 1
field.list_type = self.list_type_map.get(field.type, 'TypedList')
field.fixed_width = (field.type in {
'float', 'double', 'fixed32', 'sfixed32', 'fixed64', 'sfixed64'
Expand Down Expand Up @@ -807,6 +838,13 @@ def __init__(self, line, value):
self.line = line
self.value = float(value)

class HexValue(Token):
token_type = 'HEXVALUE'

def __init__(self, line, value):
self.line = line
self.value = int(value, 16)

class String(Token):
token_type = 'STRING'

Expand All @@ -827,6 +865,12 @@ class LBrace(Token):
def __init__(self, line):
self.line = line

class KEY_VALUE(Token):
token_type = 'KEY_VALUE'

def __init__(self, line):
self.line = line

class RBrace(Token):
token_type = 'RBRACE'

Expand Down
2 changes: 1 addition & 1 deletion pyrobuf/protobuf/templates/proto_pxd.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ from pyrobuf_util cimport *
import json

{%- for import in imports %}
from {{ import }}_proto cimport *
from {{module_name}}{{ import }}_proto cimport *
{%- endfor %}

{%- macro classdef(message) %}
Expand Down
20 changes: 8 additions & 12 deletions pyrobuf/protobuf/templates/proto_pyx.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ from pyrobuf_util cimport *
import base64
import json
import warnings
import enum

{%- for import in imports %}
from {{ import }}_proto cimport *
from {{module_name}}{{ import }}_proto cimport *
{%- endfor %}

{%- macro message_enum_fields_def(enum) %}
{%- macro enum_fields_def(enum) %}
class {{enum.full_name}}(enum.IntEnum):
{%- for field in enum.fields.values() %}
{{ field.name }} = _{{ field.full_name }}
{%- endfor %}
Expand Down Expand Up @@ -997,10 +999,6 @@ cdef class {{ message.full_name }}:
yield self.{{ field.name }}
{%- endfor %}

{% for message_enum_name, message_enum in message.enums.items() %}
{{ message_enum_fields_def(message_enum) }}
{% endfor %}

def Setters(self):
"""
Iterator over functions to set the fields in a message.
Expand All @@ -1014,6 +1012,10 @@ cdef class {{ message.full_name }}:
yield setter
{%- endfor %}

{% for message_enum_name, message_enum in message.enums.items() %}
{{ enum_fields_def(message_enum) }}
{% endfor %}

{% for message_name, message_message in message.messages.items() %}
{{ classdef(message_message) }}
{% endfor %}
Expand All @@ -1026,12 +1028,6 @@ class DecodeError(Exception):
{{ classdef(message) }}
{%- endfor %}

{%- macro enum_fields_def(enum) %}
{%- for field in enum.fields.values() %}
{{ field.name }} = _{{ field.full_name }}
{%- endfor %}
{%- endmacro %}

{%- for enum in enums %}
{{ enum_fields_def(enum) }}
{%- endfor %}
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import sys


VERSION = "0.9.3"
VERSION = "0.9.3.13"
HERE = os.path.dirname(os.path.abspath(__file__))
PYROBUF_DEFS_PXI = "pyrobuf_defs.pxi"
PYROBUF_LIST_PXD = "pyrobuf_list.pxd"
Expand Down
10 changes: 9 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,15 @@ def pytest_sessionstart(session):
# Insert built messages into path
build = os.path.join(here, 'build')
lib_path = os.path.join(build, "lib.{0}-{1}".format(get_platform(),
sys.version[0:3]))
sys.version[0:4]))

# for import with full_name (used in templates)
if lib_path not in sys.path:
sys.path.insert(0, lib_path)

if compiler.parser.module_name:
# for short import wihtout pyrogen prefix
lib_path = os.path.join(lib_path,compiler.parser.module_name)
if lib_path not in sys.path:
sys.path.insert(0, lib_path)

1 change: 1 addition & 0 deletions tests/proto/test_custom_options.proto
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ message TestCustomOptions {
VALUE1 = 1;
}
optional CustomEnum field3 = 3 [ default = VALUE1, (my_options).custom1 = 4 ];
optional double field4 = 4 [ (my_option)= 3.4 ];
}
7 changes: 6 additions & 1 deletion tests/proto/test_many_fields.proto
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,9 @@ message TestManyFields {
optional int64 field126 = 126;
optional int64 field127 = 127;
optional int64 field128 = 128;
}
}

message TestUnusedFieldIndex {
optional int64 field64 = 64;
optional int64 field65 = 65;
}
8 changes: 8 additions & 0 deletions tests/test_has_field_many.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,11 @@ def test_has_field():

for field in test.Fields():
assert not test.HasField(field)

def test_has_field_withUnusedIndex():
from test_many_fields_proto import TestUnusedFieldIndex
test = TestUnusedFieldIndex()

# Assert HasField false on clean message
for field in test.Fields():
assert not test.HasField(field)
Loading