@ai-devtools/dbspec-macos-arm64 1.6.2 → 1.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbspec
3
- Version: 1.6.2
3
+ Version: 1.6.3
4
4
  Summary: DBSpec: CLI/TUI for database design (Excel as DSL)
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -137,6 +137,7 @@ src/resources/sqlalchemy_templates/package_init.py.j2
137
137
  src/reveng/__init__.py
138
138
  src/storage/__init__.py
139
139
  src/storage/alembic_env_patching.py
140
+ src/storage/alembic_service_table.py
140
141
  src/storage/atomic.py
141
142
  src/storage/config.py
142
143
  src/storage/file_utils.py
@@ -254,6 +255,8 @@ src/tui/styles/__init__.py
254
255
  src/tui/styles/build.py
255
256
  src/tui/styles/tokens.py
256
257
  src/utils/__init__.py
258
+ src/utils/default_comparison.py
259
+ src/utils/default_literals.py
257
260
  src/utils/parsing.py
258
261
  src/utils/postgres_types.py
259
262
  src/utils/progress.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pydantic
3
- Version: 2.14.1
3
+ Version: 2.14.2
4
4
  Summary: Data validation using Python type hints
5
5
  Project-URL: Homepage, https://github.com/pydantic/pydantic
6
6
  Project-URL: Documentation, https://pydantic.dev/docs/validation/latest/get-started/
@@ -31,7 +31,7 @@ Classifier: Topic :: Internet
31
31
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
32
32
  Requires-Python: >=3.10
33
33
  Requires-Dist: annotated-types>=0.6.0
34
- Requires-Dist: pydantic-core==2.50.1
34
+ Requires-Dist: pydantic-core==2.50.2
35
35
  Requires-Dist: typing-extensions>=4.16.0
36
36
  Requires-Dist: typing-inspection>=0.4.4
37
37
  Provides-Extra: email
