interface-contract 0.4.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.
Files changed (27) hide show
  1. interface_contract-0.4.0/CHANGELOG.md +33 -0
  2. interface_contract-0.4.0/LICENSE +21 -0
  3. interface_contract-0.4.0/MANIFEST.in +6 -0
  4. interface_contract-0.4.0/PKG-INFO +305 -0
  5. interface_contract-0.4.0/README.md +268 -0
  6. interface_contract-0.4.0/README.tr.md +156 -0
  7. interface_contract-0.4.0/interface_contract/__init__.py +68 -0
  8. interface_contract-0.4.0/interface_contract/_core.py +7 -0
  9. interface_contract-0.4.0/interface_contract/adapters.py +7 -0
  10. interface_contract-0.4.0/interface_contract/mypy_plugin.py +68 -0
  11. interface_contract-0.4.0/interface_contract/py.typed +0 -0
  12. interface_contract-0.4.0/interface_contract.egg-info/PKG-INFO +305 -0
  13. interface_contract-0.4.0/interface_contract.egg-info/SOURCES.txt +25 -0
  14. interface_contract-0.4.0/interface_contract.egg-info/dependency_links.txt +1 -0
  15. interface_contract-0.4.0/interface_contract.egg-info/requires.txt +10 -0
  16. interface_contract-0.4.0/interface_contract.egg-info/top_level.txt +2 -0
  17. interface_contract-0.4.0/pyproject.toml +79 -0
  18. interface_contract-0.4.0/setup.cfg +4 -0
  19. interface_contract-0.4.0/strict_interface/__init__.py +85 -0
  20. interface_contract-0.4.0/strict_interface/_core.py +798 -0
  21. interface_contract-0.4.0/strict_interface/adapters.py +109 -0
  22. interface_contract-0.4.0/strict_interface/mypy_plugin.py +5 -0
  23. interface_contract-0.4.0/strict_interface/py.typed +0 -0
  24. interface_contract-0.4.0/test_interface_contract.py +201 -0
  25. interface_contract-0.4.0/test_strict_interface.py +668 -0
  26. interface_contract-0.4.0/typing_tests/incomplete.py +12 -0
  27. interface_contract-0.4.0/typing_tests/valid.py +18 -0
