Skip to content

Commit 07b2d60

Browse files
authored
Merge pull request #60 from eseglem/develop
Update to Pydantic v2. Fixes: #40
2 parents 64bfef7 + 240aeab commit 07b2d60

File tree

13 files changed

+432
-275
lines changed

13 files changed

+432
-275
lines changed

.github/workflows/tests.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
runs-on: ubuntu-latest
1313
strategy:
1414
matrix:
15-
python-version: ["3.8", "3.9", "3.10", "3.11"]
15+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
1616

1717
steps:
1818
- uses: actions/checkout@v4
@@ -36,10 +36,10 @@ jobs:
3636
- uses: actions/checkout@v4
3737
- name: Install poetry
3838
run: pipx install poetry
39-
- name: Set up Python 3.11
39+
- name: Set up Python 3.12
4040
uses: actions/setup-python@v5
4141
with:
42-
python-version: 3.11
42+
python-version: 3.12
4343
cache: "poetry"
4444
- name: Install dependencies
4545
run: poetry install --no-dev

.pre-commit-config.yaml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ repos:
1818
- id: pyupgrade
1919
args: ["--py38-plus", "--keep-runtime-typing"]
2020
- repo: https://github.com/astral-sh/ruff-pre-commit
21-
rev: v0.1.9
21+
rev: v0.1.11
2222
hooks:
2323
- id: ruff
2424
args: ["--fix"]
@@ -27,10 +27,6 @@ repos:
2727
hooks:
2828
- id: black
2929
language_version: python
30-
- repo: https://github.com/PyCQA/isort
31-
rev: 5.13.2
32-
hooks:
33-
- id: isort
3430
- repo: https://github.com/pre-commit/mirrors-mypy
3531
rev: v1.8.0
3632
hooks:

poetry.lock

Lines changed: 208 additions & 122 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pycql2/cql2.lark

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ start: boolean_expression
4646
is_like_predicate: character_expression _LIKE pattern_expression -> like
4747
| character_expression _NOT _LIKE pattern_expression -> not_like
4848

49-
pattern_expression: "CASEI"i "(" pattern_expression ")" -> casei
50-
| "ACCENTI"i "(" pattern_expression ")" -> accenti
51-
| CHARACTER_LITERAL
49+
?pattern_expression: "CASEI"i "(" pattern_expression ")" -> casei_pattern
50+
| "ACCENTI"i "(" pattern_expression ")" -> accenti_pattern
51+
| CHARACTER_LITERAL
5252

5353
// Between Predicate
5454
is_between_predicate: numeric_expression _BETWEEN numeric_expression _AND numeric_expression -> is_between
@@ -205,11 +205,11 @@ _argument: character_clause
205205
| property_name
206206
| function
207207

208-
character_expression: character_clause
208+
?character_expression: character_clause
209209
| property_name
210210
| function
211-
?character_clause: "CASEI"i "(" character_expression ")" -> casei
212-
| "ACCENTI"i "(" character_expression ")" -> accenti
211+
?character_clause: "CASEI"i "(" character_expression ")" -> casei_character
212+
| "ACCENTI"i "(" character_expression ")" -> accenti_character
213213
| CHARACTER_LITERAL
214214

215215
// WKT Definitions
@@ -249,7 +249,7 @@ linestring_coordinates: "(" coordinate ("," coordinate)+ ")"
249249
// BNF does not include linear ring, but you cannot have a polygon without 4 points.
250250
linear_ring_coordinates: "(" coordinate "," coordinate "," coordinate ("," coordinate)+ ")"
251251
polygon_coordinates: "(" linear_ring_coordinates ("," linear_ring_coordinates)* ")"
252-
multi_point_coordinates: "(" coordinate ("," coordinate)* ")"
252+
multi_point_coordinates: "(" point_coordinates ("," point_coordinates)* ")"
253253
multi_linestring_coordinates: "(" linestring_coordinates ("," linestring_coordinates)* ")"
254254
multi_polygon_coordinates: "(" polygon_coordinates ("," polygon_coordinates)* ")"
255255

pycql2/cql2_pydantic.py

