base-typed-int 0.1.0__tar.gz

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.
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eldeniz Guseinli
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,418 @@
1
+ Metadata-Version: 2.4
2
+ Name: base-typed-int
3
+ Version: 0.1.0
4
+ Summary: Strict typed integer base class with exact runtime subtype preservation and optional Pydantic v2 support.
5
+ Author-email: Eldeniz Guseinli <eldenizfamilyanskicode@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/eldenizfamilyanskicode/base-typed-int
8
+ Project-URL: Repository, https://github.com/eldenizfamilyanskicode/base-typed-int
9
+ Project-URL: Issues, https://github.com/eldenizfamilyanskicode/base-typed-int/issues
10
+ Keywords: typing,typed-int,integer,value-object,pydantic,domain-model
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: Implementation :: CPython
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Provides-Extra: pydantic
25
+ Requires-Dist: pydantic<3,>=2.6; extra == "pydantic"
26
+ Provides-Extra: test
27
+ Requires-Dist: pytest>=8.0; extra == "test"
28
+ Requires-Dist: pytest-cov>=5.0; extra == "test"
29
+ Requires-Dist: pydantic<3,>=2.6; extra == "test"
30
+ Provides-Extra: lint
31
+ Requires-Dist: ruff>=0.5; extra == "lint"
32
+ Provides-Extra: typecheck
33
+ Requires-Dist: mypy>=1.10; extra == "typecheck"
34
+ Requires-Dist: pyright>=1.1; extra == "typecheck"
35
+ Requires-Dist: pydantic<3,>=2.6; extra == "typecheck"
36
+ Provides-Extra: build
37
+ Requires-Dist: build>=1.2; extra == "build"
38
+ Requires-Dist: twine>=5.1; extra == "build"
39
+ Provides-Extra: dev
40
+ Requires-Dist: build>=1.2; extra == "dev"
41
+ Requires-Dist: twine>=5.1; extra == "dev"
42
+ Requires-Dist: mypy>=1.10; extra == "dev"
43
+ Requires-Dist: pyright>=1.1; extra == "dev"
44
+ Requires-Dist: pytest>=8.0; extra == "dev"
45
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
46
+ Requires-Dist: ruff>=0.5; extra == "dev"
47
+ Requires-Dist: pydantic<3,>=2.6; extra == "dev"
48
+ Dynamic: license-file
49
+
50
+ # base-typed-int
51
+
52
+ Strict typed integer base class with exact runtime subtype preservation and optional Pydantic v2 support.
53
+
54
+ ## Why
55
+
56
+ `BaseTypedInt` helps model domain integers as explicit runtime types without losing normal `int` behavior.
57
+
58
+ Example use cases:
59
+ - `UserAge`
60
+ - `RetryCount`
61
+ - `OrderPosition`
62
+ - `PriorityLevel`
63
+ - `ShardNumber`
64
+
65
+ This library is designed for cases where plain `int` is too generic, but a full wrapper object would add unnecessary ceremony and runtime overhead.
66
+
67
+ ## Design goals
68
+
69
+ - exact runtime subtype is preserved
70
+ - behaves like plain `int` in normal arithmetic operations
71
+ - arithmetic operations usually return plain `int`
72
+ - subtype is preserved in containers, attributes, pickle, and Pydantic model fields
73
+ - Pydantic serialization exports plain integers
74
+ - strict input validation
75
+ - `bool` is explicitly rejected
76
+ - no extra public API beyond the integer value itself
77
+
78
+ ## Installation
79
+
80
+ ### Core
81
+
82
+ ```bash
83
+ pip install base-typed-int
84
+ ````
85
+
86
+ ### With Pydantic v2 support
87
+
88
+ ```bash
89
+ pip install "base-typed-int[pydantic]"
90
+ ```
91
+
92
+ ### Development
93
+
94
+ ```bash
95
+ pip install "base-typed-int[dev]"
96
+ ```
97
+
98
+ ## Quick start
99
+
100
+ ```python
101
+ from base_typed_int import BaseTypedInt
102
+
103
+
104
+ class UserAge(BaseTypedInt):
105
+ pass
106
+
107
+
108
+ class Account:
109
+ def __init__(self, user_age: UserAge) -> None:
110
+ self.user_age: UserAge = user_age
111
+
112
+
113
+ user_age: UserAge = UserAge(18)
114
+
115
+ assert user_age == 18
116
+ assert isinstance(user_age, int)
117
+ assert isinstance(user_age, UserAge)
118
+ assert type(user_age) is UserAge
119
+ assert repr(user_age) == "UserAge(18)"
120
+
121
+ account: Account = Account(user_age=user_age)
122
+ assert account.user_age is user_age
123
+ ```
124
+
125
+ ## Constructor behavior
126
+
127
+ `BaseTypedInt` accepts int values and rejects bool explicitly.
128
+
129
+ ```python
130
+ from base_typed_int import BaseTypedInt
131
+
132
+
133
+ class RetryCount(BaseTypedInt):
134
+ pass
135
+
136
+
137
+ value_from_int: RetryCount = RetryCount(3)
138
+ value_from_typed_int: RetryCount = RetryCount(RetryCount(3))
139
+ ```
140
+
141
+ Invalid input raises `BaseTypedIntInvalidInputValueError`.
142
+
143
+ ```python
144
+ from base_typed_int import BaseTypedIntInvalidInputValueError
145
+
146
+
147
+ try:
148
+ RetryCount("3")
149
+ except BaseTypedIntInvalidInputValueError:
150
+ pass
151
+
152
+ try:
153
+ RetryCount(True)
154
+ except BaseTypedIntInvalidInputValueError:
155
+ pass
156
+ ```
157
+
158
+ ## Why `bool` is rejected
159
+
160
+ In Python, `bool` is a subclass of `int`.
161
+
162
+ ```python
163
+ assert isinstance(True, int)
164
+ assert int(True) == 1
165
+ ```
166
+
167
+ That behavior is useful in Python internals, but usually unsafe for domain modeling.
168
+ A domain integer such as `RetryCount`, `UserAge`, or `ShardNumber` should not silently accept `True` or `False`.
169
+
170
+ For that reason, `BaseTypedInt` explicitly rejects `bool` even though `bool` is technically an `int` subtype.
171
+
172
+ ## Runtime behavior
173
+
174
+ Normal arithmetic keeps standard Python `int` semantics.
175
+
176
+ ```python
177
+ class UserAge(BaseTypedInt):
178
+ pass
179
+
180
+
181
+ user_age: UserAge = UserAge(18)
182
+
183
+ incremented_value: int = user_age + 1
184
+ multiplied_value: int = user_age * 2
185
+ subtracted_value: int = user_age - 3
186
+
187
+ assert incremented_value == 19
188
+ assert multiplied_value == 36
189
+ assert subtracted_value == 15
190
+
191
+ assert type(incremented_value) is int
192
+ assert type(multiplied_value) is int
193
+ assert type(subtracted_value) is int
194
+ ```
195
+
196
+ This is intentional.
197
+ The typed subtype marks the boundary value itself, while regular numeric operations stay native and unsurprising.
198
+
199
+ ## Containers and attributes
200
+
201
+ The exact subtype instance is preserved when stored and retrieved.
202
+
203
+ ```python
204
+ class UserAge(BaseTypedInt):
205
+ pass
206
+
207
+
208
+ class Account:
209
+ def __init__(self, user_age: UserAge) -> None:
210
+ self.user_age: UserAge = user_age
211
+
212
+
213
+ source_user_age: UserAge = UserAge(18)
214
+
215
+ user_age_list: list[UserAge] = [source_user_age]
216
+ user_age_by_field_name: dict[str, UserAge] = {
217
+ "user_age": source_user_age,
218
+ }
219
+ values_by_user_age: dict[int, str] = {
220
+ source_user_age: "present",
221
+ }
222
+ account: Account = Account(user_age=source_user_age)
223
+
224
+ assert user_age_list[0] is source_user_age
225
+ assert user_age_by_field_name["user_age"] is source_user_age
226
+ assert account.user_age is source_user_age
227
+ assert values_by_user_age[source_user_age] == "present"
228
+ assert values_by_user_age[18] == "present"
229
+ assert type(tuple(values_by_user_age.keys())[0]) is UserAge
230
+ ```
231
+
232
+ ## Pickle support
233
+
234
+ Pickle roundtrip preserves the exact subtype.
235
+
236
+ ```python
237
+ import pickle
238
+
239
+ from base_typed_int import BaseTypedInt
240
+
241
+
242
+ class RetryCount(BaseTypedInt):
243
+ pass
244
+
245
+
246
+ source_value: RetryCount = RetryCount(7)
247
+ serialized_value: bytes = pickle.dumps(source_value)
248
+ restored_value: object = pickle.loads(serialized_value)
249
+
250
+ assert restored_value == 7
251
+ assert isinstance(restored_value, RetryCount)
252
+ assert type(restored_value) is RetryCount
253
+ ```
254
+
255
+ ## JSON behavior
256
+
257
+ Since `BaseTypedInt` inherits from `int`, standard JSON serialization naturally produces plain JSON numbers.
258
+
259
+ ```python
260
+ import json
261
+
262
+
263
+ class RetryCount(BaseTypedInt):
264
+ pass
265
+
266
+
267
+ value: RetryCount = RetryCount(7)
268
+ serialized_value: str = json.dumps(value)
269
+ restored_value: object = json.loads(serialized_value)
270
+
271
+ assert serialized_value == "7"
272
+ assert restored_value == 7
273
+ assert type(restored_value) is int
274
+ ```
275
+
276
+ ## Pydantic v2 support
277
+
278
+ `BaseTypedInt` integrates with Pydantic v2 through `__get_pydantic_core_schema__`.
279
+
280
+ Validation rules:
281
+
282
+ * accepts only strict integer input
283
+ * rejects `bool`
284
+ * rejects strings and other non-integer values
285
+ * reconstructs the exact runtime subtype
286
+
287
+ Serialization rules:
288
+
289
+ * `model_dump()` returns plain integers
290
+ * `model_dump_json()` returns JSON numbers
291
+
292
+ ### Example
293
+
294
+ ```python
295
+ from pydantic import BaseModel
296
+
297
+ from base_typed_int import BaseTypedInt
298
+
299
+
300
+ class RetryCount(BaseTypedInt):
301
+ pass
302
+
303
+
304
+ class MetricsModel(BaseModel):
305
+ primary_retry_count: RetryCount
306
+ backup_retry_count: RetryCount
307
+
308
+
309
+ metrics_model: MetricsModel = MetricsModel.model_validate(
310
+ {
311
+ "primary_retry_count": 5,
312
+ "backup_retry_count": 8,
313
+ }
314
+ )
315
+
316
+ assert type(metrics_model.primary_retry_count) is RetryCount
317
+ assert type(metrics_model.backup_retry_count) is RetryCount
318
+
319
+ python_dump: dict[str, object] = metrics_model.model_dump()
320
+ json_dump: str = metrics_model.model_dump_json()
321
+
322
+ assert python_dump == {
323
+ "primary_retry_count": 5,
324
+ "backup_retry_count": 8,
325
+ }
326
+ assert json_dump == '{"primary_retry_count":5,"backup_retry_count":8}'
327
+ ```
328
+
329
+ ### Roundtrip from exported payload
330
+
331
+ ```python
332
+ source_model: MetricsModel = MetricsModel.model_validate(
333
+ {
334
+ "primary_retry_count": 5,
335
+ "backup_retry_count": 8,
336
+ }
337
+ )
338
+
339
+ python_dump: dict[str, object] = source_model.model_dump()
340
+ restored_model: MetricsModel = MetricsModel.model_validate(python_dump)
341
+
342
+ assert type(restored_model.primary_retry_count) is RetryCount
343
+ assert type(restored_model.backup_retry_count) is RetryCount
344
+ ```
345
+
346
+ ## Error types
347
+
348
+ ```python
349
+ from base_typed_int import (
350
+ BaseTypedIntError,
351
+ BaseTypedIntInvalidInputValueError,
352
+ BaseTypedIntInvariantViolationError,
353
+ )
354
+ ```
355
+
356
+ * `BaseTypedIntError` — root exception for library errors
357
+ * `BaseTypedIntInvalidInputValueError` — invalid caller input
358
+ * `BaseTypedIntInvariantViolationError` — internal invariant or integration failure
359
+
360
+ ## Testing
361
+
362
+ ```bash
363
+ pytest
364
+ ```
365
+
366
+ With coverage:
367
+
368
+ ```bash
369
+ pytest --cov=base_typed_int --cov-report=term-missing
370
+ ```
371
+
372
+ ## Type checking
373
+
374
+ ```bash
375
+ mypy src tests
376
+ pyright
377
+ ```
378
+
379
+ ## Linting
380
+
381
+ ```bash
382
+ ruff check .
383
+ ruff format .
384
+ ```
385
+
386
+ ## Build
387
+
388
+ ```bash
389
+ python -m build
390
+ ```
391
+
392
+ ## Package structure
393
+
394
+ ```text
395
+ src/
396
+ base_typed_int/
397
+ __init__.py
398
+ _base_typed_int.py
399
+ _exceptions.py
400
+ py.typed
401
+ ```
402
+
403
+ ## Compatibility
404
+
405
+ * Python 3.10+
406
+ * CPython
407
+ * Pydantic v2 support is optional
408
+
409
+ ## Notes
410
+
411
+ `BaseTypedInt` is intentionally minimal.
412
+ It is not a numeric validation framework and does not enforce domain-specific constraints such as non-negative values, ranges, or upper bounds.
413
+
414
+ Those constraints should be modeled in your own subclasses or service-layer validation where appropriate.
415
+
416
+ ## License
417
+
418
+ MIT