Skip to content

Commit 48a500f

Browse files
authored
Merge pull request #45 from google/type-annotation
Start adding type annotations
2 parents 082466b + 5d6f2fb commit 48a500f

6 files changed

Lines changed: 61 additions & 47 deletions

File tree

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
maintainer_email='dbrandao@google.com',
2323
url='https://github.com/google/python-spanner-orm',
2424
packages=['spanner_orm', 'spanner_orm.admin'],
25+
python_requires='~=3.7',
2526
install_requires=['google-cloud-spanner >= 1.6, <2.0.0dev'],
2627
tests_require=['absl-py'],
2728
entry_points={

spanner_orm/condition.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -498,8 +498,8 @@ def equal_to(column, value):
498498
return EqualityCondition(column, value)
499499

500500

501-
def force_index(index):
502-
return ForceIndexCondition(index)
501+
def force_index(forced_index):
502+
return ForceIndexCondition(forced_index)
503503

504504

505505
def greater_than(column, value):

spanner_orm/field.py

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,39 +14,45 @@
1414
# limitations under the License.
1515
"""Helper to deal with field types in Spanner interactions."""
1616

17+
from __future__ import annotations
18+
1719
import abc
1820
import datetime
21+
from typing import Any, Type
1922

2023
from google.cloud.spanner_v1.proto import type_pb2
2124

2225

2326
class Field(object):
2427
"""Represents a column in a table as a field in a model."""
2528

26-
def __init__(self, field_type, nullable=False, primary_key=False):
29+
def __init__(self,
30+
field_type: Type[FieldType],
31+
nullable: bool = False,
32+
primary_key: bool = False):
2733
self._type = field_type
2834
self._nullable = nullable
2935
self._primary_key = primary_key
3036
self.name = None
3137

32-
def ddl(self):
38+
def ddl(self) -> str:
3339
if self._nullable:
3440
return self._type.ddl()
3541
return '{field_type} NOT NULL'.format(field_type=self._type.ddl())
3642

37-
def field_type(self):
43+
def field_type(self) -> Type[FieldType]:
3844
return self._type
3945

40-
def grpc_type(self):
46+
def grpc_type(self) -> str:
4147
return self._type.grpc_type()
4248

43-
def nullable(self):
49+
def nullable(self) -> bool:
4450
return self._nullable
4551

46-
def primary_key(self):
52+
def primary_key(self) -> bool:
4753
return self._primary_key
4854

49-
def validate(self, value):
55+
def validate(self, value) -> None:
5056
if value is None:
5157
assert self._nullable, 'None set for non-nullable field'
5258
else:
@@ -58,81 +64,81 @@ class FieldType(abc.ABC):
5864

5965
@staticmethod
6066
@abc.abstractmethod
61-
def ddl():
67+
def ddl() -> str:
6268
raise NotImplementedError
6369

6470
@staticmethod
6571
@abc.abstractmethod
66-
def grpc_type():
72+
def grpc_type() -> type_pb2.Type:
6773
raise NotImplementedError
6874

6975
@staticmethod
7076
@abc.abstractmethod
71-
def validate_type(value):
77+
def validate_type(value: Any) -> None:
7278
raise NotImplementedError
7379

7480

7581
class Boolean(FieldType):
7682
"""Represents a boolean type."""
7783

7884
@staticmethod
79-
def ddl():
85+
def ddl() -> str:
8086
return 'BOOL'
8187

8288
@staticmethod
83-
def grpc_type():
89+
def grpc_type() -> type_pb2.Type:
8490
return type_pb2.Type(code=type_pb2.BOOL)
8591

8692
@staticmethod
87-
def validate_type(value):
93+
def validate_type(value: Any) -> None:
8894
assert isinstance(value, bool), '{} is not of type bool'.format(value)
8995

9096

9197
class Integer(FieldType):
9298
"""Represents an integer type."""
9399

94100
@staticmethod
95-
def ddl():
101+
def ddl() -> str:
96102
return 'INT64'
97103

98104
@staticmethod
99-
def grpc_type():
105+
def grpc_type() -> type_pb2.Type:
100106
return type_pb2.Type(code=type_pb2.INT64)
101107

102108
@staticmethod
103-
def validate_type(value):
109+
def validate_type(value: Any) -> None:
104110
assert isinstance(value, int), '{} is not of type int'.format(value)
105111

106112

107113
class String(FieldType):
108114
"""Represents a string type."""
109115

110116
@staticmethod
111-
def ddl():
117+
def ddl() -> str:
112118
return 'STRING(MAX)'
113119

114120
@staticmethod
115-
def grpc_type():
121+
def grpc_type() -> type_pb2.Type:
116122
return type_pb2.Type(code=type_pb2.STRING)
117123

118124
@staticmethod
119-
def validate_type(value):
125+
def validate_type(value) -> None:
120126
assert isinstance(value, str), '{} is not of type str'.format(value)
121127

122128

123129
class StringArray(FieldType):
124130
"""Represents an array of strings type."""
125131

126132
@staticmethod
127-
def ddl():
133+
def ddl() -> str:
128134
return 'ARRAY<STRING(MAX)>'
129135

130136
@staticmethod
131-
def grpc_type():
137+
def grpc_type() -> type_pb2.Type:
132138
return type_pb2.Type(code=type_pb2.ARRAY)
133139

134140
@staticmethod
135-
def validate_type(value):
141+
def validate_type(value: Any) -> None:
136142
assert isinstance(value, list), '{} is not of type list'.format(value)
137143
for item in value:
138144
assert isinstance(item, str), '{} is not of type str'.format(item)
@@ -142,15 +148,15 @@ class Timestamp(FieldType):
142148
"""Represents a timestamp type."""
143149

144150
@staticmethod
145-
def ddl():
151+
def ddl() -> str:
146152
return 'TIMESTAMP'
147153

148154
@staticmethod
149-
def grpc_type():
155+
def grpc_type() -> type_pb2.Type:
150156
return type_pb2.Type(code=type_pb2.TIMESTAMP)
151157

152158
@staticmethod
153-
def validate_type(value):
159+
def validate_type(value: Any) -> None:
154160
assert isinstance(value, datetime.datetime)
155161

156162

spanner_orm/index.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,21 @@
1212
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
15-
"""Helps to deal with indices on Models."""
15+
"""Represents an index on a Model."""
16+
17+
from typing import List, Optional
1618

1719

1820
class Index(object):
21+
"""Represents an index on a Model."""
1922
PRIMARY_INDEX = 'PRIMARY_KEY'
2023

2124
def __init__(self,
22-
columns,
23-
parent=None,
24-
null_filtered=False,
25-
unique=False,
26-
storing_columns=None):
25+
columns: List[str],
26+
parent: Optional[str] = None,
27+
null_filtered: bool = False,
28+
unique: bool = False,
29+
storing_columns: Optional[List[str]] = None):
2730
assert columns, 'An index must have at least one column'
2831
self.columns = columns
2932
self.name = None
@@ -33,5 +36,5 @@ def __init__(self,
3336
self.storing_columns = storing_columns or []
3437

3538
@property
36-
def primary(self):
39+
def primary(self) -> bool:
3740
return self.name == self.PRIMARY_INDEX

spanner_orm/relationship.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
# limitations under the License.
1515
"""Helps define a foreign key relationship between two models."""
1616

17+
from __future__ import annotations
18+
19+
from typing import Dict, List, Type, Union
20+
1721
from spanner_orm import condition
1822
from spanner_orm import error
1923
from spanner_orm import model
@@ -23,10 +27,10 @@ class Relationship(object):
2327
"""Helps define a foreign key relationship between two models."""
2428

2529
def __init__(self,
26-
destination_handle,
27-
constraints,
28-
is_parent=False,
29-
single=False):
30+
destination_handle: Union[Type[model.Model], str],
31+
constraints: Dict[str, str],
32+
is_parent: bool = False,
33+
single: bool = False):
3034
"""Creates a ModelRelationship.
3135
3236
Args:
@@ -48,21 +52,21 @@ def __init__(self,
4852
self.origin = None
4953

5054
@property
51-
def conditions(self):
55+
def conditions(self) -> List[condition.Condition]:
5256
assert self.origin, 'Origin must be set before conditions is called'
5357
return self._parse_constraints()
5458

5559
@property
56-
def destination(self):
60+
def destination(self) -> Type[model.Model]:
5761
if not self._destination:
5862
self._destination = model.load_model(self._destination_handle)
5963
return self._destination
6064

6165
@property
62-
def single(self):
66+
def single(self) -> bool:
6367
return self._single
6468

65-
def _parse_constraints(self):
69+
def _parse_constraints(self) -> List[condition.Condition]:
6670
"""Validates the dictionary of constraints and turns it into Conditions."""
6771
conditions = []
6872
for origin_column, destination_column in self._constraints.items():

spanner_orm/tests/query_test.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,14 +209,14 @@ def test_includes(self):
209209
self.assertEmpty(select_query.types())
210210

211211
def test_includes_with_object(self):
212-
select_query = self.includes(models.ChildTestModel.parent)
212+
select_query = self.includes(models.RelationshipTestModel.parent)
213213

214214
# The column order varies between test runs
215215
expected_sql = (
216-
r'SELECT ChildTestModel\S* ChildTestModel\S* ARRAY\(SELECT AS '
217-
r'STRUCT SmallTestModel\S* SmallTestModel\S* SmallTestModel\S* FROM '
218-
r'SmallTestModel WHERE SmallTestModel.key = '
219-
r'ChildTestModel.parent_key\)')
216+
r'SELECT RelationshipTestModel\S* RelationshipTestModel\S* '
217+
r'ARRAY\(SELECT AS STRUCT SmallTestModel\S* SmallTestModel\S* '
218+
r'SmallTestModel\S* FROM SmallTestModel WHERE SmallTestModel.key = '
219+
r'RelationshipTestModel.parent_key\)')
220220
self.assertRegex(select_query.sql(), expected_sql)
221221
self.assertEmpty(select_query.parameters())
222222
self.assertEmpty(select_query.types())

0 commit comments

Comments
 (0)