Lines changed: 104 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,27 @@
44
from enum import Enum
55
from typing import (
66
TYPE_CHECKING,
7-
Generic,
7+
Any,
88
List,
99
Literal,
10+
MutableSequence,
1011
Sequence,
1112
Tuple,
12-
TypeVar,
1313
Union,
1414
)
1515

1616
from geojson_pydantic.geometries import Geometry, GeometryCollection
1717
from geojson_pydantic.types import BBox
18-
from pydantic import BaseModel, StrictBool, StrictFloat, StrictInt, StrictStr, conlist
19-
from pydantic.generics import GenericModel
18+
from pydantic import (
19+
BaseModel,
20+
Field,
21+
RootModel,
22+
StrictBool,
23+
StrictFloat,
24+
StrictInt,
25+
StrictStr,
26+
)
27+
from typing_extensions import Annotated
2028

2129
# The use of `Strict*` is necessary in a few places because pydantic will convert
2230
# booleans to numbers and vice versa. As well as various types to strings. This
@@ -130,18 +138,18 @@ def __str__(self) -> str:
130138
return f"{self.op.upper()}({self.args[0]}, {self.args[1]})"
131139

132140

133-
class Array(BaseModel):
134-
__root__: List[ArrayElement]
141+
class Array(RootModel):
142+
root: MutableSequence[ArrayElement]
135143

136144
def __str__(self) -> str:
137-
return f"({join_list(self.__root__, ', ')})"
145+
return f"({join_list(self.root, ', ')})"
138146

139147

140-
class ArrayExpression(BaseModel):
141-
__root__: Tuple[ArrayExpressionItems, ArrayExpressionItems]
148+
class ArrayExpression(RootModel):
149+
root: Tuple[ArrayExpressionItems, ArrayExpressionItems]
142150

143151
def __str__(self) -> str:
144-
return f"({self.__root__[0]}, {self.__root__[1]})"
152+
return f"({self.root[0]}, {self.root[1]})"
145153

146154

147155
class ArrayOperator(str, Enum):
@@ -159,15 +167,15 @@ def __str__(self) -> str:
159167
return f"{self.op.upper()}{self.args}"
160168

161169

162-
class BooleanExpression(BaseModel):
163-
__root__: BooleanExpressionItems
170+
class BooleanExpression(RootModel):
171+
root: BooleanExpressionItems
164172

165173
def __str__(self) -> str:
166174
# If it's a bool, we uppercase it.
167-
if isinstance(self.__root__, bool):
168-
return str(self.__root__).upper()
175+
if isinstance(self.root, bool):
176+
return str(self.root).upper()
169177
# Otherwise, we return the string representation of the root.
170-
return str(self.__root__)
178+
return str(self.root)
171179

172180

173181
# Type checking does not like `conlist`, so use a conditional here to avoid the error.
@@ -176,7 +184,7 @@ def __str__(self) -> str:
176184
if TYPE_CHECKING:
177185
BooleanExpressionList = List[BooleanExpression]
178186
else:
179-
BooleanExpressionList = conlist(item_type=BooleanExpression, min_items=2)
187+
BooleanExpressionList = Annotated[List[BooleanExpression], Field(min_length=2)]
180188

181189

182190
class AndOrExpression(BaseModel):
@@ -245,21 +253,21 @@ def __str__(self) -> str:
245253
return f"BBOX{self.bbox}"
246254

247255

248-
class IntervalArrayItems(BaseModel):
249-
__root__: Union[datetime, date, Literal[".."], PropertyRef, FunctionRef]
256+
class IntervalArrayItems(RootModel):
257+
root: Union[datetime, date, Literal[".."], PropertyRef, FunctionRef]
250258

251259
def __str__(self) -> str:
252260
# If it is a datetime, format it as iso with `Z` at the end. Note this will
253261
# always include the microseconds, even if they are 0.
254-
if isinstance(self.__root__, datetime):
255-
return f"""'{self.__root__.strftime("%Y-%m-%dT%H:%M:%S.%fZ")}'"""
262+
if isinstance(self.root, datetime):
263+
return f"""'{self.root.strftime("%Y-%m-%dT%H:%M:%S.%fZ")}'"""
256264
# If it is a date, format with built-in isoformat
257-
if isinstance(self.__root__, date):
258-
return f"'{self.__root__.isoformat()}'"
259-
if self.__root__ == "..":
265+
if isinstance(self.root, date):
266+
return f"'{self.root.isoformat()}'"
267+
if self.root == "..":
260268
return "'..'"
261269
# Otherwise use the string representation of the root.
262-
return str(self.__root__)
270+
return str(self.root)
263271

