Skip to content

Commit 9f78406

Browse files
Merge pull request #5 from CASParser/release-please--branches--main--changes--next
release: 1.1.0
2 parents 098b75b + 4f226d1 commit 9f78406

21 files changed

+565
-236
lines changed

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "1.0.2"
2+
".": "1.1.0"
33
}

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
## 1.1.0 (2025-09-06)
4+
5+
Full Changelog: [v1.0.2...v1.1.0](https://github.com/CASParser/cas-parser-python/compare/v1.0.2...v1.1.0)
6+
7+
### Features
8+
9+
* improve future compat with pydantic v3 ([39d5f4a](https://github.com/CASParser/cas-parser-python/commit/39d5f4a6f631f01627f9e0ad3846431b2f36410d))
10+
* **types:** replace List[str] with SequenceNotStr in params ([ffdac79](https://github.com/CASParser/cas-parser-python/commit/ffdac79d2ac8394afc09f540c6b89c01675946d9))
11+
12+
13+
### Chores
14+
15+
* **internal:** add Sequence related utils ([cffd684](https://github.com/CASParser/cas-parser-python/commit/cffd6844655dfe7f52a1c85140dc4ab2a370e8f7))
16+
* **internal:** move mypy configurations to `pyproject.toml` file ([2f7e6bc](https://github.com/CASParser/cas-parser-python/commit/2f7e6bccf82f65c0b753a909654ba1ee7cc61157))
17+
* **tests:** simplify `get_platform` test ([7c54665](https://github.com/CASParser/cas-parser-python/commit/7c546655385360b8ba97e20e79fbdedb43af07d5))
18+
319
## 1.0.2 (2025-08-27)
420

521
Full Changelog: [v1.0.1...v1.0.2](https://github.com/CASParser/cas-parser-python/compare/v1.0.1...v1.0.2)

mypy.ini

Lines changed: 0 additions & 50 deletions
This file was deleted.

pyproject.toml

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "cas-parser-python"
3-
version = "1.0.2"
3+
version = "1.1.0"
44
description = "The official Python library for the CAS Parser API"
55
dynamic = ["readme"]
66
license = "Apache-2.0"
@@ -56,7 +56,6 @@ dev-dependencies = [
5656
"dirty-equals>=0.6.0",
5757
"importlib-metadata>=6.7.0",
5858
"rich>=13.7.1",
59-
"nest_asyncio==1.6.0",
6059
"pytest-xdist>=3.6.1",
6160
]
6261

@@ -157,6 +156,58 @@ reportOverlappingOverload = false
157156
reportImportCycles = false
158157
reportPrivateUsage = false
159158

159+
[tool.mypy]
160+
pretty = true
161+
show_error_codes = true
162+
163+
# Exclude _files.py because mypy isn't smart enough to apply
164+
# the correct type narrowing and as this is an internal module
165+
# it's fine to just use Pyright.
166+
#
167+
# We also exclude our `tests` as mypy doesn't always infer
168+
# types correctly and Pyright will still catch any type errors.
169+
exclude = ['src/cas_parser/_files.py', '_dev/.*.py', 'tests/.*']
170+
171+
strict_equality = true
172+
implicit_reexport = true
173+
check_untyped_defs = true
174+
no_implicit_optional = true
175+
176+
warn_return_any = true
177+
warn_unreachable = true
178+
warn_unused_configs = true
179+
180+
# Turn these options off as it could cause conflicts
181+
# with the Pyright options.
182+
warn_unused_ignores = false
183+
warn_redundant_casts = false
184+
185+
disallow_any_generics = true
186+
disallow_untyped_defs = true
187+
disallow_untyped_calls = true
188+
disallow_subclassing_any = true
189+
disallow_incomplete_defs = true
190+
disallow_untyped_decorators = true
191+
cache_fine_grained = true
192+
193+
# By default, mypy reports an error if you assign a value to the result
194+
# of a function call that doesn't return anything. We do this in our test
195+
# cases:
196+
# ```
197+
# result = ...
198+
# assert result is None
199+
# ```
200+
# Changing this codegen to make mypy happy would increase complexity
201+
# and would not be worth it.
202+
disable_error_code = "func-returns-value,overload-cannot-match"
203+
204+
# https://github.com/python/mypy/issues/12162
205+
[[tool.mypy.overrides]]
206+
module = "black.files.*"
207+
ignore_errors = true
208+
ignore_missing_imports = true
209+
210+
160211
[tool.ruff]
161212
line-length = 120
162213
output-format = "grouped"

requirements-dev.lock

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ multidict==6.4.4
7575
mypy==1.14.1
7676
mypy-extensions==1.0.0
7777
# via mypy
78-
nest-asyncio==1.6.0
7978
nodeenv==1.8.0
8079
# via pyright
8180
nox==2023.4.22

src/cas_parser/_base_client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
ModelBuilderProtocol,
6060
)
6161
from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping
62-
from ._compat import PYDANTIC_V2, model_copy, model_dump
62+
from ._compat import PYDANTIC_V1, model_copy, model_dump
6363
from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type
6464
from ._response import (
6565
APIResponse,
@@ -232,7 +232,7 @@ def _set_private_attributes(
232232
model: Type[_T],
233233
options: FinalRequestOptions,
234234
) -> None:
235-
if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None:
235+
if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None:
236236
self.__pydantic_private__ = {}
237237

238238
self._model = model
@@ -320,7 +320,7 @@ def _set_private_attributes(
320320
client: AsyncAPIClient,
321321
options: FinalRequestOptions,
322322
) -> None:
323-
if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None:
323+
if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None:
324324
self.__pydantic_private__ = {}
325325

326326
self._model = model

src/cas_parser/_compat.py

Lines changed: 48 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@
1212
_T = TypeVar("_T")
1313
_ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel)
1414

15-
# --------------- Pydantic v2 compatibility ---------------
15+
# --------------- Pydantic v2, v3 compatibility ---------------
1616

1717
# Pyright incorrectly reports some of our functions as overriding a method when they don't
1818
# pyright: reportIncompatibleMethodOverride=false
1919

20-
PYDANTIC_V2 = pydantic.VERSION.startswith("2.")
20+
PYDANTIC_V1 = pydantic.VERSION.startswith("1.")
2121

22-
# v1 re-exports
2322
if TYPE_CHECKING:
2423

2524
def parse_date(value: date | StrBytesIntFloat) -> date: # noqa: ARG001
@@ -44,90 +43,92 @@ def is_typeddict(type_: type[Any]) -> bool: # noqa: ARG001
4443
...
4544

4645
else:
47-
if PYDANTIC_V2:
48-
from pydantic.v1.typing import (
46+
# v1 re-exports
47+
if PYDANTIC_V1:
48+
from pydantic.typing import (
4949
get_args as get_args,
5050
is_union as is_union,
5151
get_origin as get_origin,
5252
is_typeddict as is_typeddict,
5353
is_literal_type as is_literal_type,
5454
)
55-
from pydantic.v1.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime
55+
from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime
5656
else:
57-
from pydantic.typing import (
57+
from ._utils import (
5858
get_args as get_args,
5959
is_union as is_union,
6060
get_origin as get_origin,
61+
parse_date as parse_date,
6162
is_typeddict as is_typeddict,
63+
parse_datetime as parse_datetime,
6264
is_literal_type as is_literal_type,
6365
)
64-
from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime
6566

6667

6768
# refactored config
6869
if TYPE_CHECKING:
6970
from pydantic import ConfigDict as ConfigDict
7071
else:
71-
if PYDANTIC_V2:
72-
from pydantic import ConfigDict
73-
else:
72+
if PYDANTIC_V1:
7473
# TODO: provide an error message here?
7574
ConfigDict = None
75+
else:
76+
from pydantic import ConfigDict as ConfigDict
7677

7778

7879
# renamed methods / properties
7980
def parse_obj(model: type[_ModelT], value: object) -> _ModelT:
80-
if PYDANTIC_V2:
81-
return model.model_validate(value)
82-
else:
81+
if PYDANTIC_V1:
8382
return cast(_ModelT, model.parse_obj(value)) # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
83+
else:
84+
return model.model_validate(value)
8485

8586

8687
def field_is_required(field: FieldInfo) -> bool:
87-
if PYDANTIC_V2:
88-
return field.is_required()
89-
return field.required # type: ignore
88+
if PYDANTIC_V1:
89+
return field.required # type: ignore
90+
return field.is_required()
9091

9192

9293
def field_get_default(field: FieldInfo) -> Any:
9394
value = field.get_default()
94-
if PYDANTIC_V2:
95-
from pydantic_core import PydanticUndefined
96-
97-
if value == PydanticUndefined:
98-
return None
95+
if PYDANTIC_V1:
9996
return value
97+
from pydantic_core import PydanticUndefined
98+
99+
if value == PydanticUndefined:
100+
return None
100101
return value
101102

102103

103104
def field_outer_type(field: FieldInfo) -> Any:
104-
if PYDANTIC_V2:
105-
return field.annotation
106-
return field.outer_type_ # type: ignore
105+
if PYDANTIC_V1:
106+
return field.outer_type_ # type: ignore
107+
return field.annotation
107108

108109

109110
def get_model_config(model: type[pydantic.BaseModel]) -> Any:
110-
if PYDANTIC_V2:
111-
return model.model_config
112-
return model.__config__ # type: ignore
111+
if PYDANTIC_V1:
112+
return model.__config__ # type: ignore
113+
return model.model_config
113114

114115

115116
def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]:
116-
if PYDANTIC_V2:
117-
return model.model_fields
118-
return model.__fields__ # type: ignore
117+
if PYDANTIC_V1:
118+
return model.__fields__ # type: ignore
119+
return model.model_fields
119120

120121

121122
def model_copy(model: _ModelT, *, deep: bool = False) -> _ModelT:
122-
if PYDANTIC_V2:
123-
return model.model_copy(deep=deep)
124-
return model.copy(deep=deep) # type: ignore
123+
if PYDANTIC_V1:
124+
return model.copy(deep=deep) # type: ignore
125+
return model.model_copy(deep=deep)
125126

126127

127128
def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str:
128-
if PYDANTIC_V2:
129-
return model.model_dump_json(indent=indent)
130-
return model.json(indent=indent) # type: ignore
129+
if PYDANTIC_V1:
130+
return model.json(indent=indent) # type: ignore
131+
return model.model_dump_json(indent=indent)
131132

132133

133134
def model_dump(
@@ -139,14 +140,14 @@ def model_dump(
139140
warnings: bool = True,
140141
mode: Literal["json", "python"] = "python",
141142
) -> dict[str, Any]:
142-
if PYDANTIC_V2 or hasattr(model, "model_dump"):
143+
if (not PYDANTIC_V1) or hasattr(model, "model_dump"):
143144
return model.model_dump(
144145
mode=mode,
145146
exclude=exclude,
146147
exclude_unset=exclude_unset,
147148
exclude_defaults=exclude_defaults,
148149
# warnings are not supported in Pydantic v1
149-
warnings=warnings if PYDANTIC_V2 else True,
150+
warnings=True if PYDANTIC_V1 else warnings,
150151
)
151152
return cast(
152153
"dict[str, Any]",
@@ -159,9 +160,9 @@ def model_dump(
159160

160161

161162
def model_parse(model: type[_ModelT], data: Any) -> _ModelT:
162-
if PYDANTIC_V2:
163-
return model.model_validate(data)
164-
return model.parse_obj(data) # pyright: ignore[reportDeprecated]
163+
if PYDANTIC_V1:
164+
return model.parse_obj(data) # pyright: ignore[reportDeprecated]
165+
return model.model_validate(data)
165166

166167

167168
# generic models
@@ -170,17 +171,16 @@ def model_parse(model: type[_ModelT], data: Any) -> _ModelT:
170171
class GenericModel(pydantic.BaseModel): ...
171172

172173
else:
173-
if PYDANTIC_V2:
174+
if PYDANTIC_V1:
175+
import pydantic.generics
176+
177+
class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ...
178+
else:
174179
# there no longer needs to be a distinction in v2 but
175180
# we still have to create our own subclass to avoid
176181
# inconsistent MRO ordering errors
177182
class GenericModel(pydantic.BaseModel): ...
178183

179-
else:
180-
import pydantic.generics
181-
182-
class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ...
183-
184184

185185
# cached properties
186186
if TYPE_CHECKING:

0 commit comments

Comments
 (0)