@@ -0,0 +1,33 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
5
+ ## 0.4.0 - 2026-08-30
6
+
7
+ ### Added
8
+
9
+ - New `interface_contract` import package and `interface-contract` distribution
10
+ name.
11
+ - Opt-in instance-field contracts through `check_attributes=True`.
12
+ - `AttributeSpec`, `attributes_of`, `missing_attributes`, `verify_instance`, and
13
+ `satisfies` inspection and validation APIs.
14
+ - Explicit adapter registries and process-wide adapter helpers.
15
+ - Optional mypy plugin for detecting incomplete implementations statically.
16
+ - English primary documentation and a Turkish companion guide.
17
+ - Dataclass compatibility coverage.
18
+
19
+ ### Compatibility
20
+
21
+ - `strict_interface` remains a supported alias with identical public objects.
22
+ - Existing annotation behavior is unchanged unless field checking is explicitly
23
+ enabled.
24
+
25
+ ## 0.3.0
26
+
27
+ - Added Python 3.14 support and fail-closed bytecode stub detection.
28
+ - Improved custom metaclass composition.
29
+
30
+ ## 0.2.0
31
+
32
+ - Made interface validation automatic at class-definition time.
33
+ - Added `abstract=True` for intentionally incomplete intermediate classes.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ayhan Öztemel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ include CHANGELOG.md
2
+ include LICENSE
3
+ include README.md
4
+ include README.tr.md
5
+ include test_*.py
6
+ recursive-include typing_tests *.py
@@ -0,0 +1,305 @@
1
+ Metadata-Version: 2.4
2
+ Name: interface-contract
3
+ Version: 0.4.0
4
+ Summary: Definition-time Python interface contracts with signature-aware runtime validation
5
+ Author: Ayhan Öztemel
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/AyhanOztemel/py_interface
8
+ Project-URL: Repository, https://github.com/AyhanOztemel/py_interface
9
+ Project-URL: Issues, https://github.com/AyhanOztemel/py_interface/issues
10
+ Project-URL: Changelog, https://github.com/AyhanOztemel/py_interface/blob/main/CHANGELOG.md
11
+ Keywords: interface,protocol,abc,contract,runtime,type-safety
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Programming Language :: Python :: Implementation :: CPython
22
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Provides-Extra: test
29
+ Requires-Dist: pytest>=7; extra == "test"
30
+ Requires-Dist: typeguard>=4; extra == "test"
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=7; extra == "dev"
33
+ Requires-Dist: typeguard>=4; extra == "dev"
34
+ Requires-Dist: mypy==2.3.1; extra == "dev"
35
+ Requires-Dist: ruff==0.16.5; extra == "dev"
36
+ Dynamic: license-file
37
+
38
+ # interface-contract
39
+
40
+ Strict runtime interface contracts for Python, with definition-time failures and
41
+ signature-aware structural checks.
42
+
43
+ ```bash
44
+ pip install interface-contract
45
+ ```
46
+
47
+ ```python
48
+ from interface_contract import Interface, default
49
+
50
+
51
+ class Repository(Interface):
52
+ def find(self, item_id: int) -> str: ...
53
+ def save(self, item: str) -> None: ...
54
+
55
+ @property
56
+ def name(self) -> str: ...
57
+
58
+ @default
59
+ def describe(self) -> str:
60
+ return f"repository<{self.name}>"
61
+
62
+
63
+ class SqlRepository(Repository):
64
+ def find(self, item_id: int) -> str:
65
+ return f"row {item_id}"
66
+
67
+ def save(self, item: str) -> None:
68
+ pass
69
+
70
+ @property
71
+ def name(self) -> str:
72
+ return "sql"
73
+ ```
74
+
75
+ If `SqlRepository` misses a required member, changes its descriptor kind, or has
76
+ an incompatible signature, class creation raises `InterfaceError`. You do not
77
+ need to wait until an instance is created or a method is called.
78
+
79
+ ## Why interface-contract?
80
+
81
+ Python already has `abc.ABC` and `typing.Protocol`; this package targets a
82
+ different boundary: strict runtime validation for plugin systems, application
83
+ architecture, dependency injection, and dynamically loaded code.
84
+
85
+ | Capability | `abc.ABC` | runtime `Protocol` | `interface-contract` |
86
+ |---|---:|---:|---:|
87
+ | Missing method detected at runtime | instantiation | `isinstance` | class definition |
88
+ | Runtime signature validation | no | no | yes |
89
+ | Property/static/class method kind validation | no | no | yes |
90
+ | Signature-aware structural `isinstance` | no | no | yes |
91
+ | Explicit default implementations | concrete method | concrete method | `@default` |
92
+ | Optional instance-field contracts | annotations only | presence only | presence + shallow type check |
93
+ | Runtime adapter registry | no | no | yes |
94
+
95
+ This is not a replacement for static typing. Use mypy or another type checker for
96
+ whole-program analysis, and use interface-contract where runtime boundaries must
97
+ fail loudly and predictably.
98
+
99
+ ## Core behavior
100
+
101
+ ### Definition-time validation
102
+
103
+ Concrete subclasses are checked as soon as their `class` statement executes.
104
+ Intermediate implementations can opt out until a concrete subclass is ready:
105
+
106
+ ```python
107
+ class BaseRepository(Repository, abstract=True):
108
+ def save(self, item: str) -> None:
109
+ pass
110
+
111
+
112
+ class MemoryRepository(BaseRepository):
113
+ def find(self, item_id: int) -> str:
114
+ return "row"
115
+
116
+ @property
117
+ def name(self) -> str:
118
+ return "memory"
119
+ ```
120
+
121
+ Abstract implementations cannot be instantiated.
122
+
123
+ ### Default methods
124
+
125
+ Interface methods normally declare requirements and therefore must have an empty
126
+ body (`...`, `pass`, or a docstring-only body). Mark intentional implementations
127
+ with `@default`:
128
+
129
+ ```python
130
+ from interface_contract import Interface, default
131
+
132
+
133
+ class Named(Interface):
134
+ @property
135
+ def name(self) -> str: ...
136
+
137
+ @default
138
+ def display_name(self) -> str:
139
+ return self.name.title()
140
+ ```
141
+
142
+ ### Structural interfaces
143
+
144
+ Set `structural=True` when inheritance is not under your control:
145
+
146
+ ```python
147
+ class Closable(Interface, structural=True):
148
+ def close(self) -> None: ...
149
+
150
+
151
+ class FileLike:
152
+ def close(self) -> None:
153
+ pass
154
+
155
+
156
+ assert isinstance(FileLike(), Closable)
157
+ assert issubclass(FileLike, Closable)
158
+ ```
159
+
160
+ Unlike runtime-checkable protocols, the structural check also validates callable
161
+ signatures and descriptor kinds.
162
+
163
+ ### Instance-field contracts
164
+
165
+ Field checking is opt-in, preserving compatibility with versions that ignored
166
+ class annotations:
167
+
168
+ ```python
169
+ class UserRecord(Interface, check_attributes=True):
170
+ name: str
171
+ age: int
172
+
173
+
174
+ class User(UserRecord):
175
+ def __init__(self, name: str, age: int) -> None:
176
+ self.name = name
177
+ self.age = age
178
+ ```
179
+
180
+ Fields are checked immediately after `__init__`. Standard annotations receive a
181
+ best-effort shallow runtime check; parameter contents such as every item inside
182
+ `list[str]` are intentionally not traversed. `ClassVar` does not declare an
183
+ instance field. Dataclass implementations are supported.
184
+
185
+ For objects that cannot inherit from an interface, use `verify_instance` or
186
+ `satisfies`:
187
+
188
+ ```python
189
+ from interface_contract import satisfies, verify_instance
190
+
191
+ verify_instance(User("Ada", 37), UserRecord) # returns the object or raises
192
+ assert satisfies(User("Ada", 37), UserRecord)
193
+ ```
194
+
195
+ ### Adapters
196
+
197
+ The registry converts an existing type to a target interface and validates the
198
+ result:
199
+
200
+ ```python
201
+ from interface_contract import AdapterRegistry
202
+
203
+ registry = AdapterRegistry()
204
+
205
+
206
+ @registry.register(dict, UserRecord)
207
+ def dict_to_user(data: dict[str, object]) -> User:
208
+ return User(name=str(data["name"]), age=int(data["age"]))
209
+
210
+
211
+ user = registry.adapt({"name": "Ada", "age": 37}, UserRecord)
212
+ ```
213
+
214
+ `adapt`, `can_adapt`, `register_adapter`, and `unregister_adapter` expose a
215
+ process-wide default registry when a dedicated registry is unnecessary.
216
+
217
+ ## Optional annotation checks
218
+
219
+ Call signatures are always checked for parameter shape. To also compare available
220
+ parameter and return annotations, enable `check_annotations=True`:
221
+
222
+ ```python
223
+ class Parser(Interface, check_annotations=True):
224
+ def parse(self, value: str) -> int: ...
225
+ ```
226
+
227
+ Runtime annotation comparison is deliberately conservative. It does not try to
228
+ replace a static type checker.
229
+
230
+ ## Mypy integration
231
+
232
+ The package is typed and ships an optional mypy plugin. It lets mypy reject the
233
+ instantiation of incomplete implementations before execution. No extra runtime
234
+ dependency is installed.
235
+
236
+ ```toml
237
+ [tool.mypy]
238
+ plugins = ["interface_contract.mypy_plugin"]
239
+ ```
240
+
241
+ ```python
242
+ class Job(Interface):
243
+ def execute(self, payload: str) -> int: ...
244
+
245
+
246
+ class Incomplete(Job):
247
+ pass
248
+
249
+
250
+ Incomplete() # mypy: Cannot instantiate abstract class "Incomplete"
251
+ ```
252
+
253
+ The mypy plugin API is itself experimental; runtime validation remains the source
254
+ of truth.
255
+
256
+ ## Supported members
257
+
258
+ - regular and async methods
259
+ - properties, including independent getter/setter/deleter requirements
260
+ - static methods and class methods
261
+ - generic interfaces
262
+ - multiple and derived interfaces
263
+ - custom metaclass composition
264
+ - source-less environments such as REPL, `exec`, notebooks, frozen apps, and
265
+ bytecode-only distributions
266
+
267
+ Useful inspection functions include `members_of`, `attributes_of`,
268
+ `missing_members`, `missing_attributes`, `signature_problem`, `verify`, and
269
+ `structurally_implements`.
270
+
271
+ ## Backward compatibility
272
+
273
+ The former import path remains fully supported:
274
+
275
+ ```python
276
+ from strict_interface import Interface
277
+ ```
278
+
279
+ `strict_interface.Interface` and `interface_contract.Interface` are the same
280
+ object. Existing source code does not need an import migration. The PyPI
281
+ distribution name is `interface-contract`; the preferred new import is
282
+ `interface_contract`.
283
+
284
+ Version 0.4.0 is additive except for the distribution rename. Runtime field
285
+ checking only activates when `check_attributes=True` is explicitly selected.
286
+
287
+ ## Development
288
+
289
+ ```bash
290
+ python -m pip install -e ".[dev]"
291
+ python -m pytest
292
+ python -m ruff check .
293
+ python -m mypy strict_interface interface_contract typing_tests/valid.py
294
+ python -m build
295
+ python -m twine check dist/*
296
+ ```
297
+
298
+ See [README.tr.md](https://github.com/AyhanOztemel/py_interface/blob/main/README.tr.md)
299
+ for Turkish documentation and
300
+ [CHANGELOG.md](https://github.com/AyhanOztemel/py_interface/blob/main/CHANGELOG.md)
301
+ for release notes.
302
+
303
+ ## License
304
+
305
+ MIT
@@ -0,0 +1,268 @@
1
+ # interface-contract
2
+
3
+ Strict runtime interface contracts for Python, with definition-time failures and
4
+ signature-aware structural checks.
5
+
6
+ ```bash
7
+ pip install interface-contract
8
+ ```
9
+
10
+ ```python
11
+ from interface_contract import Interface, default
12
+
13
+
14
+ class Repository(Interface):
15
+ def find(self, item_id: int) -> str: ...
16
+ def save(self, item: str) -> None: ...
17
+
18
+ @property
19
+ def name(self) -> str: ...
20
+
21
+ @default
22
+ def describe(self) -> str:
23
+ return f"repository<{self.name}>"
24
+
25
+
26
+ class SqlRepository(Repository):
27
+ def find(self, item_id: int) -> str:
28
+ return f"row {item_id}"
29
+
30
+ def save(self, item: str) -> None:
31
+ pass
32
+
33
+ @property
34
+ def name(self) -> str:
35
+ return "sql"
36
+ ```
37
+
38
+ If `SqlRepository` misses a required member, changes its descriptor kind, or has
39
+ an incompatible signature, class creation raises `InterfaceError`. You do not
40
+ need to wait until an instance is created or a method is called.
41
+
42
+ ## Why interface-contract?
43
+
44
+ Python already has `abc.ABC` and `typing.Protocol`; this package targets a
45
+ different boundary: strict runtime validation for plugin systems, application
46
+ architecture, dependency injection, and dynamically loaded code.
47
+
48
+ | Capability | `abc.ABC` | runtime `Protocol` | `interface-contract` |
49
+ |---|---:|---:|---:|
50
+ | Missing method detected at runtime | instantiation | `isinstance` | class definition |
51
+ | Runtime signature validation | no | no | yes |
52
+ | Property/static/class method kind validation | no | no | yes |
53
+ | Signature-aware structural `isinstance` | no | no | yes |
54
+ | Explicit default implementations | concrete method | concrete method | `@default` |
55
+ | Optional instance-field contracts | annotations only | presence only | presence + shallow type check |
56
+ | Runtime adapter registry | no | no | yes |
57
+
58
+ This is not a replacement for static typing. Use mypy or another type checker for
59
+ whole-program analysis, and use interface-contract where runtime boundaries must
60
+ fail loudly and predictably.
61
+
62
+ ## Core behavior
63
+
64
+ ### Definition-time validation
65
+
66
+ Concrete subclasses are checked as soon as their `class` statement executes.
67
+ Intermediate implementations can opt out until a concrete subclass is ready:
68
+
69
+ ```python
70
+ class BaseRepository(Repository, abstract=True):
71
+ def save(self, item: str) -> None:
72
+ pass
73
+
74
+
75
+ class MemoryRepository(BaseRepository):
76
+ def find(self, item_id: int) -> str:
77
+ return "row"
78
+
79
+ @property
80
+ def name(self) -> str:
81
+ return "memory"
82
+ ```
83
+
84
+ Abstract implementations cannot be instantiated.
85
+
86
+ ### Default methods
87
+
88
+ Interface methods normally declare requirements and therefore must have an empty
89
+ body (`...`, `pass`, or a docstring-only body). Mark intentional implementations
90
+ with `@default`:
91
+
92
+ ```python
93
+ from interface_contract import Interface, default
94
+
95
+
96
+ class Named(Interface):
97
+ @property
98
+ def name(self) -> str: ...
99
+
100
+ @default
101
+ def display_name(self) -> str:
102
+ return self.name.title()
103
+ ```
104
+
105
+ ### Structural interfaces
106
+
107
+ Set `structural=True` when inheritance is not under your control:
108
+
109
+ ```python
110
+ class Closable(Interface, structural=True):
111
+ def close(self) -> None: ...
112
+
113
+
114
+ class FileLike:
115
+ def close(self) -> None:
116
+ pass
117
+
118
+
119
+ assert isinstance(FileLike(), Closable)
120
+ assert issubclass(FileLike, Closable)
121
+ ```
122
+
123
+ Unlike runtime-checkable protocols, the structural check also validates callable
124
+ signatures and descriptor kinds.
125
+
126
+ ### Instance-field contracts
127
+
128
+ Field checking is opt-in, preserving compatibility with versions that ignored
129
+ class annotations:
130
+
131
+ ```python
132
+ class UserRecord(Interface, check_attributes=True):
133
+ name: str
134
+ age: int
135
+
136
+
137
+ class User(UserRecord):
138
+ def __init__(self, name: str, age: int) -> None:
139
+ self.name = name
140
+ self.age = age
141
+ ```
142
+
143
+ Fields are checked immediately after `__init__`. Standard annotations receive a
144
+ best-effort shallow runtime check; parameter contents such as every item inside
145
+ `list[str]` are intentionally not traversed. `ClassVar` does not declare an
146
+ instance field. Dataclass implementations are supported.
147
+
148
+ For objects that cannot inherit from an interface, use `verify_instance` or
149
+ `satisfies`:
150
+
151
+ ```python
152
+ from interface_contract import satisfies, verify_instance
153
+
154
+ verify_instance(User("Ada", 37), UserRecord) # returns the object or raises
155
+ assert satisfies(User("Ada", 37), UserRecord)
156
+ ```
157
+
158
+ ### Adapters
159
+
160
+ The registry converts an existing type to a target interface and validates the
161
+ result:
162
+
163
+ ```python
164
+ from interface_contract import AdapterRegistry
165
+
166
+ registry = AdapterRegistry()
167
+
168
+
169
+ @registry.register(dict, UserRecord)
170
+ def dict_to_user(data: dict[str, object]) -> User:
171
+ return User(name=str(data["name"]), age=int(data["age"]))
172
+
173
+
174
+ user = registry.adapt({"name": "Ada", "age": 37}, UserRecord)
175
+ ```
176
+
177
+ `adapt`, `can_adapt`, `register_adapter`, and `unregister_adapter` expose a
178
+ process-wide default registry when a dedicated registry is unnecessary.
179
+
180
+ ## Optional annotation checks
181
+
182
+ Call signatures are always checked for parameter shape. To also compare available
183
+ parameter and return annotations, enable `check_annotations=True`:
184
+
185
+ ```python
186
+ class Parser(Interface, check_annotations=True):
187
+ def parse(self, value: str) -> int: ...
188
+ ```
189
+
190
+ Runtime annotation comparison is deliberately conservative. It does not try to
191
+ replace a static type checker.
192
+
193
+ ## Mypy integration
194
+
195
+ The package is typed and ships an optional mypy plugin. It lets mypy reject the
196
+ instantiation of incomplete implementations before execution. No extra runtime
197
+ dependency is installed.
198
+
199
+ ```toml
200
+ [tool.mypy]
201
+ plugins = ["interface_contract.mypy_plugin"]
202
+ ```
203
+
204
+ ```python
205
+ class Job(Interface):
206
+ def execute(self, payload: str) -> int: ...
207
+
208
+
209
+ class Incomplete(Job):
210
+ pass
211
+
212
+
213
+ Incomplete() # mypy: Cannot instantiate abstract class "Incomplete"
214
+ ```
215
+
216
+ The mypy plugin API is itself experimental; runtime validation remains the source
217
+ of truth.
218
+
219
+ ## Supported members
220
+
221
+ - regular and async methods
222
+ - properties, including independent getter/setter/deleter requirements
223
+ - static methods and class methods
224
+ - generic interfaces
225
+ - multiple and derived interfaces
226
+ - custom metaclass composition
227
+ - source-less environments such as REPL, `exec`, notebooks, frozen apps, and
228
+ bytecode-only distributions
229
+
230
+ Useful inspection functions include `members_of`, `attributes_of`,
231
+ `missing_members`, `missing_attributes`, `signature_problem`, `verify`, and
232
+ `structurally_implements`.
233
+
234
+ ## Backward compatibility
235
+
236
+ The former import path remains fully supported:
237
+
238
+ ```python
239
+ from strict_interface import Interface
240
+ ```
241
+
242
+ `strict_interface.Interface` and `interface_contract.Interface` are the same
243
+ object. Existing source code does not need an import migration. The PyPI
244
+ distribution name is `interface-contract`; the preferred new import is
245
+ `interface_contract`.
246
+
247
+ Version 0.4.0 is additive except for the distribution rename. Runtime field
248
+ checking only activates when `check_attributes=True` is explicitly selected.
249
+
250
+ ## Development
251
+
252
+ ```bash
253
+ python -m pip install -e ".[dev]"
254
+ python -m pytest
255
+ python -m ruff check .
256
+ python -m mypy strict_interface interface_contract typing_tests/valid.py
257
+ python -m build
258
+ python -m twine check dist/*
259
+ ```
260
+
261
+ See [README.tr.md](https://github.com/AyhanOztemel/py_interface/blob/main/README.tr.md)
262
+ for Turkish documentation and
263
+ [CHANGELOG.md](https://github.com/AyhanOztemel/py_interface/blob/main/CHANGELOG.md)
264
+ for release notes.
265
+
266
+ ## License
267
+
268
+ MIT