264272

265273
class IntervalInstance(BaseModel):
@@ -269,50 +277,60 @@ def __str__(self) -> str:
269277
return f"INTERVAL({self.interval[0]}, {self.interval[1]})"
270278

271279

272-
class CharacterExpression(BaseModel):
273-
__root__: CharacterExpressionItems
280+
class Casei(BaseModel):
281+
casei: Union[CharacterExpression, PatternExpression]
274282

275283
def __str__(self) -> str:
276-
# If it is already a string, make it a char literal
277-
if isinstance(self.__root__, str):
278-
return make_char_literal(self.__root__)
279-
# Otherwise, return the string representation of the root
280-
return str(self.__root__)
284+
return f"CASEI({self.casei})"
281285

282286

283-
class PatternExpression(BaseModel):
284-
__root__: PatternExpressionItems
287+
class CaseiCharacterExpression(Casei):
288+
casei: CharacterExpression
289+
290+
291+
class CaseiPatternExpression(Casei):
292+
casei: PatternExpression
293+
294+
295+
class Accenti(BaseModel):
296+
accenti: Union[CharacterExpression, PatternExpression]
285297

286298
def __str__(self) -> str:
287-
# If it is already a string, make it a char literal
288-
if isinstance(self.__root__, str):
289-
return make_char_literal(self.__root__)
290-
# Otherwise, return the string representation of the root
291-
return str(self.__root__)
299+
return f"ACCENTI({self.accenti})"
300+
292301

302+
class AccentiCharacterExpression(Accenti):
303+
accenti: CharacterExpression
293304

294-
E = TypeVar("E", bound=Union[CharacterExpression, PatternExpression])
295305

306+
class AccentiPatternExpression(Accenti):
307+
accenti: PatternExpression
296308

297-
class Casei(GenericModel, Generic[E]):
298-
casei: E
309+
310+
class GeometryLiteral(RootModel):
311+
root: Union[Geometry, GeometryCollection]
299312

300313
def __str__(self) -> str:
301-
return f"CASEI({self.casei})"
314+
return self.root.wkt
302315

303316

304-
class Accenti(GenericModel, Generic[E]):
305-
accenti: E
317+
class CharLiteralRootModel(RootModel):
318+
root: Any
306319

307320
def __str__(self) -> str:
308-
return f"ACCENTI({self.accenti})"
321+
# If it is already a string, make it a char literal
322+
if isinstance(self.root, str):
323+
return make_char_literal(self.root)
324+
# Otherwise, return the string representation of the root
325+
return str(self.root)
309326

310327

311-
class GeometryLiteral(BaseModel):
312-
__root__: Union[Geometry, GeometryCollection]
328+
class CharacterClause(CharLiteralRootModel):
329+
root: CharacterClauseItems
313330

314-
def __str__(self) -> str:
315-
return self.__root__.wkt
331+
332+
class PatternExpression(CharLiteralRootModel):
333+
root: PatternExpressionItems
316334

317335

