Skip to content

Commit cdddb2f

Browse files
Merge pull request #88 from boukeversteegh/fix/imports
🍏 Fix imports
2 parents 9532844 + d21cd6e commit cdddb2f

File tree

41 files changed

+874
-242
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+874
-242
lines changed

README.md

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,46 +68,47 @@ message Greeting {
6868
You can run the following:
6969

7070
```sh
71-
protoc -I . --python_betterproto_out=. example.proto
71+
mkdir lib
72+
protoc -I . --python_betterproto_out=lib example.proto
7273
```
7374

74-
This will generate `hello.py` which looks like:
75+
This will generate `lib/hello/__init__.py` which looks like:
7576

76-
```py
77+
```python
7778
# Generated by the protocol buffer compiler. DO NOT EDIT!
78-
# sources: hello.proto
79+
# sources: example.proto
7980
# plugin: python-betterproto
8081
from dataclasses import dataclass
8182

8283
import betterproto
8384

8485

8586
@dataclass
86-
class Hello(betterproto.Message):
87+
class Greeting(betterproto.Message):
8788
"""Greeting represents a message you can tell a user."""
8889

8990
message: str = betterproto.string_field(1)
9091
```
9192

9293
Now you can use it!
9394

94-
```py
95-
>>> from hello import Hello
96-
>>> test = Hello()
95+
```python
96+
>>> from lib.hello import Greeting
97+
>>> test = Greeting()
9798
>>> test
98-
Hello(message='')
99+
Greeting(message='')
99100

100101
>>> test.message = "Hey!"
101102
>>> test
102-
Hello(message="Hey!")
103+
Greeting(message="Hey!")
103104

104105
>>> serialized = bytes(test)
105106
>>> serialized
106107
b'\n\x04Hey!'
107108

108-
>>> another = Hello().parse(serialized)
109+
>>> another = Greeting().parse(serialized)
109110
>>> another
110-
Hello(message="Hey!")
111+
Greeting(message="Hey!")
111112

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

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

318-
Before commiting changes run:
319+
Before committing changes run:
319320

320321
```sh
321322
make format
@@ -336,7 +337,7 @@ Adding a standard test case is easy.
336337

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

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

betterproto/__init__.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,22 @@
77
from abc import ABC
88
from base64 import b64decode, b64encode
99
from datetime import datetime, timedelta, timezone
10-
import stringcase
1110
from typing import (
1211
Any,
13-
AsyncGenerator,
1412
Callable,
15-
Collection,
1613
Dict,
1714
Generator,
18-
Iterator,
1915
List,
20-
Mapping,
2116
Optional,
2217
Set,
23-
SupportsBytes,
2418
Tuple,
2519
Type,
2620
Union,
2721
get_type_hints,
2822
)
29-
from ._types import ST, T
30-
from .casing import safe_snake_case
23+
24+
from ._types import T
25+
from .casing import camel_case, safe_snake_case, safe_snake_case, snake_case
3126
from .grpc.grpclib_client import ServiceStub
3227

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

127-
CAMEL = stringcase.camelcase
128-
SNAKE = stringcase.snakecase
122+
CAMEL = camel_case
123+
SNAKE = snake_case
129124

130125

131126
class _PLACEHOLDER:

betterproto/casing.py

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
1-
import stringcase
1+
import re
2+
3+
# Word delimiters and symbols that will not be preserved when re-casing.
4+
# language=PythonRegExp
5+
SYMBOLS = "[^a-zA-Z0-9]*"
6+
7+
# Optionally capitalized word.
8+
# language=PythonRegExp
9+
WORD = "[A-Z]*[a-z]*[0-9]*"
10+
11+
# Uppercase word, not followed by lowercase letters.
12+
# language=PythonRegExp
13+
WORD_UPPER = "[A-Z]+(?![a-z])[0-9]*"
214

315

416
def safe_snake_case(value: str) -> str:
517
"""Snake case a value taking into account Python keywords."""
6-
value = stringcase.snakecase(value)
18+
value = snake_case(value)
719
if value in [
820
"and",
921
"as",
@@ -39,3 +51,70 @@ def safe_snake_case(value: str) -> str:
3951
# https://www.python.org/dev/peps/pep-0008/#descriptive-naming-styles
4052
value += "_"
4153
return value
54+
55+
56+
def snake_case(value: str, strict: bool = True):
57+
"""
58+
Join words with an underscore into lowercase and remove symbols.
59+
@param value: value to convert
60+
@param strict: force single underscores
61+
"""
62+
63+
def substitute_word(symbols, word, is_start):
64+
if not word:
65+
return ""
66+
if strict:
67+
delimiter_count = 0 if is_start else 1 # Single underscore if strict.
68+
elif is_start:
69+
delimiter_count = len(symbols)
70+
elif word.isupper() or word.islower():
71+
delimiter_count = max(
72+
1, len(symbols)
73+
) # Preserve all delimiters if not strict.
74+
else:
75+
delimiter_count = len(symbols) + 1 # Extra underscore for leading capital.
76+
77+
return ("_" * delimiter_count) + word.lower()
78+
79+
snake = re.sub(
80+
f"(^)?({SYMBOLS})({WORD_UPPER}|{WORD})",
81+
lambda groups: substitute_word(groups[2], groups[3], groups[1] is not None),
82+
value,
83+
)
84+
return snake
85+
86+
87+
def pascal_case(value: str, strict: bool = True):
88+
"""
89+
Capitalize each word and remove symbols.
90+
@param value: value to convert
91+
@param strict: output only alphanumeric characters
92+
"""
93+
94+
def substitute_word(symbols, word):
95+
if strict:
96+
return word.capitalize() # Remove all delimiters
97+
98+
if word.islower():
99+
delimiter_length = len(symbols[:-1]) # Lose one delimiter
100+
else:
101+
delimiter_length = len(symbols) # Preserve all delimiters
102+
103+
return ("_" * delimiter_length) + word.capitalize()
104+
105+
return re.sub(
106+
f"({SYMBOLS})({WORD_UPPER}|{WORD})",
107+
lambda groups: substitute_word(groups[1], groups[2]),
108+
value,
109+
)
110+
111+
112+
def camel_case(value: str, strict: bool = True):
113+
"""
114+
Capitalize all words except first and remove symbols.
115+
"""
116+
return lowercase_first(pascal_case(value, strict=strict))
117+
118+
119+
def lowercase_first(value: str):
120+
return value[0:1].lower() + value[1:]

0 commit comments

Comments
 (0)