Skip to content

Commit

Permalink
Merge pull request #88 from boukeversteegh/fix/imports
Browse files Browse the repository at this point in the history
🍏 Fix imports
  • Loading branch information
boukeversteegh authored Jul 4, 2020
2 parents 9532844 + d21cd6e commit cdddb2f
Show file tree
Hide file tree
Showing 41 changed files with 874 additions and 242 deletions.
29 changes: 15 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,46 +68,47 @@ message Greeting {
You can run the following:

```sh
protoc -I . --python_betterproto_out=. example.proto
mkdir lib
protoc -I . --python_betterproto_out=lib example.proto
```

This will generate `hello.py` which looks like:
This will generate `lib/hello/__init__.py` which looks like:

```py
```python
# Generated by the protocol buffer compiler. DO NOT EDIT!
# sources: hello.proto
# sources: example.proto
# plugin: python-betterproto
from dataclasses import dataclass

import betterproto


@dataclass
class Hello(betterproto.Message):
class Greeting(betterproto.Message):
"""Greeting represents a message you can tell a user."""

message: str = betterproto.string_field(1)
```

Now you can use it!

```py
>>> from hello import Hello
>>> test = Hello()
```python
>>> from lib.hello import Greeting
>>> test = Greeting()
>>> test
Hello(message='')
Greeting(message='')

>>> test.message = "Hey!"
>>> test
Hello(message="Hey!")
Greeting(message="Hey!")

>>> serialized = bytes(test)
>>> serialized
b'\n\x04Hey!'

>>> another = Hello().parse(serialized)
>>> another = Greeting().parse(serialized)
>>> another
Hello(message="Hey!")
Greeting(message="Hey!")

>>> another.to_dict()
{"message": "Hey!"}
Expand Down Expand Up @@ -315,7 +316,7 @@ To benefit from the collection of standard development tasks ensure you have mak

This project enforces [black](https://github.com/psf/black) python code formatting.

Before commiting changes run:
Before committing changes run:

```sh
make format
Expand All @@ -336,7 +337,7 @@ Adding a standard test case is easy.

- Create a new directory `betterproto/tests/inputs/<name>`
- add `<name>.proto` with a message called `Test`
- add `<name>.json` with some test data
- add `<name>.json` with some test data (optional)

It will be picked up automatically when you run the tests.

Expand Down
15 changes: 5 additions & 10 deletions betterproto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,22 @@
from abc import ABC
from base64 import b64decode, b64encode
from datetime import datetime, timedelta, timezone
import stringcase
from typing import (
Any,
AsyncGenerator,
Callable,
Collection,
Dict,
Generator,
Iterator,
List,
Mapping,
Optional,
Set,
SupportsBytes,
Tuple,
Type,
Union,
get_type_hints,
)
from ._types import ST, T
from .casing import safe_snake_case

from ._types import T
from .casing import camel_case, safe_snake_case, safe_snake_case, snake_case
from .grpc.grpclib_client import ServiceStub

if not (sys.version_info.major == 3 and sys.version_info.minor >= 7):
Expand Down Expand Up @@ -124,8 +119,8 @@ def datetime_default_gen():
class Casing(enum.Enum):
"""Casing constants for serialization."""

CAMEL = stringcase.camelcase
SNAKE = stringcase.snakecase
CAMEL = camel_case
SNAKE = snake_case


class _PLACEHOLDER:
Expand Down
83 changes: 81 additions & 2 deletions betterproto/casing.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import stringcase
import re

# Word delimiters and symbols that will not be preserved when re-casing.
# language=PythonRegExp
SYMBOLS = "[^a-zA-Z0-9]*"

# Optionally capitalized word.
# language=PythonRegExp
WORD = "[A-Z]*[a-z]*[0-9]*"

# Uppercase word, not followed by lowercase letters.
# language=PythonRegExp
WORD_UPPER = "[A-Z]+(?![a-z])[0-9]*"


def safe_snake_case(value: str) -> str:
"""Snake case a value taking into account Python keywords."""
value = stringcase.snakecase(value)
value = snake_case(value)
if value in [
"and",
"as",
Expand Down Expand Up @@ -39,3 +51,70 @@ def safe_snake_case(value: str) -> str:
# https://www.python.org/dev/peps/pep-0008/#descriptive-naming-styles
value += "_"
return value


def snake_case(value: str, strict: bool = True):
"""
Join words with an underscore into lowercase and remove symbols.
@param value: value to convert
@param strict: force single underscores
"""

def substitute_word(symbols, word, is_start):
if not word:
return ""
if strict:
delimiter_count = 0 if is_start else 1 # Single underscore if strict.
elif is_start:
delimiter_count = len(symbols)
elif word.isupper() or word.islower():
delimiter_count = max(
1, len(symbols)
) # Preserve all delimiters if not strict.
else:
delimiter_count = len(symbols) + 1 # Extra underscore for leading capital.

return ("_" * delimiter_count) + word.lower()

snake = re.sub(
f"(^)?({SYMBOLS})({WORD_UPPER}|{WORD})",
lambda groups: substitute_word(groups[2], groups[3], groups[1] is not None),
value,
)
return snake


def pascal_case(value: str, strict: bool = True):
"""
Capitalize each word and remove symbols.
@param value: value to convert
@param strict: output only alphanumeric characters
"""

def substitute_word(symbols, word):
if strict:
return word.capitalize() # Remove all delimiters

if word.islower():
delimiter_length = len(symbols[:-1]) # Lose one delimiter
else:
delimiter_length = len(symbols) # Preserve all delimiters

return ("_" * delimiter_length) + word.capitalize()

return re.sub(
f"({SYMBOLS})({WORD_UPPER}|{WORD})",
lambda groups: substitute_word(groups[1], groups[2]),
value,
)


def camel_case(value: str, strict: bool = True):
"""
Capitalize all words except first and remove symbols.
"""
return lowercase_first(pascal_case(value, strict=strict))


def lowercase_first(value: str):
return value[0:1].lower() + value[1:]
Loading

0 comments on commit cdddb2f

Please sign in to comment.