interface-contract 0.4.0__py3-none-any.whl

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,798 @@
1
+ """Cekirdek uygulama. Genel API icin `strict_interface/__init__.py`ye bakin."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ import inspect
7
+ import textwrap
8
+ import types
9
+ from collections.abc import Callable
10
+ from typing import (
11
+ Annotated,
12
+ Any,
13
+ ClassVar,
14
+ Literal,
15
+ TypeVar,
16
+ Union,
17
+ get_args,
18
+ get_origin,
19
+ get_type_hints,
20
+ )
21
+
22
+ _FLAG = "__is_interface__"
23
+ _MEMBERS = "__interface_members__"
24
+ _VERIFIED = "__interface_verified__"
25
+ _STRUCTURAL = "__interface_structural__"
26
+ _DEFAULT = "__interface_default__"
27
+ _ANNOTATED = "__interface_check_annotations__"
28
+ _ABSTRACT = "__interface_abstract__"
29
+ _ATTRIBUTES = "__interface_attributes__"
30
+ _CHECK_ATTRIBUTES = "__interface_check_attributes__"
31
+
32
+
33
+ class InterfaceError(TypeError):
34
+ """Arayuz sozlesmesi ihlal edildiginde atilir."""
35
+
36
+
37
+ # --------------------------------------------------------------------------- #
38
+ # Govde bosluk kontrolu
39
+ #
40
+ # Birincil yontem AST: surumler arasi kararli ve okunabilir.
41
+ # Kaynak koda ulasilamadiginda (REPL, exec, notebook, frozen app, sadece .pyc)
42
+ # bytecode'a dusuluyor.
43
+ #
44
+ # Kaynak bulunamazsa kabul edilen bos govdeler calisan yorumlayicida derlenir.
45
+ # Aday govde, bunlardan uretilen tam kod imzalarindan biriyle eslesmelidir.
46
+ # Opcode adlari veya farkli govdelerden birlestirilmis bir izin listesi
47
+ # kullanilmaz. Boylece gercek bir govde parca parca stub komutlarindan olussa
48
+ # bile kabul edilmez; PyPy gibi yorumlayicilar da kendi imzalarini kalibre eder.
49
+ # --------------------------------------------------------------------------- #
50
+
51
+ # Kabul ettigimiz butun bos govde bicimleri. Kod imzalari bunlardan uretilir.
52
+ _STUB_FORMS = (
53
+ "def _s(self): pass",
54
+ "def _s(self): ...",
55
+ "def _s(self): 'dokuman'",
56
+ "def _s(self): raise NotImplementedError",
57
+ "def _s(self): raise NotImplementedError('mesaj')",
58
+ "async def _s(self): pass",
59
+ "async def _s(self): ...",
60
+ "async def _s(self): 'dokuman'",
61
+ "async def _s(self): raise NotImplementedError",
62
+ "async def _s(self): raise NotImplementedError('mesaj')",
63
+ )
64
+
65
+ # Senkron, coroutine ve generator govdeleri birbirinden ayiran davranis bitleri.
66
+ _CODE_FLAGS = inspect.CO_COROUTINE | inspect.CO_GENERATOR | inspect.CO_ASYNC_GENERATOR
67
+ _CodeShape = tuple[bytes, tuple[str, ...], tuple[str, ...], int]
68
+
69
+
70
+ def _code_shape(code: Any) -> _CodeShape | None:
71
+ raw = getattr(code, "co_code", None)
72
+ constants = getattr(code, "co_consts", None)
73
+ names = getattr(code, "co_names", None)
74
+ flags = getattr(code, "co_flags", None)
75
+ if (
76
+ not isinstance(raw, bytes)
77
+ or not isinstance(constants, tuple)
78
+ or not isinstance(names, tuple)
79
+ or not isinstance(flags, int)
80
+ ):
81
+ return None
82
+ const_shape = tuple(
83
+ "none" if value is None else "ellipsis" if value is Ellipsis else "literal"
84
+ for value in constants
85
+ )
86
+ return raw, const_shape, tuple(map(str, names)), flags & _CODE_FLAGS
87
+
88
+
89
+ def _calibrate_stub_shapes() -> frozenset[_CodeShape]:
90
+ """Bos govde kod imzalarini calisan yorumlayicidan ogrenir."""
91
+ shapes: set[_CodeShape] = set()
92
+ for source in _STUB_FORMS:
93
+ namespace: dict[str, Any] = {}
94
+ # S102: derlenen kaynak _STUB_FORMS'tan geliyor, disaridan girdi almiyor.
95
+ exec(compile(source, "<strict_interface>", "exec"), namespace) # noqa: S102
96
+ shape = _code_shape(namespace["_s"].__code__)
97
+ if shape is not None:
98
+ shapes.add(shape)
99
+ return frozenset(shapes)
100
+
101
+
102
+ _STUB_SHAPES = _calibrate_stub_shapes()
103
+
104
+
105
+ def _ast_is_stub(func: Callable[..., Any]) -> bool | None:
106
+ try:
107
+ source = textwrap.dedent(inspect.getsource(func))
108
+ except (OSError, TypeError):
109
+ return None
110
+ try:
111
+ module = ast.parse(source)
112
+ except SyntaxError:
113
+ return None
114
+ if not module.body:
115
+ return None
116
+ node = module.body[0]
117
+ if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
118
+ return None
119
+
120
+ body = list(node.body)
121
+ if (body and isinstance(body[0], ast.Expr)
122
+ and isinstance(body[0].value, ast.Constant)
123
+ and isinstance(body[0].value.value, str)):
124
+ body = body[1:] # docstring
125
+ if not body:
126
+ return True
127
+
128
+ for stmt in body:
129
+ if isinstance(stmt, ast.Pass):
130
+ continue
131
+ if (isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Constant)
132
+ and stmt.value.value is Ellipsis):
133
+ continue
134
+ if isinstance(stmt, ast.Raise) and stmt.exc is not None:
135
+ exc = stmt.exc
136
+ target = exc.func if isinstance(exc, ast.Call) else exc
137
+ if isinstance(target, ast.Name) and target.id == "NotImplementedError":
138
+ continue
139
+ return False
140
+ return True
141
+
142
+
143
+ def _bytecode_is_stub(func: Callable[..., Any]) -> bool:
144
+ code = getattr(func, "__code__", None)
145
+ if code is None:
146
+ return False
147
+ shape = _code_shape(code)
148
+ return shape is not None and shape in _STUB_SHAPES
149
+
150
+
151
+ def is_stub(func: Callable[..., Any]) -> bool:
152
+ """Govde `pass`, `...`, sadece docstring veya `raise NotImplementedError` mi?"""
153
+ verdict = _ast_is_stub(func)
154
+ return _bytecode_is_stub(func) if verdict is None else verdict
155
+
156
+
157
+ # --------------------------------------------------------------------------- #
158
+ # Uyeler
159
+ # --------------------------------------------------------------------------- #
160
+ class Member:
161
+ """Arayuzun bir uyesi: metot, property, staticmethod veya classmethod."""
162
+
163
+ __slots__ = ("is_async", "is_default", "kind", "name", "owner", "parts")
164
+
165
+ def __init__(self, name: str, kind: str, parts: dict[str, Callable[..., Any]],
166
+ is_async: bool, owner: str, is_default: bool = False) -> None:
167
+ self.name = name
168
+ self.kind = kind
169
+ self.parts = parts # property icin fget/fset/fdel, digerleri icin ""
170
+ self.is_async = is_async
171
+ self.owner = owner
172
+ self.is_default = is_default
173
+
174
+ @property
175
+ def primary(self) -> Callable[..., Any] | None:
176
+ return self.parts.get("") or self.parts.get("fget")
177
+
178
+ def __repr__(self) -> str:
179
+ flag = " (default)" if self.is_default else ""
180
+ return f"<{self.kind} {self.owner}.{self.name}{flag}>"
181
+
182
+
183
+ class AttributeSpec:
184
+ """An opt-in instance-attribute requirement declared by an interface."""
185
+
186
+ __slots__ = ("annotation", "name", "owner")
187
+
188
+ def __init__(self, name: str, annotation: Any, owner: str) -> None:
189
+ self.name = name
190
+ self.annotation = annotation
191
+ self.owner = owner
192
+
193
+ def __repr__(self) -> str:
194
+ return f"<attribute {self.owner}.{self.name}: {self.annotation!r}>"
195
+
196
+
197
+ def _has_default(raw: Any) -> bool:
198
+ target = raw.__func__ if isinstance(raw, (staticmethod, classmethod)) else raw
199
+ if isinstance(raw, property):
200
+ target = raw.fget
201
+ return bool(getattr(target, _DEFAULT, False))
202
+
203
+
204
+ def classify(name: str, value: Any, owner: str) -> Member | None:
205
+ """Sinif sozlugundeki bir girdiyi Member'a cevirir; sozlesme disi ise None."""
206
+ is_default = _has_default(value)
207
+ if isinstance(value, staticmethod):
208
+ fn = value.__func__
209
+ return Member(name, "staticmethod", {"": fn},
210
+ inspect.iscoroutinefunction(fn), owner, is_default)
211
+ if isinstance(value, classmethod):
212
+ fn = value.__func__
213
+ return Member(name, "classmethod", {"": fn},
214
+ inspect.iscoroutinefunction(fn), owner, is_default)
215
+ if isinstance(value, property):
216
+ parts = {k: f for k, f in
217
+ (("fget", value.fget), ("fset", value.fset), ("fdel", value.fdel))
218
+ if f is not None}
219
+ return Member(name, "property", parts, False, owner, is_default)
220
+ if inspect.isfunction(value):
221
+ return Member(name, "method", {"": value},
222
+ inspect.iscoroutinefunction(value), owner, is_default)
223
+ return None # sabitler ve sinif degiskenleri sozlesmeye dahil degil
224
+
225
+
226
+ # --------------------------------------------------------------------------- #
227
+ # Imza uyumu
228
+ # --------------------------------------------------------------------------- #
229
+ _P = inspect.Parameter
230
+
231
+
232
+ def _is_catch_all(params: list[_P]) -> bool:
233
+ kinds = {p.kind for p in params}
234
+ return _P.VAR_POSITIONAL in kinds and _P.VAR_KEYWORD in kinds
235
+
236
+
237
+ def signature_problem(declared: Callable[..., Any], impl: Callable[..., Any],
238
+ check_annotations: bool = False) -> str | None:
239
+ """Imzalar uyumsuzsa aciklama, uyumluysa None dondurur."""
240
+ try:
241
+ dsig = inspect.signature(declared)
242
+ isig = inspect.signature(impl)
243
+ except (TypeError, ValueError):
244
+ return None # C seviyesinde cagrilabilir; imza okunamiyor
245
+
246
+ d = list(dsig.parameters.values())
247
+ i = list(isig.parameters.values())
248
+
249
+ if _is_catch_all(i):
250
+ return None # (*args, **kwargs) esnek imzaya izin ver
251
+
252
+ if len(i) < len(d):
253
+ return (f"beklenen ({', '.join(p.name for p in d)}), "
254
+ f"verilen ({', '.join(p.name for p in i)}) — eksik parametre")
255
+
256
+ # strict=False bilincli: implementasyon fazladan opsiyonel parametre
257
+ # tanimlayabilir, fazlaliklar asagida ayrica denetleniyor.
258
+ for want, got in zip(d, i, strict=False):
259
+ if want.name != got.name:
260
+ return f"parametre adi '{want.name}' olmali, '{got.name}' verilmis"
261
+ if want.kind is not got.kind:
262
+ return f"parametre '{want.name}' turu {want.kind.description} olmali"
263
+ if (check_annotations and want.annotation is not _P.empty
264
+ and got.annotation is not _P.empty
265
+ and want.annotation != got.annotation):
266
+ return (f"parametre '{want.name}' anotasyonu {want.annotation!r} "
267
+ f"olmali, {got.annotation!r} verilmis")
268
+
269
+ for extra in i[len(d):]:
270
+ if extra.default is _P.empty and extra.kind not in (
271
+ _P.VAR_POSITIONAL, _P.VAR_KEYWORD):
272
+ return f"fazladan zorunlu parametre '{extra.name}' — varsayilan deger verin"
273
+
274
+ if (check_annotations and dsig.return_annotation is not inspect.Signature.empty
275
+ and isig.return_annotation is not inspect.Signature.empty
276
+ and dsig.return_annotation != isig.return_annotation):
277
+ return (f"donus anotasyonu {dsig.return_annotation!r} olmali, "
278
+ f"{isig.return_annotation!r} verilmis")
279
+ return None
280
+
281
+
282
+ def member_problem(declared: Member, impl: Member,
283
+ check_annotations: bool = False) -> str | None:
284
+ if impl.kind != declared.kind:
285
+ return f"{declared.kind} olmali, {impl.kind} olarak tanimlanmis"
286
+ if impl.is_async != declared.is_async:
287
+ return ("async def olarak tanimlanmali" if declared.is_async
288
+ else "async olmayan def olarak tanimlanmali")
289
+ for part, decl_fn in declared.parts.items():
290
+ impl_fn = impl.parts.get(part)
291
+ if impl_fn is None:
292
+ label = {"fget": "okuyucu", "fset": "setter", "fdel": "deleter"}.get(part, part)
293
+ return f"property {label}'si eksik"
294
+ problem = signature_problem(decl_fn, impl_fn, check_annotations)
295
+ if problem:
296
+ prefix = f"{part} " if part else ""
297
+ return f"{prefix}imzasi uyusmuyor: {problem}"
298
+ return None
299
+
300
+
301
+ # --------------------------------------------------------------------------- #
302
+ # Metaclass
303
+ # --------------------------------------------------------------------------- #
304
+ def _is_allowed_base(base: type) -> bool:
305
+ """Arayuzun turetilebilecegi taban mi? (arayuz, object, typing yardimcilari)"""
306
+ if base is object:
307
+ return True
308
+ if getattr(base, "__module__", "") in ("typing", "typing_extensions"):
309
+ return True
310
+ return is_interface(base)
311
+
312
+
313
+ def _looks_like_interface(namespace: dict[str, Any]) -> bool:
314
+ """Govdesi tamamen bos olan bir sinif — muhtemelen arayuz olmasi isteniyordu."""
315
+ found = False
316
+ for key, value in namespace.items():
317
+ if key.startswith("__") and key.endswith("__"):
318
+ continue
319
+ member = classify(key, value, "?")
320
+ if member is None:
321
+ continue
322
+ found = True
323
+ if not all(is_stub(fn) for fn in member.parts.values()):
324
+ return False
325
+ return found
326
+
327
+
328
+ def _with_interface_hint(exc: InterfaceError, cls: type,
329
+ namespace: dict[str, Any]) -> InterfaceError:
330
+ problems = [line for line in str(exc).splitlines() if line.startswith(" - ")]
331
+ if not problems or not all("implement edilmemis" in p for p in problems):
332
+ return exc # imza/tur hatasi var; bu bir arayuz karisikligi degil
333
+ if not _looks_like_interface(namespace):
334
+ return exc
335
+ return InterfaceError(
336
+ f"{exc}\n\n"
337
+ f"Ipucu: {cls.__name__} govdesi tamamen bos. Bunun bir arayuz olmasini "
338
+ f"istiyorsaniz `Interface`i taban olarak listeleyin veya `interface=True` "
339
+ f"verin:\n"
340
+ f" class {cls.__name__}(..., Interface): ...\n"
341
+ f" class {cls.__name__}(..., interface=True): ...\n"
342
+ f"Sozlesmeyi kasten kismen dolduran bir ara sinif ise `abstract=True` verin.")
343
+
344
+
345
+ class InterfaceMeta(type):
346
+ """Arayuz semantigini tasiyan metaclass.
347
+
348
+ - arayuzler ornekleneemez, hata mesaji anlamlidir
349
+ - bir arayuzden turetilen her sinif **tanim aninda** dogrulanir; kismen
350
+ dolduran ara siniflar icin `abstract=True` verin
351
+ - sinifa sonradan atama yapilirsa dogrulama onbellegi gecersizlenir
352
+ - structural=True verilen arayuzlerde isinstance/issubclass mirassiz calisir
353
+ """
354
+
355
+ def __new__(mcls, name: str, bases: tuple[type, ...], namespace: dict[str, Any],
356
+ *, interface: bool | None = None, structural: bool = False,
357
+ name_prefix: str | None = None, strict_body: bool = True,
358
+ check_annotations: bool = False, abstract: bool = False,
359
+ check_attributes: bool | None = None,
360
+ **kwargs: Any) -> InterfaceMeta:
361
+ cls = super().__new__(mcls, name, bases, dict(namespace), **kwargs)
362
+
363
+ root = globals().get("Interface")
364
+ if interface is None:
365
+ interface = bool(namespace.get(_FLAG)) or (root is not None and root in bases)
366
+
367
+ type.__setattr__(cls, _FLAG, bool(interface))
368
+ type.__setattr__(cls, _VERIFIED, False)
369
+
370
+ # --- implementasyon yolu ------------------------------------------- #
371
+ if not interface:
372
+ if abstract:
373
+ # sozlesmeyi kasten kismen dolduran ara sinif: dogrulama
374
+ # somut alt sinifa ertelenir
375
+ type.__setattr__(cls, _ABSTRACT, True)
376
+ return cls
377
+ if any(is_interface(base) for base in cls.__mro__[1:]):
378
+ try:
379
+ verify(cls)
380
+ except InterfaceError as exc:
381
+ raise _with_interface_hint(exc, cls, namespace) from None
382
+ return cls
383
+
384
+ # --- arayuz yolu ---------------------------------------------------- #
385
+ if check_attributes is None:
386
+ check_attributes = any(
387
+ bool(base.__dict__.get(_CHECK_ATTRIBUTES, False))
388
+ for base in cls.__mro__[1:] if is_interface(base)
389
+ )
390
+ type.__setattr__(cls, _STRUCTURAL, structural)
391
+ type.__setattr__(cls, _ANNOTATED, check_annotations)
392
+ type.__setattr__(cls, _CHECK_ATTRIBUTES, bool(check_attributes))
393
+
394
+ problems: list[str] = []
395
+ if name_prefix and not name.startswith(name_prefix):
396
+ problems.append(f"arayuz adi '{name_prefix}' ile baslamali")
397
+
398
+ members: dict[str, Member] = {}
399
+ attributes: dict[str, AttributeSpec] = {}
400
+ for base in reversed(cls.__mro__[1:]):
401
+ if is_interface(base):
402
+ members.update(base.__dict__.get(_MEMBERS, {}))
403
+ attributes.update(base.__dict__.get(_ATTRIBUTES, {}))
404
+ elif not _is_allowed_base(base):
405
+ problems.append(f"arayuz yalnizca arayuzlerden turetilebilir; "
406
+ f"'{base.__name__}' bir arayuz degil")
407
+
408
+ for key, value in namespace.items():
409
+ if key.startswith("__") and key.endswith("__"):
410
+ if key in ("__init__", "__new__"):
411
+ problems.append(f"arayuz '{key}' tanimlayamaz")
412
+ continue
413
+ member = classify(key, value, name)
414
+ if member is None:
415
+ continue
416
+ if strict_body and not member.is_default:
417
+ for part, fn in member.parts.items():
418
+ if not is_stub(fn):
419
+ where = f"{key}.{part}" if part and part != "fget" else key
420
+ problems.append(
421
+ f"'{where}' govdesi bos olmali (`...`, `pass`, docstring "
422
+ f"veya `raise NotImplementedError`); govdeli metot icin "
423
+ f"@default kullanin")
424
+ members[key] = member
425
+
426
+ if check_attributes:
427
+ raw_annotations = namespace.get("__annotations__", {})
428
+ try:
429
+ resolved_annotations = get_type_hints(cls, include_extras=True)
430
+ except (NameError, TypeError):
431
+ resolved_annotations = {}
432
+ for key, raw_annotation in raw_annotations.items():
433
+ annotation = resolved_annotations.get(key, raw_annotation)
434
+ if key.startswith("_") or get_origin(annotation) is ClassVar:
435
+ continue
436
+ attributes[key] = AttributeSpec(key, annotation, name)
437
+
438
+ if problems:
439
+ raise InterfaceError(
440
+ f"{name} arayuzu gecersiz:\n - " + "\n - ".join(problems))
441
+
442
+ type.__setattr__(cls, _MEMBERS, members)
443
+ type.__setattr__(cls, _ATTRIBUTES, attributes)
444
+ return cls
445
+
446
+ # --- ornekleme -------------------------------------------------------- #
447
+ def __call__(cls, *args: Any, **kwargs: Any) -> Any:
448
+ if cls.__dict__.get(_FLAG, False):
449
+ raise InterfaceError(
450
+ f"{cls.__name__} bir arayuzdur, ornegi olusturulamaz.")
451
+ if cls.__dict__.get(_ABSTRACT, False):
452
+ raise InterfaceError(
453
+ f"{cls.__name__} soyut bir sinif (abstract=True), "
454
+ f"ornegi olusturulamaz.")
455
+ if not cls.__dict__.get(_VERIFIED, False):
456
+ verify(cls)
457
+ instance = super().__call__(*args, **kwargs)
458
+ verify_instance(instance)
459
+ return instance
460
+
461
+ # --- onbellek gecersizleme -------------------------------------------- #
462
+ def __setattr__(cls, key: str, value: Any) -> None:
463
+ type.__setattr__(cls, key, value)
464
+ if key not in (
465
+ _VERIFIED, _MEMBERS, _FLAG, _STRUCTURAL, _ANNOTATED, _ABSTRACT,
466
+ _ATTRIBUTES, _CHECK_ATTRIBUTES,
467
+ ):
468
+ _invalidate(cls)
469
+
470
+ def __delattr__(cls, key: str) -> None:
471
+ type.__delattr__(cls, key)
472
+ _invalidate(cls)
473
+
474
+ # --- yapisal tipleme --------------------------------------------------- #
475
+ def __instancecheck__(cls, obj: Any) -> bool:
476
+ if super().__instancecheck__(obj):
477
+ return True
478
+ if not (cls.__dict__.get(_FLAG) and cls.__dict__.get(_STRUCTURAL)):
479
+ return False
480
+ return satisfies(obj, cls)
481
+
482
+ def __subclasscheck__(cls, sub: type) -> bool:
483
+ # super() ile zincirleniyor ki InterfaceMeta baska bir metaclass ile
484
+ # birlestirildiginde (ornegin ABCMeta) onun register() semantigi yasasin.
485
+ if super().__subclasscheck__(sub):
486
+ return True
487
+ if not (cls.__dict__.get(_FLAG) and cls.__dict__.get(_STRUCTURAL)):
488
+ return False
489
+ return structurally_implements(sub, cls)
490
+
491
+
492
+ def _invalidate(cls: type) -> None:
493
+ type.__setattr__(cls, _VERIFIED, False)
494
+ for sub in cls.__subclasses__():
495
+ _invalidate(sub)
496
+
497
+
498
+ # --------------------------------------------------------------------------- #
499
+ # Genel yardimcilar
500
+ # --------------------------------------------------------------------------- #
501
+ def is_interface(obj: Any) -> bool:
502
+ """Sinifin *kendisi* arayuz mu? Implementasyonlar icin False."""
503
+ return isinstance(obj, type) and obj.__dict__.get(_FLAG, False)
504
+
505
+
506
+ class Interface(metaclass=InterfaceMeta, interface=True):
507
+ """Tum arayuzlerin kok sinifi.
508
+
509
+ `class IFoo(Interface): ...` seklinde turetin. Dogrudan `Interface`
510
+ listelemeyen alt siniflar implementasyon sayilir; turetilmis bir arayuz
511
+ yaziyorsaniz `Interface`i tekrar listeleyin veya `interface=True` verin:
512
+
513
+ class IAudited(IRepository, Interface): ...
514
+ class IAudited(IRepository, interface=True): ...
515
+ """
516
+
517
+ __slots__ = ()
518
+
519
+ def __init_subclass__(
520
+ cls,
521
+ *,
522
+ interface: bool | None = None,
523
+ structural: bool = False,
524
+ name_prefix: str | None = None,
525
+ strict_body: bool = True,
526
+ check_annotations: bool = False,
527
+ abstract: bool = False,
528
+ check_attributes: bool | None = None,
529
+ **kwargs: Any,
530
+ ) -> None:
531
+ """Document class keywords for type checkers; InterfaceMeta consumes them."""
532
+ super().__init_subclass__(**kwargs)
533
+
534
+
535
+ def is_abstract(obj: Any) -> bool:
536
+ """`abstract=True` ile tanimlanmis, sozlesmeyi kismen dolduran ara sinif mi?"""
537
+ return isinstance(obj, type) and obj.__dict__.get(_ABSTRACT, False)
538
+
539
+
540
+ def members_of(cls: type) -> dict[str, Member]:
541
+ """Sinifin uymasi gereken tum arayuz uyeleri (default'lar dahil)."""
542
+ collected: dict[str, Member] = {}
543
+ for base in reversed(getattr(cls, "__mro__", (cls,))):
544
+ if is_interface(base):
545
+ collected.update(base.__dict__.get(_MEMBERS, {}))
546
+ return collected
547
+
548
+
549
+ def attributes_of(cls: type) -> dict[str, AttributeSpec]:
550
+ """Return opt-in instance-attribute requirements inherited by ``cls``."""
551
+ collected: dict[str, AttributeSpec] = {}
552
+ for base in reversed(getattr(cls, "__mro__", (cls,))):
553
+ if is_interface(base):
554
+ collected.update(base.__dict__.get(_ATTRIBUTES, {}))
555
+ return collected
556
+
557
+
558
+ def _required(cls: type) -> dict[str, Member]:
559
+ return {n: m for n, m in members_of(cls).items() if not m.is_default}
560
+
561
+
562
+ def _find_implementation(cls: type, name: str) -> tuple[Any, type] | None:
563
+ for base in cls.__mro__:
564
+ if is_interface(base):
565
+ continue
566
+ if name in vars(base):
567
+ return vars(base)[name], base
568
+ return None
569
+
570
+
571
+ def missing_members(cls: type) -> list[str]:
572
+ """Implement edilmemis zorunlu uyelerin adlari."""
573
+ return [n for n in _required(cls) if _find_implementation(cls, n) is None]
574
+
575
+
576
+ def _concrete_attribute_owner(cls: type, name: str) -> type | None:
577
+ for base in cls.__mro__:
578
+ if is_interface(base):
579
+ continue
580
+ if name in vars(base):
581
+ return base
582
+ return None
583
+
584
+
585
+ def _annotation_matches(value: Any, annotation: Any) -> bool:
586
+ """Best-effort, shallow runtime check for a standard type annotation."""
587
+ if annotation is Any or annotation is inspect.Signature.empty:
588
+ return True
589
+ if annotation is None:
590
+ return value is None
591
+ if isinstance(annotation, str):
592
+ return True # Forward references need the defining module namespace.
593
+ if isinstance(annotation, TypeVar):
594
+ if annotation.__constraints__:
595
+ return any(_annotation_matches(value, item) for item in annotation.__constraints__)
596
+ return _annotation_matches(value, annotation.__bound__) if annotation.__bound__ else True
597
+
598
+ origin = get_origin(annotation)
599
+ args = get_args(annotation)
600
+ if origin is Annotated:
601
+ return _annotation_matches(value, args[0]) if args else True
602
+ if origin in (Union, types.UnionType):
603
+ return any(_annotation_matches(value, item) for item in args)
604
+ if origin is Literal:
605
+ return value in args
606
+ if origin is ClassVar:
607
+ return True
608
+ if origin is not None:
609
+ try:
610
+ return isinstance(value, origin)
611
+ except TypeError:
612
+ return True
613
+ try:
614
+ return isinstance(value, annotation)
615
+ except TypeError:
616
+ return True
617
+
618
+
619
+ def _instance_attribute_value(obj: Any, name: str) -> tuple[bool, Any]:
620
+ try:
621
+ namespace = vars(obj)
622
+ except TypeError:
623
+ namespace = {}
624
+ if name in namespace:
625
+ return True, namespace[name]
626
+
627
+ owner = _concrete_attribute_owner(type(obj), name)
628
+ if owner is None:
629
+ return False, None
630
+ try:
631
+ return True, getattr(obj, name)
632
+ except AttributeError:
633
+ return False, None
634
+
635
+
636
+ def _attribute_problems(obj: Any, iface_or_impl: type) -> list[str]:
637
+ problems: list[str] = []
638
+ for name, spec in attributes_of(iface_or_impl).items():
639
+ exists, value = _instance_attribute_value(obj, name)
640
+ if not exists:
641
+ problems.append(f"'{name}' instance attribute eksik ({spec.owner} arayuzu)")
642
+ elif not _annotation_matches(value, spec.annotation):
643
+ problems.append(
644
+ f"'{name}' degeri {spec.annotation!r} anotasyonuyla uyusmuyor; "
645
+ f"{type(value).__name__} verildi"
646
+ )
647
+ return problems
648
+
649
+
650
+ def missing_attributes(obj: Any, iface_or_impl: type | None = None) -> list[str]:
651
+ """Return missing opt-in instance attributes without raising."""
652
+ contract = type(obj) if iface_or_impl is None else iface_or_impl
653
+ return [
654
+ name for name in attributes_of(contract)
655
+ if not _instance_attribute_value(obj, name)[0]
656
+ ]
657
+
658
+
659
+ def verify_instance(obj: Any, iface: type | None = None) -> Any:
660
+ """Verify runtime attributes, and optionally structural methods, on ``obj``."""
661
+ contract = type(obj) if iface is None else iface
662
+ if iface is not None and not is_interface(iface):
663
+ raise TypeError(f"{iface!r} bir arayuz degil")
664
+ if iface is not None and not structurally_implements(type(obj), iface, check_attributes=False):
665
+ raise InterfaceError(
666
+ f"{type(obj).__name__} {iface.__name__} arayuzunun metotlarini karsilamiyor."
667
+ )
668
+ problems = _attribute_problems(obj, contract)
669
+ if problems:
670
+ raise InterfaceError(
671
+ f"{type(obj).__name__} instance sozlesmesini karsilamiyor:\n - "
672
+ + "\n - ".join(problems)
673
+ )
674
+ return obj
675
+
676
+
677
+ def satisfies(obj: Any, iface: type) -> bool:
678
+ """Return whether an object structurally satisfies methods and attributes."""
679
+ try:
680
+ verify_instance(obj, iface)
681
+ except (InterfaceError, TypeError, ValueError):
682
+ return False
683
+ return True
684
+
685
+
686
+ def verify(cls: type) -> type:
687
+ """Sinifi arayuz sozlesmesine karsi dogrular; uyumsuzsa InterfaceError atar."""
688
+ contract = members_of(cls)
689
+ if not contract and not attributes_of(cls):
690
+ raise InterfaceError(
691
+ f"{cls.__name__} hicbir arayuzden turetilmemis.")
692
+
693
+ check_annotations = any(
694
+ b.__dict__.get(_ANNOTATED, False) for b in cls.__mro__ if is_interface(b))
695
+
696
+ problems: list[str] = []
697
+ for name, declared in contract.items():
698
+ found = _find_implementation(cls, name)
699
+ if found is None:
700
+ if not declared.is_default:
701
+ problems.append(
702
+ f"'{name}' implement edilmemis ({declared.owner} arayuzu)")
703
+ continue
704
+ value, owner = found
705
+ impl = classify(name, value, owner.__name__)
706
+ if impl is None:
707
+ problems.append(f"'{name}' cagrilabilir bir uye degil")
708
+ continue
709
+ problem = member_problem(declared, impl, check_annotations)
710
+ if problem:
711
+ problems.append(f"'{name}' {problem}")
712
+
713
+ if problems:
714
+ raise InterfaceError(
715
+ f"{cls.__name__} arayuz sozlesmesini karsilamiyor:\n - "
716
+ + "\n - ".join(problems))
717
+
718
+ type.__setattr__(cls, _VERIFIED, True)
719
+ return cls
720
+
721
+
722
+ def structurally_implements(
723
+ candidate: type,
724
+ iface: type,
725
+ *,
726
+ check_attributes: bool = True,
727
+ ) -> bool:
728
+ """Miras olmaksizin, uye uye uyum kontrolu (Protocol benzeri)."""
729
+ if not is_interface(iface):
730
+ raise TypeError(f"{iface!r} bir arayuz degil")
731
+ for name, declared in members_of(iface).items():
732
+ raw = None
733
+ for base in getattr(candidate, "__mro__", (candidate,)):
734
+ if name in vars(base):
735
+ raw = vars(base)[name]
736
+ break
737
+ if raw is None:
738
+ return False
739
+ impl = classify(name, raw, getattr(candidate, "__name__", "?"))
740
+ if impl is None or member_problem(declared, impl) is not None:
741
+ return False
742
+ if check_attributes:
743
+ for name, spec in attributes_of(iface).items():
744
+ owner = _concrete_attribute_owner(candidate, name)
745
+ if owner is None:
746
+ return False
747
+ raw = vars(owner)[name]
748
+ if not isinstance(raw, property) and not _annotation_matches(raw, spec.annotation):
749
+ return False
750
+ return True
751
+
752
+
753
+ def implements(cls: type) -> type:
754
+ """Geriye donuk uyumluluk.
755
+
756
+ Dogrulama artik tanim aninda otomatik yapiliyor; bu dekoratör yalnizca
757
+ niyeti belgelemek isteyenler icin duruyor ve sinifi degistirmeden dondurur.
758
+ """
759
+ if not cls.__dict__.get(_VERIFIED, False):
760
+ verify(cls)
761
+ return cls
762
+
763
+
764
+ def default(func: Any) -> Any:
765
+ """Arayuzde govdeli 'default method' tanimlamaya izin verir (Java 8 benzeri)."""
766
+ target = func
767
+ if isinstance(func, (staticmethod, classmethod)):
768
+ target = func.__func__
769
+ elif isinstance(func, property):
770
+ target = func.fget
771
+ setattr(target, _DEFAULT, True)
772
+ return func
773
+
774
+
775
+ def interface(cls: type | None = None, **options: Any) -> Any:
776
+ """Dekoratör stili arayuz tanimi.
777
+
778
+ `class IFoo(Interface)` yazimi tercih edilmelidir. Bu dekoratör sinifi
779
+ InterfaceMeta ile yeniden olusturur; govdede sifir argumanli `super()`
780
+ kullanan bir `@default` metot varsa o metot bozulur (`__class__` hucresi
781
+ eski sinifi gosterir). Arayuzlerin govdesi normalde bos oldugu icin bu
782
+ pratikte yalnizca default metotlari ilgilendirir.
783
+ """
784
+ def decorate(target: type) -> type:
785
+ namespace = {k: v for k, v in vars(target).items()
786
+ if k not in ("__dict__", "__weakref__")}
787
+ namespace[_FLAG] = True
788
+ bases = tuple(b for b in target.__bases__ if b is not object)
789
+ if not any(is_interface(b) for b in bases):
790
+ bases = (*bases, Interface)
791
+ return InterfaceMeta(target.__name__, bases, namespace,
792
+ interface=True, **options)
793
+
794
+ return decorate(cls) if cls is not None else decorate
795
+
796
+
797
+ # Geriye donuk uyumluluk
798
+ interface_implement = implements