@@ -117,6 +117,65 @@ See our [security policy](https://github.com/pydantic/pydantic/security/policy).
117
117
  <!-- markdownlint-disable descriptive-link-text -->
118
118
  <!-- markdownlint-disable-next-line first-line-heading -->
119
119
 
120
+ ## v2.14.2 (2026-10-29)
121
+
122
+ [GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.14.2)
123
+
124
+ ### What's Changed
125
+
126
+ #### Packaging
127
+
128
+ * Bump PyO3 to 0.30.1, jiter to 0.18.0, use `PyFrozenDict` by [@Viicos](https://github.com/Viicos) in [#14003](https://github.com/pydantic/pydantic/pull/14003)
129
+ * Bump speedate to v0.18.0 by [@Viicos](https://github.com/Viicos) in [#14007](https://github.com/pydantic/pydantic/pull/14007)
130
+ * Publish `pydantic-core` wheels for PyPy 8.0 (`pp80` ABI) by [@Viicos](https://github.com/Viicos) in [#14083](https://github.com/pydantic/pydantic/pull/14083)
131
+
132
+ #### Performance
133
+
134
+ * Avoid Python tuple allocation during model fields validation by [@Viicos](https://github.com/Viicos) in [#13955](https://github.com/pydantic/pydantic/pull/13955)
135
+ * Lazily call repr of values during nested union serialization by [@Viicos](https://github.com/Viicos) in [#14082](https://github.com/pydantic/pydantic/pull/14082)
136
+ * Skip decorator field checks when no field decorators exist by [@alexmojaki](https://github.com/alexmojaki) in [#14111](https://github.com/pydantic/pydantic/pull/14111)
137
+ * Avoid heap allocation for small JSON model field buffers by [@alexmojaki](https://github.com/alexmojaki) in [#14113](https://github.com/pydantic/pydantic/pull/14113)
138
+
139
+ #### Fixes
140
+
141
+ * Make JSON validation by iteration consistent with Python approach by [@davidhewitt](https://github.com/davidhewitt) in [#13275](https://github.com/pydantic/pydantic/pull/13275).
142
+
143
+ JSON validation now handles field lookup and extra data the same way as Python validation. In particular, keys that only match an
144
+ unused alias or field name are now treated as extra data, instead of being silently dropped:
145
+
146
+ ```python
147
+ from pydantic import BaseModel, ConfigDict
148
+ from pydantic.alias_generators import to_camel
149
+
150
+
151
+ class Page(BaseModel):
152
+ model_config = ConfigDict(extra='forbid', alias_generator=to_camel)
153
+
154
+ page_size: int = 10
155
+
156
+
157
+ Page.model_validate_json('{"page_size": 50}')
158
+ # Before: Page(page_size=10)
159
+ # After: ValidationError (extra_forbidden)
160
+ ```
161
+
162
+ See the [pull request](https://github.com/pydantic/pydantic/pull/13275) for the full list of behavior changes.
163
+
164
+ * Fix unpickling of generic classes defined in separate module by [@Viicos](https://github.com/Viicos) in [#13994](https://github.com/pydantic/pydantic/pull/13994)
165
+ * Support explicit `by_alias` overriding `validate_by_alias` in validation JSON schemas by [@Viicos](https://github.com/Viicos) in [#13997](https://github.com/pydantic/pydantic/pull/13997)
166
+ * Fix mypy plugin crash on deferred empty model by [@ilevkivskyi](https://github.com/ilevkivskyi) in [#14027](https://github.com/pydantic/pydantic/pull/14027)
167
+ * Fix infinite recursion when validating an invalid default with `PydanticUseDefault` by [@Viicos](https://github.com/Viicos) in [#14028](https://github.com/pydantic/pydantic/pull/14028)
168
+ * Implement recursion guard for `validate_strings()` by [@Viicos](https://github.com/Viicos) in [#14029](https://github.com/pydantic/pydantic/pull/14029)
169
+ * Support named tuples in `Sequence` serialization by [@Viicos](https://github.com/Viicos) in [#14033](https://github.com/pydantic/pydantic/pull/14033)
170
+ * Take `preserve_empty_path` into account in URL types equality by [@Viicos](https://github.com/Viicos) in [#14035](https://github.com/pydantic/pydantic/pull/14035)
171
+ * Fix pickling of `PydanticCustomError` and `ValidationError`s containing custom errors by [@Viicos](https://github.com/Viicos) in [#14051](https://github.com/pydantic/pydantic/pull/14051)
172
+ * Do not extract attribute docstrings from nested classes and functions by [@Viicos](https://github.com/Viicos) in [#14052](https://github.com/pydantic/pydantic/pull/14052)
173
+ * Fix forward annotations evaluation and type variables substitution when rebuilding parametrized generic models by [@Viicos](https://github.com/Viicos) in [#14054](https://github.com/pydantic/pydantic/pull/14054)
174
+ * Fix `Self` resolution in parametrized generics by [@Viicos](https://github.com/Viicos) in [#14065](https://github.com/pydantic/pydantic/pull/14065)
175
+ * Ignore extra data matching a field name in `TypedDict` and dataclasses with `extra=allow` by [@Viicos](https://github.com/Viicos) in [#14090](https://github.com/pydantic/pydantic/pull/14090)
176
+ * Check mapping `max_length` constraint during validation by [@Viicos](https://github.com/Viicos) in [#14092](https://github.com/pydantic/pydantic/pull/14092)
177
+ * Fix deep copy of self-referential models by [@Viicos](https://github.com/Viicos) in [#14116](https://github.com/pydantic/pydantic/pull/14116)
178
+
120
179
  ## v2.14.1 (2026-10-11)
121
180
 
122
181
  [GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.14.1)
@@ -1,8 +1,8 @@
1
- pydantic-2.14.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
- pydantic-2.14.1.dist-info/METADATA,sha256=TUyOjV5qJ9Ugjw15SfJWKOZ0cTMDSWnU0TmbGtU5QDY,133006
3
- pydantic-2.14.1.dist-info/RECORD,,
4
- pydantic-2.14.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
5
- pydantic-2.14.1.dist-info/licenses/LICENSE,sha256=qeGG88oWte74QxjnpwFyE1GgDLe4rjpDlLZ7SeNSnvM,1129
1
+ pydantic-2.14.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ pydantic-2.14.2.dist-info/METADATA,sha256=XidxXTUMyZvDOHdATeuiqUkSIa6XLbj4x59kAyvuOgE,137593
3
+ pydantic-2.14.2.dist-info/RECORD,,
4
+ pydantic-2.14.2.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
5
+ pydantic-2.14.2.dist-info/licenses/LICENSE,sha256=qeGG88oWte74QxjnpwFyE1GgDLe4rjpDlLZ7SeNSnvM,1129
6
6
  pydantic/__init__.py,sha256=5SSMubvik7NTbb7X_qVDJtzfPAM3CuXwPXlMWEluwBs,15951
7
7
  pydantic/__pycache__/__init__.cpython-313.pyc,,
8
8
  pydantic/__pycache__/_migration.cpython-313.pyc,,
@@ -76,11 +76,11 @@ pydantic/_internal/_dataclasses.py,sha256=Oxv8s2EK-PQvYAe_9kD1yKg9cMNJtm1lg0SWtR
76
76
  pydantic/_internal/_decorators.py,sha256=ZqaoKcwtWudWa-WsopixHSZeA75YGIkI9GOigCmIzlk,35483
77
77
  pydantic/_internal/_decorators_v1.py,sha256=v7_cyEBvJceFO0aYa1AuvYeaMvjEYRQ3-TbW8U8Ghds,6198
78
78
  pydantic/_internal/_discriminated_union.py,sha256=HdRL2XOFoCeV95rK0YkxJWPhSdmZWnutRaaBkmQ60Hs,27686
79
- pydantic/_internal/_docs_extraction.py,sha256=8UYnAy-GxCePc0XoH43oIkdNAqFmICLyjp48SMtGoCw,4140
80
- pydantic/_internal/_fields.py,sha256=-lSyiply9Ttnx51WhUnaxXvnZ0Yem_qZxh7vUSptqyo,39627
79
+ pydantic/_internal/_docs_extraction.py,sha256=V8_-TfG1oUHpEThCvNtZjJaPwd-zVodn4EqkAGv17hY,4708
80
+ pydantic/_internal/_fields.py,sha256=OlKgiqVO6ITTezu46rIDkGdf0gh3qnphx92RhrP4m28,41083
81
81
  pydantic/_internal/_forward_ref.py,sha256=q6SQhW8wo9vPZiuXJ661Vfg7PgoqeJBQTA3dpInXWlA,641
82
- pydantic/_internal/_generate_schema.py,sha256=dZNxARwclDkMeG2j_S16MS8FvDA9GdnNh6VCaiEiFrA,141797
83
- pydantic/_internal/_generics.py,sha256=4WpBm518c-ZHOiT3ft1hK9JNocIuZkxzjXs8d1bnafg,24574
82
+ pydantic/_internal/_generate_schema.py,sha256=_BIwnVrd3fVO9eSIOvP6-39o7kzsHii1Wbe8eVwJbwY,142174
83
+ pydantic/_internal/_generics.py,sha256=LRATBwa-ezzQkFfTUXET4zfZaQT7U0hPUBioml_fIP0,24847
84
84
  pydantic/_internal/_git.py,sha256=IwPh3DPfa2Xq3rBuB9Nx8luR2A1i69QdeTfWWXIuCVg,809
85
85
  pydantic/_internal/_import_utils.py,sha256=TRhxD5OuY6CUosioBdBcJUs0om7IIONiZdYAV7zQ8jM,402
86
86
  pydantic/_internal/_known_annotated_metadata.py,sha256=KSObdJ3af4YRPEcLFR65XBizJF33h5Ypckeu9JAvKxQ,16863
@@ -90,7 +90,7 @@ pydantic/_internal/_namespace_utils.py,sha256=8_QlLr773kmPzIGCu8RjExBJhGwBn0RAjK
90
90
  pydantic/_internal/_repr.py,sha256=MNka4onC9u-tXJvV2rsz3JXsuxqlwKbKnd16-tgxriw,4981
91
91
  pydantic/_internal/_schema_gather.py,sha256=0qGbiz4pdcfvdMiH7JueJ10tQ8c750g1OkZ0xyP_20Q,1892
92
92
  pydantic/_internal/_schema_generation_shared.py,sha256=8nLsSaqgEebxeF26OwiPBt0ZVPipGqW1IV40agkRfxw,4869
93
- pydantic/_internal/_serializers.py,sha256=YIWvSmAR5fnbGSWCOQduWt1yB4ZQY42eAruc-enrb6c,1491
93
+ pydantic/_internal/_serializers.py,sha256=evOucDhMiIjaik5f4yMLdDP08ODB0l2pCir22HuQ-Ag,1786
94
94
  pydantic/_internal/_signature.py,sha256=64JyRJJ8Gf-JOfrZa2xTNUdGpd7RGqdNLYFXYOFuPZU,7143
95
95
  pydantic/_internal/_type_refs.py,sha256=nxJgVhwwxiSOcQbrc1e5La-Dpm5wU_4SSKJUBkp0hOU,4521
96
96
  pydantic/_internal/_typing_extra.py,sha256=5WgRV0MVsZqah9pW4o_Bbmsu73BEmELVhO7k23NUNwQ,20967
@@ -139,9 +139,9 @@ pydantic/functional_serializers.py,sha256=wcF7l3ZzY4rEXBG0IkU3KLGr5_NIEKS6-CFq14
139
139
  pydantic/functional_validators.py,sha256=ungjnjugPxAh7vPhU7Yp7k6zdPUfKZrtOFbRzN064fw,33225
140
140
  pydantic/generics.py,sha256=0ZqZ9O9annIj_3mGBRqps4htey3b5lV1-d2tUxPMMnA,144
141
141
  pydantic/json.py,sha256=ZH8RkI7h4Bz-zp8OdTAxbJUoVvcoU-jhMdRZ0B-k0xc,140
142
- pydantic/json_schema.py,sha256=rRB_vFJULXrsTMsIVahTyIsa0nJX8oMYLe_mhPzqbSA,142234
143
- pydantic/main.py,sha256=hDNfwFoRKig0r0oEj3HGp8RrkPRGi-5UHaNuQ4o4cZA,91376
144
- pydantic/mypy.py,sha256=8qaRSTchUYqUln0bg8zojiTEKrO17Sjw5LSyhQSuGRk,64439
142
+ pydantic/json_schema.py,sha256=OM7Qy6JgZ1Gr7ln_2-4lGbAa7N1ftl1-7pYVzO3jtok,143064
143
+ pydantic/main.py,sha256=oYoupv4Cq9EDXdVrNQBv0psMkCJ_cdd-RfoOUq7Vruc,91726
144
+ pydantic/mypy.py,sha256=qWTROdLlgp9sRNXTPd4F3nQke818LSWdr7r83_5lmv4,64611
145
145
  pydantic/networks.py,sha256=YIXsCaWUqE7FMwAO5ujYXClMLxI3KP6e2MdVwnP5X4s,44516
146
146
  pydantic/parse.py,sha256=wkd82dgtvWtD895U_I6E1htqMlGhBSYEV39cuBSeo3A,141
147
147
  pydantic/plugin/__init__.py,sha256=SrVX9XDbUKzt6BVNBRKXKmEqxecEREq6A4WoV_RMJhc,7263
@@ -151,10 +151,10 @@ pydantic/plugin/__pycache__/_schema_validator.cpython-313.pyc,,
151
151
  pydantic/plugin/_loader.py,sha256=hAjgSljoIhGx3AVpIpuqw5SPttBNNeGBSTrqSMnNiJk,2213
152
152
  pydantic/plugin/_schema_validator.py,sha256=__9f3t6adZxNgyzajuNda5siIvKWDDXssE6QD8hzXB4,5815
153
153
  pydantic/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
154
- pydantic/root_model.py,sha256=DlZVc2b8wLEYQD6ZmmxPSPMLxI-EnqHspBKysXGZSy0,8703
154
+ pydantic/root_model.py,sha256=7enqbPZHFXk7DO-2H9wG9NxmIGeJcbHSewhKSLS8a4A,8777
155
155
  pydantic/schema.py,sha256=Vqqjvq_LnapVknebUd3Bp_J1p2gXZZnZRgL48bVEG7o,142
156
156
  pydantic/tools.py,sha256=iHQpd8SJ5DCTtPV5atAV06T89bjSaMFeZZ2LX9lasZY,141
157
- pydantic/type_adapter.py,sha256=woRrtEFtcoVTrfTyzQ_e2yoprISljsSQU6LzPVx8ZV4,36039
157
+ pydantic/type_adapter.py,sha256=HuIrYnURDsX0TS1L8A_AKxymlqg9MA-ODuaCp99v-kc,36569
158
158
  pydantic/types.py,sha256=5IFO7vOYHyaPf4VE2bRi9aUdH1CY-NgADjpYDfwIWQQ,110280
159
159
  pydantic/typing.py,sha256=P7feA35MwTcLsR1uL7db0S-oydBxobmXa55YDoBgajQ,138
160
160
  pydantic/utils.py,sha256=15nR2QpqTBFlQV4TNtTItMyTJx_fbyV-gPmIEY1Gooc,141
@@ -190,8 +190,8 @@ pydantic/v1/annotated_types.py,sha256=uk2NAAxqiNELKjiHhyhxKaIOh8F1lYW_LzrW3X7oZB
190
190
  pydantic/v1/class_validators.py,sha256=ULOaIUgYUDBsHL7EEVEarcM-UubKUggoN8hSbDonsFE,14672
191
191
  pydantic/v1/color.py,sha256=iZABLYp6OVoo2AFkP9Ipri_wSc6-Kklu8YuhSartd5g,16844
192
192
  pydantic/v1/config.py,sha256=a6P0Wer9x4cbwKW7Xv8poSUqM4WP-RLWwX6YMpYq9AA,6532
193
- pydantic/v1/dataclasses.py,sha256=784cqvInbwIPWr9usfpX3ch7z4t3J2tTK6N067_wk1o,18172
194
- pydantic/v1/datetime_parse.py,sha256=4Qy1kQpq3rNVZJeIHeSPDpuS2Bvhp1KPtzJG1xu-H00,7724
193
+ pydantic/v1/dataclasses.py,sha256=0Sfk-TNwm21SqiGhhpzs1DsXajhdWM6Eh5ZiSu4bjIA,18358
194
+ pydantic/v1/datetime_parse.py,sha256=exzh-Ul0IWR4FwRVpvE5aoTQ69DK6R-vNCtUakowBxs,7728
195
195
  pydantic/v1/decorator.py,sha256=zaaxxxoWPCm818D1bs0yhapRjXm32V8G0ZHWCdM1uXA,10339
196
196
  pydantic/v1/env_settings.py,sha256=A9VXwtRl02AY-jH0C0ouy5VNw3fi6F_pkzuHDjgAAOM,14105
197
197
  pydantic/v1/error_wrappers.py,sha256=6625Mfw9qkC2NwitB_JFAWe8B-Xv6zBU7rL9k28tfyo,5196
@@ -200,18 +200,18 @@ pydantic/v1/fields.py,sha256=MjiQ6Q5RkE-koXfmx8tYJbfe97_QeYtnpkQf8OTve_s,50646
200
200
  pydantic/v1/generics.py,sha256=YzyKTZN6x5Q1RGJ3WQ9jN-uwHJxL3W4qoZqwcZXqxWg,17829
201
201
  pydantic/v1/json.py,sha256=WQ5Hy_hIpfdR3YS8k6N2E6KMJzsdbBi_ldWOPJaV81M,3390
202
202
  pydantic/v1/main.py,sha256=vRB1TbpkzPN3P5ijJlc-cjNuO-HciNOpC4b8K3zZnfc,45697
203
- pydantic/v1/mypy.py,sha256=Cl8XRfCmIcVE3j5AEU52C8iDh8lcX__D3hz2jIWxMAs,38860
203
+ pydantic/v1/mypy.py,sha256=egR4NqF3clfaVYPP5nmj36jjdOHJpRPeGYS83mzNTJY,38898
204
204
  pydantic/v1/networks.py,sha256=HYNtKAfOmOnKJpsDg1g6SIkj9WPhU_-i8l5e2JKBpG4,22124
205
205
  pydantic/v1/parse.py,sha256=BJtdqiZRtav9VRFCmOxoY-KImQmjPy-A_NoojiFUZxY,1821
206
206
  pydantic/v1/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
207
207
  pydantic/v1/schema.py,sha256=aqBuA--cq8gAVkim5BJPFASHzOZ8dFtmFX_fNGr6ip4,47801
208
208
  pydantic/v1/tools.py,sha256=1lDdXHk0jL5uP3u5RCYAvUAlGClgAO-45lkq9j7fyBA,2881
209
- pydantic/v1/types.py,sha256=Bzl-RcnitPBHnqwwj9iv7JjHuN1GpnWH24dKkF3l9e8,35455
209
+ pydantic/v1/types.py,sha256=RfJ19cNFhy-VKs2lzIIl9_dHYn8Vlabd7VktOvoQawI,35793
210
210
  pydantic/v1/typing.py,sha256=ovwtLpEZCbnghZaHfSNJupzetzHNkLXjn_66kgTnIV4,20102
211
211
  pydantic/v1/utils.py,sha256=1PqOIlz6OVWwGds3HBKlw4Et6asFou0UUpAto7jFOCs,26014
212
212
  pydantic/v1/validators.py,sha256=lyUkn1MWhHxlCX5ZfEgFj_CAHojoiPcaQeMdEM9XviU,22187
213
- pydantic/v1/version.py,sha256=YpHWOQKtGoxfyikzGrcmXJVKUVYB9EBdoCR994QxSnE,1039
213
+ pydantic/v1/version.py,sha256=51ZPNC7FcsbAGRl-p2_vbJ0-bKUtjtwc6hiKrBoG4ZA,1039
214
214
  pydantic/validate_call_decorator.py,sha256=oktZNbH04RAEV99NSvl5ETPNLTEEE_HLkwj64TOsWd0,4749
215
215
  pydantic/validators.py,sha256=pwbIJXVb1CV2mAE4w_EGfNj7DwzsKaWw_tTL6cviTus,146
216
- pydantic/version.py,sha256=9afD-VnYJ0CS7G0FdXKCEWW5IPY-ZHRsbU-ETDyysiA,3985
216
+ pydantic/version.py,sha256=mmIXsYMfk9459TcBRmYuHsYc6wO9c9iMFbAT8GZZUFs,3985
217
217
  pydantic/warnings.py,sha256=tIhE-YCNiWNgO53dwo7jYdOFv7AHzJmTkbMdEGZLEko,5177
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.32.4
2
+ Generator: hatchling 1.33.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dbspec"
7
- version = "1.6.2"
7
+ version = "1.6.3"
8
8
  description = "DBSpec: CLI/TUI for database design (Excel as DSL)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -7,11 +7,12 @@ from collections.abc import Iterator as _DBSpecIterator
7
7
 
8
8
  _dbspec_NEXTVAL_RE = _dbspec_re.compile(
9
9
  r"\bnextval\s*\(\s*'((?:[^']|'')+)'\s*(?:::\s*(?:pg_catalog\.)?regclass)?\s*\)",
10
- _dbspec_re.IGNORECASE,
10
+ _dbspec_re.IGNORECASE | _dbspec_re.ASCII,
11
11
  )
12
12
  _dbspec_SEQUENCE_IDENTIFIER_RE = _dbspec_re.compile(r'"((?:[^"]|"")+)"|([^\W\d][\w$]*)')
13
13
  _dbspec_DOLLAR_QUOTE_RE = _dbspec_re.compile(r"\$(?:[^\W\d]\w*)?\$")
14
14
  _dbspec_INVALID_SEQUENCE_IDENTIFIER = "Invalid nextval sequence identifier"
15
+ _dbspec_SQL_WHITESPACE = " \t\n\r\f\v"
15
16
 
16
17
 
17
18
  def _dbspec_sequence_identity(value: str) -> tuple[str, ...]:
@@ -19,7 +20,7 @@ def _dbspec_sequence_identity(value: str) -> tuple[str, ...]:
19
20
  parts: list[str] = []
20
21
  end = 0
21
22
  for match in _dbspec_SEQUENCE_IDENTIFIER_RE.finditer(value):
22
- separator = value[end : match.start()].strip()
23
+ separator = value[end : match.start()].strip(_dbspec_SQL_WHITESPACE)
23
24
  if separator != ("." if parts else ""):
24
25
  raise ValueError(f"{_dbspec_INVALID_SEQUENCE_IDENTIFIER}: {value!r}")
25
26
  quoted, unquoted = match.groups()
@@ -29,7 +30,7 @@ def _dbspec_sequence_identity(value: str) -> tuple[str, ...]:
29
30
  else "".join(char.lower() if char.isascii() else char for char in unquoted)
30
31
  )
31
32
  end = match.end()
32
- if value[end:].strip() or len(parts) not in (1, 2):
33
+ if value[end:].strip(_dbspec_SQL_WHITESPACE) or len(parts) not in (1, 2):
33
34
  raise ValueError(f"{_dbspec_INVALID_SEQUENCE_IDENTIFIER}: {value!r}")
34
35
  return tuple(parts)
35
36
 
@@ -85,11 +86,7 @@ def _dbspec_executed_nextval_arguments(sql: str) -> _DBSpecIterator[str]:
85
86
  position += 1
86
87
 
87
88
 
88
- _dbspec_CAST_RE = _dbspec_re.compile(
89
- r"::\s*(?:pg_catalog\.)?[a-z_][\w. ]*(?:\(\s*\d+\s*\))?$", _dbspec_re.I
90
- )
91
89
  _dbspec_INTEGER_RE = _dbspec_re.compile(r"[+-]?\d+")
92
- _dbspec_WHITESPACE_RE = _dbspec_re.compile(r"\s+")
93
90
 
94
91
 
95
92
  def dbspec_compare_server_default(
@@ -126,21 +123,21 @@ def _dbspec_normalize_default(
126
123
  ) -> tuple[str, tuple[str, ...] | str] | None:
127
124
  if value is None:
128
125
  return None
129
- text = str(value).strip()
126
+ text = str(value).strip(_dbspec_SQL_WHITESPACE)
130
127
  if not text:
131
128
  return None
132
129
  sequence = _dbspec_normalize_nextval(text, column)
133
130
  if sequence is not None:
134
131
  return ("nextval", sequence)
135
- return ("literal", _dbspec_normalize_literal_default(text))
132
+ return ("literal", _dbspec_normalize_literal_default(text, column))
136
133
 
137
134
 
138
135
  def _dbspec_normalize_nextval(value: str, column: _DBSpecAny) -> tuple[str, ...] | None:
139
136
  try:
140
- arguments = list(_dbspec_executed_nextval_arguments(value))
141
- if not arguments:
137
+ match = _dbspec_NEXTVAL_RE.fullmatch(_dbspec_strip_balanced_parentheses(value))
138
+ if match is None:
142
139
  return None
143
- identity = _dbspec_sequence_identity(arguments[0].replace("''", "'"))
140
+ identity = _dbspec_sequence_identity(match[1].replace("''", "'"))
144
141
  except ValueError:
145
142
  return None
146
143
  if len(identity) == 1:
@@ -156,58 +153,143 @@ def _dbspec_column_schema(column: _DBSpecAny) -> str | None:
156
153
  return schema if isinstance(schema, str) and schema else None
157
154
 
158
155
 
159
- def _dbspec_normalize_literal_default(value: str) -> str:
160
- text = value.strip()
161
- lowered = text.lower()
162
- if lowered.startswith("default "):
163
- text = text[8:].strip()
164
- text = _dbspec_strip_balanced_parentheses(text)
165
- text = _dbspec_CAST_RE.sub("", text).strip()
166
- text = _dbspec_strip_balanced_parentheses(text)
167
- lowered = text.lower()
168
- if lowered in {"true", "'true'", "1"}:
169
- return "true"
170
- if lowered in {"false", "'false'", "0"}:
171
- return "false"
172
- if _dbspec_INTEGER_RE.fullmatch(text):
173
- return str(int(text))
174
- return _dbspec_WHITESPACE_RE.sub(" ", lowered)
156
+ def _dbspec_sql_tokens(value: str) -> list[str]:
157
+ """Tokenize enough PostgreSQL syntax to preserve literals and grouping."""
158
+ tokens: list[str] = []
159
+ position = 0
160
+ whitespace = ""
161
+ while position < len(value):
162
+ if value[position] in _dbspec_SQL_WHITESPACE:
163
+ whitespace += value[position]
164
+ position += 1
165
+ continue
166
+ start = position
167
+ if value.startswith("--", position):
168
+ end = value.find("\n", position)
169
+ position = len(value) if end < 0 else end + 1
170
+ elif value.startswith("/*", position):
171
+ depth = 1
172
+ position += 2
173
+ while position < len(value) and depth:
174
+ if value.startswith("/*", position):
175
+ depth += 1
176
+ position += 2
177
+ elif value.startswith("*/", position):
178
+ depth -= 1
179
+ position += 2
180
+ else:
181
+ position += 1
182
+ elif match := _dbspec_DOLLAR_QUOTE_RE.match(value, position):
183
+ end = value.find(match[0], match.end())
184
+ position = len(value) if end < 0 else end + len(match[0])
185
+ elif value[position] in ("'", '"') or (
186
+ value[position] in "eEbBxXnN" and value[position + 1:position + 2] == "'"
187
+ ) or (
188
+ value[position] in "uU" and value[position + 1:position + 2] == "&"
189
+ and value[position + 2:position + 3] in ("'", '"')
190
+ ):
191
+ escaped = value[position] in "eE"
192
+ if value[position] in "uU":
193
+ position += 2
194
+ elif value[position] in "eEbBxXnN":
195
+ position += 1
196
+ quote = value[position]
197
+ position += 1
198
+ while position < len(value):
199
+ if value.startswith(quote * 2, position):
200
+ position += 2
201
+ elif escaped and value[position] == "\\":
202
+ position += 2
203
+ elif value[position] == quote:
204
+ position += 1
205
+ break
206
+ else:
207
+ position += 1
208
+ else:
209
+ match = _dbspec_re.match(
210
+ r"(?:[A-Za-z_]|[^\x00-\x7f])(?:[A-Za-z_0-9$]|[^\x00-\x7f])*|[0-9]+(?:\.[0-9]+)?|[+*/<>=~!@#%^&|`?:-]+|.",
211
+ value[position:],
212
+ )
213
+ assert match is not None
214
+ position += len(match[0])
215
+ token = value[start:position]
216
+ # Adjacent string literals separated by a newline concatenate in PostgreSQL.
217
+ if whitespace and tokens and tokens[-1].endswith("'") and token.startswith("'"):
218
+ tokens.append("\n" if "\n" in whitespace else " ")
219
+ whitespace = ""
220
+ opaque = token.startswith(("'", '"', "$", "--", "/*")) or (
221
+ len(token) >= 2 and token[0] in "eEbBxXnN" and token[1] == "'"
222
+ ) or (len(token) >= 3 and token[0] in "uU" and token[1] == "&" and token[2] in ("'", '"'))
223
+ folded = "".join(char.lower() if char.isascii() else char for char in token)
224
+ tokens.append(token if opaque else folded)
225
+ return tokens
175
226
 
176
227
 
177
228
  def _dbspec_strip_balanced_parentheses(value: str) -> str:
178
- text = value.strip()
179
- while (
180
- text.startswith("(")
181
- and text.endswith(")")
182
- and _dbspec_has_wrapping_parentheses(text)
183
- ):
184
- text = text[1:-1].strip()
185
- return text
229
+ tokens = _dbspec_sql_tokens(value)
230
+ while len(tokens) >= 2 and tokens[0] == "(" and tokens[-1] == ")":
231
+ depth = 0
232
+ for position, token in enumerate(tokens):
233
+ if token == "(":
234
+ depth += 1
235
+ elif token == ")":
236
+ depth -= 1
237
+ if depth <= 0 and position != len(tokens) - 1:
238
+ return " ".join(tokens)
239
+ if depth != 0:
240
+ break
241
+ tokens = tokens[1:-1]
242
+ return " ".join(tokens)
186
243
 
187
244
 
188
- def _dbspec_has_wrapping_parentheses(value: str) -> bool:
189
- depth = 0
190
- quote: str | None = None
191
- index = 0
192
- while index < len(value):
193
- char = value[index]
194
- if quote is not None:
195
- if char == quote:
196
- if index + 1 < len(value) and value[index + 1] == quote:
197
- index += 2
198
- continue
199
- quote = None
200
- index += 1
201
- continue
202
- if char in {"'", '"'}:
203
- quote = char
204
- elif char == "(":
205
- depth += 1
206
- elif char == ")":
207
- depth -= 1
208
- if depth == 0 and index != len(value) - 1:
209
- return False
210
- if depth < 0:
211
- return False
212
- index += 1
213
- return depth == 0
245
+ def _dbspec_normalize_sql_expression(value: str) -> str:
246
+ return _dbspec_strip_balanced_parentheses(value)
247
+
248
+
249
+ def _dbspec_string_literal(value: str) -> str | None:
250
+ match = _dbspec_re.fullmatch(
251
+ r"'((?:[^']|'')*)'(?:\s*::\s*(?:pg_catalog\s*\.\s*)?(?:text|varchar|character\s+varying))?",
252
+ value, _dbspec_re.IGNORECASE | _dbspec_re.ASCII,
253
+ )
254
+ return match[1].replace("''", "'") if match else None
255
+
256
+
257
+ def _dbspec_normalize_literal_default(value: str, column: _DBSpecAny = None) -> str:
258
+ text = value.strip(_dbspec_SQL_WHITESPACE)
259
+ if text.lower().startswith("default "):
260
+ text = text[8:].strip(_dbspec_SQL_WHITESPACE)
261
+ text = _dbspec_strip_balanced_parentheses(text)
262
+ column_type = str(getattr(column, "type", "")).lower()
263
+ if _dbspec_re.fullmatch(r"(?:varchar|character varying|text)(?:\(\d+\))?", column_type):
264
+ literal = _dbspec_string_literal(text)
265
+ if literal is not None:
266
+ return "string:" + literal
267
+ return "sql:" + text
268
+ # Only normalize primitive casts; expressions and domain casts stay visible.
269
+ cast = _dbspec_re.fullmatch(
270
+ r"(.+?)\s*::\s*(?:pg_catalog\s*\.\s*)?(boolean|bool|integer|int|int4|bigint|int8|smallint|int2)",
271
+ text, _dbspec_re.IGNORECASE | _dbspec_re.ASCII,
272
+ )
273
+ primitive_text = _dbspec_strip_balanced_parentheses(cast[1]) if cast else text
274
+ cast_type = cast[2].lower() if cast else ""
275
+ boolean_casts = {"boolean", "bool"}
276
+ if cast_type in boolean_casts:
277
+ boolean = _dbspec_re.fullmatch(r"(?:'(true|false)'|(true|false))", primitive_text, _dbspec_re.IGNORECASE | _dbspec_re.ASCII)
278
+ if boolean:
279
+ return (boolean[1] or boolean[2]).lower()
280
+ return text
281
+ if cast:
282
+ # Quoted numeric literals are safe only with an explicit integer cast.
283
+ quoted = _dbspec_re.fullmatch(r"'([+-]?[0-9]+)'", primitive_text)
284
+ if quoted:
285
+ primitive_text = quoted[1]
286
+ integer = _dbspec_re.fullmatch(r"([+-]?)[ \t\n\r\f\v]*([0-9]+)", primitive_text)
287
+ if integer:
288
+ number = int(integer[1] + integer[2])
289
+ bit_width = {"smallint": 16, "int2": 16, "integer": 32, "int": 32, "int4": 32, "bigint": 64, "int8": 64}.get(cast_type)
290
+ if bit_width is not None and not -(2 ** (bit_width - 1)) <= number < 2 ** (bit_width - 1):
291
+ return text
292
+ return str(number)
293
+ if not cast and primitive_text.lower() in {"true", "false"}:
294
+ return primitive_text.lower()
295
+ return text
package/bin/dbspec/dbspec CHANGED
Binary file
package/package.json CHANGED
@@ -24,5 +24,5 @@
24
24
  "publishConfig": {
25
25
  "access": "public"
26
26
  },
27
- "version": "1.6.2"
27
+ "version": "1.6.3"
28
28
  }