318336
ComparisonPredicate = Union[
@@ -332,33 +350,36 @@ def __str__(self) -> str:
332350
TemporalExpression = Union[TemporalInstance, PropertyRef, FunctionRef]
333351
TemporalInstantExpression = Union[InstantInstance, PropertyRef, FunctionRef]
334352
ScalarExpression = Union[
335-
TemporalInstantExpression, BooleanExpression, CharacterExpression, NumericExpression
353+
TemporalInstantExpression, BooleanExpression, CharacterClause, NumericExpression
336354
]
337355
ArithmeticOperandsItems = Union[
338356
ArithmeticExpression, PropertyRef, FunctionRef, StrictFloatOrInt
339357
]
340358
ArrayExpressionItems = Union[Array, PropertyRef, FunctionRef]
341359
PatternExpressionItems = Union[
342-
Casei[PatternExpression], Accenti[PatternExpression], StrictStr
360+
CaseiPatternExpression, AccentiPatternExpression, StrictStr
343361
]
344-
CharacterExpressionItems = Union[
345-
Casei[CharacterExpression],
346-
Accenti[CharacterExpression],
362+
CharacterClauseItems = Union[
363+
CaseiCharacterExpression,
364+
AccentiCharacterExpression,
347365
StrictStr,
366+
]
367+
CharacterExpression = Union[
368+
CharacterClause,
348369
PropertyRef,
349370
FunctionRef,
350371
]
351372

352373
# Extra types to match the cql2-text grammar better
353374
IsNullOperand = Union[
354-
CharacterExpression,
375+
CharacterClause,
355376
NumericExpression,
356377
TemporalExpression,
357378
BooleanExpression,
358379
GeomExpression,
359380
]
360381
ArrayElement = Union[
361-
CharacterExpression,
382+
CharacterClause,
362383
NumericExpression,
363384
BooleanExpression,
364385
GeomExpression,
@@ -369,7 +390,7 @@ def __str__(self) -> str:
369390
None,
370391
List[
371392
Union[
372-
CharacterExpression,
393+
CharacterClause,
373394
NumericExpression,
374395
BooleanExpression,
375396
GeomExpression,
@@ -389,28 +410,29 @@ def __str__(self) -> str:
389410
]
390411

391412
# Update all the forward references
392-
Accenti.update_forward_refs()
393-
AndOrExpression.update_forward_refs()
394-
ArithmeticExpression.update_forward_refs()
395-
ArrayExpression.update_forward_refs()
396-
Array.update_forward_refs()
397-
ArrayPredicate.update_forward_refs()
398-
BboxLiteral.update_forward_refs()
399-
BinaryComparisonPredicate.update_forward_refs()
400-
BooleanExpression.update_forward_refs()
401-
Casei.update_forward_refs()
402-
CharacterExpression.update_forward_refs()
403-
DateInstant.update_forward_refs()
404-
Function.update_forward_refs()
405-
FunctionRef.update_forward_refs()
406-
IntervalInstance.update_forward_refs()
407-
IsBetweenPredicate.update_forward_refs()
408-
IsInListPredicate.update_forward_refs()
409-
IsLikePredicate.update_forward_refs()
410-
IsNullPredicate.update_forward_refs()
411-
NotExpression.update_forward_refs()
412-
PatternExpression.update_forward_refs()
413-
PropertyRef.update_forward_refs()
414-
SpatialPredicate.update_forward_refs()
415-
TemporalPredicate.update_forward_refs()
416-
TimestampInstant.update_forward_refs()
413+
AccentiCharacterExpression.model_rebuild()
414+
AccentiPatternExpression.model_rebuild()
415+
AndOrExpression.model_rebuild()
416+
ArithmeticExpression.model_rebuild()
417+
ArrayExpression.model_rebuild()
418+
ArrayPredicate.model_rebuild()
419+
BboxLiteral.model_rebuild()
420+
BinaryComparisonPredicate.model_rebuild()
421+
BooleanExpression.model_rebuild()
422+
CaseiCharacterExpression.model_rebuild()
423+
CaseiPatternExpression.model_rebuild()
424+
CharacterClause.model_rebuild()
425+
DateInstant.model_rebuild()
426+
Function.model_rebuild()
427+
FunctionRef.model_rebuild()
428+
IntervalInstance.model_rebuild()
429+
IsBetweenPredicate.model_rebuild()
430+
IsInListPredicate.model_rebuild()
431+
IsLikePredicate.model_rebuild()
432+
IsNullPredicate.model_rebuild()
433+
NotExpression.model_rebuild()
434+
PatternExpression.model_rebuild()
435+
PropertyRef.model_rebuild()
436+
SpatialPredicate.model_rebuild()
437+
TemporalPredicate.model_rebuild()
438+
TimestampInstant.model_rebuild()

0 commit comments

Comments
 (0)