CodeSampleX

샘플

wrapt 2.3.0: Subclass the right object proxy base in wrapt 2.x so iteration works

검증된 샘플 — pypi wrapt 2.3.0: Subclass the right object proxy base in wrapt 2.x so iteration works. python 3.12 · linux alpine/x64 · docker에서 contract를 실행해…

sha256:61d02a30076981ae851aad530bd2d9dbce05537be4411337993c75500740cdd6

이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다. 통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다. MIT-0

실행 증거

선언된 환경과 서명된 실행을 분리해 두었습니다. 이 샘플이 무엇을 어디서 실행했는지 그대로 볼 수 있습니다.

증거 기준
서명된 컨트랙트 통과
검증 영수증
2
빌드한 서명 키
2
선언된 환경 python 3.12 linux x64 python 3.12 python pip

검증 실행 환경

환경 컨트랙트 단계 실행일
python 3.12 · linux alpine/x64 · docker ed25519:a2ec939a4c60e243 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · python@1
2026-08-14
python 3.12 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · python@1
2026-08-18

케이스

HOW
목표
Subclass the right object proxy base in wrapt 2.x so iteration works
패키지
심벌
  • BaseObjectProxy
  • ObjectProxy
  • AutoObjectProxy
  • with_signature
  • function_wrapper
환경
python 3.12
생성일
2026-08-14T20:44:52Z

컨트랙트

  1. assert ObjectProxy is a subclass of BaseObjectProxy, not an alias for it
  2. assert BaseObjectProxy around a list refuses to iterate while isinstance and hasattr both claim it is iterable
  3. assert the 1.x shape of subclassing ObjectProxy still iterates, but reports a non-iterable as iterable
  4. assert AutoObjectProxy agrees with reality both ways and builds a new class per instance
  5. assert wrapt.signature is a module so calling it raises TypeError, while inspect.signature reads the original signature through a wrapt decorator
  6. assert wrapt.function_wrapper is not wrapt.decorator and takes no enabled/adapter/proxy keywords
  7. assert proxy subclass state needs the _self_ prefix or it leaks onto the wrapped object

파일

  • csx.json
  • requirements.txt
  • src/__init__.py
  • src/compat.py
  • test/contract.py

소스 아티팩트 내려받기 (tar.gz)

소스

csx.json
{"case":{"caseId":"case:sha256:ef4e9a72558ecec67503f11767568b8fada3a15ab81f2ce0f46e5ec34fe75d6e","constraints":{"runtime":"python"},"contract":["assert ObjectProxy is a subclass of BaseObjectProxy, not an alias for it","assert BaseObjectProxy around a list refuses to iterate while isinstance and hasattr both claim it is iterable","assert the 1.x shape of subclassing ObjectProxy still iterates, but reports a non-iterable as iterable","assert AutoObjectProxy agrees with reality both ways and builds a new class per instance","assert wrapt.signature is a module so calling it raises TypeError, while inspect.signature reads the original signature through a wrapt decorator","assert wrapt.function_wrapper is not wrapt.decorator and takes no enabled/adapter/proxy keywords","assert proxy subclass state needs the _self_ prefix or it leaks onto the wrapped object"],"goal":"Subclass the right object proxy base in wrapt 2.x so iteration works","kind":"HOW","packages":["pkg:pypi/wrapt@2.3.0"],"schemaVersion":1,"symbols":["BaseObjectProxy","ObjectProxy","AutoObjectProxy","with_signature","function_wrapper"]},"contractCommand":["python","test/contract.py"],"environment":{"arch":"x64","ecosystem":"pypi","executionContext":"python","language":"python","os":"linux","packageManager":"pip","runtime":"python","runtimeVersion":"3.12","schemaVersion":1},"license":"MIT-0","packages":["pkg:pypi/wrapt@2.3.0"],"schemaVersion":1,"symbols":["BaseObjectProxy","ObjectProxy","AutoObjectProxy","with_signature","function_wrapper"],"verifierAdapter":"python@1"}
requirements.txt
wrapt==2.3.0
src/__init__.py
src/compat.py
"""Picking an object-proxy base class in wrapt 2.x.

wrapt sat on 1.x from 2013 until August 2025, so essentially every example in
circulation says "subclass wrapt.ObjectProxy". In 2.x that name still exists and
still imports, which is exactly why the change is easy to miss: ObjectProxy is
no longer the base class. The base is BaseObjectProxy, and ObjectProxy is a thin
backwards-compatibility subclass whose only added special method is __iter__.

Which class you pick decides whether iteration works, and the usual duck-typing
checks cannot tell you which one you have: isinstance() and hasattr() both
answer "iterable" for the two combinations where iteration actually fails.
"""

import collections.abc as abc

import wrapt

# The C-level base type in 2.x (wrapt.BaseObjectProxy, module "_wrappers").
# wrapt's own ObjectProxy docstring points new subclasses here: "If you don't
# need backward compatibility for __iter__() support then it is preferable to
# use BaseObjectProxy directly."
BASE = wrapt.BaseObjectProxy

# The 1.x-compatible name (module "wrapt.proxies"). It is a SUBCLASS of BASE,
# not an alias for it, and the only special method it adds is __iter__.
COMPAT = wrapt.ObjectProxy

# Builds a bespoke class per instance carrying the dunder methods the wrapped
# object actually has. Correct, but it pays a new class for every proxy created.
AUTO = wrapt.AutoObjectProxy


def can_iterate(proxy):
    """Whether the proxy can ACTUALLY be consumed as an iterable.

    Python resolves __iter__ on the type and never on the instance, so a proxy
    iterates only when its own class defines __iter__. wrapt's __getattr__
    forwarding does not help here. The consequence that catches people out:
    list(BaseObjectProxy([1, 2, 3])) raises TypeError even though the wrapped
    object is a list.
    """
    try:
        list(proxy)
        return True
    except TypeError:
        return False


def looks_iterable(proxy):
    """The cheap pre-check an agent reaches for -- and it over-reports.

    isinstance() against an ABC consults the proxy's __class__, which is
    forwarded to the WRAPPED object, and also the proxy's real type; iteration
    is decided by the real type alone. So "if isinstance(p, Iterable): list(p)"
    is the obvious wrong version, and it is wrong the SAME way both times --
    a false positive, for two different reasons. BASE around a list says True
    because the wrapped list is iterable; COMPAT around a non-iterable says
    True because the proxy type defines __iter__. Both then raise on list(p).
    """
    return isinstance(proxy, abc.Iterable)
test/contract.py
import inspect
import os
import sys
import types

import collections.abc as abc

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import wrapt

from src.compat import AUTO, BASE, COMPAT, can_iterate, looks_iterable


class Bare:
    """A plain object with no dunder methods beyond the defaults."""

    def __init__(self):
        self.value = 1


# ObjectProxy is NOT the base class in 2.x, and not an alias for it either: it
# is a subclass of the real base, and so is AutoObjectProxy.
assert wrapt.ObjectProxy is not wrapt.BaseObjectProxy
assert issubclass(wrapt.ObjectProxy, wrapt.BaseObjectProxy)
assert issubclass(wrapt.AutoObjectProxy, wrapt.BaseObjectProxy)

# The base class does not DEFINE __iter__ on the type, and that is where Python
# looks for it, so a proxied list will not iterate: the obvious "list(proxy)"
# raises TypeError even though the wrapped object is a list. Attribute
# forwarding is irrelevant here -- this is the whole reason ObjectProxy stayed.
base_list = BASE([1, 2, 3])
assert can_iterate(base_list) is False, "BaseObjectProxy unexpectedly iterated"
# ...while every non-dunder-dispatched check insists it is fine.
assert looks_iterable(base_list) is True
assert hasattr(base_list, "__iter__") is True
assert base_list.__class__ is list
assert len(base_list) == 3 and base_list[0] == 1

# Calling the forwarded attribute by hand does work; only the iter() protocol,
# which looks the method up on the type, fails.
assert list(base_list.__iter__()) == [1, 2, 3]

# The compat class iterates, because __iter__ is precisely what it adds. That
# is why 1.x-shaped code -- "class MyProxy(wrapt.ObjectProxy)" -- keeps running
# unchanged, and why nothing announces that a different base is now intended.
assert can_iterate(COMPAT([1, 2, 3])) is True


class LegacyProxy(COMPAT):
    """Exactly what twelve years of wrapt 1.x examples tell you to write."""


assert list(LegacyProxy([1, 2, 3])) == [1, 2, 3]

# But it adds __iter__ unconditionally, so it claims a NON-iterable is iterable.
compat_bare = COMPAT(Bare())
assert looks_iterable(compat_bare) is True, "expected the false positive"
assert can_iterate(compat_bare) is False
# The base class gets this one right: the false positive changes sides with the
# base class you picked, so neither answer can be trusted on its own.
assert looks_iterable(BASE(Bare())) is False

# AutoObjectProxy is the only one that agrees with reality in both directions.
assert can_iterate(AUTO([1, 2, 3])) is True
assert looks_iterable(AUTO(Bare())) is False
assert isinstance(AUTO([1, 2, 3]), abc.Iterable) is True

# The cost is a freshly built class for every instance, so identity checks on
# type() and any per-class caching keyed on it stop working.
assert type(AUTO([1, 2])) is not type(AUTO([3, 4]))
assert type(COMPAT([1, 2])) is type(COMPAT([3, 4]))

# wrapt.signature is a MODULE, not a callable. "wrapt.signature(fn)" reads like
# a proxy-aware inspect.signature and fails at runtime instead; the exported
# helper is wrapt.with_signature, and plain inspect.signature already sees
# through a wrapt decorator.
assert isinstance(wrapt.signature, types.ModuleType)
try:
    wrapt.signature(lambda a: a)
    raise AssertionError("expected TypeError: module object is not callable")
except TypeError:
    pass
assert callable(wrapt.with_signature)


@wrapt.decorator
def passthrough(wrapped, instance, args, kwargs):
    return wrapped(*args, **kwargs)


@passthrough
def add(a, b=2):
    return a + b


# The reason you do not need a proxy-aware signature helper: the decorated
# function still advertises the ORIGINAL signature, not (*args, **kwargs).
assert str(inspect.signature(add)) == "(a, b=2)"
assert add(1) == 3

# Likewise wrapt.function_wrapper is not another name for wrapt.decorator: it
# takes the wrapper and nothing else, so the enabled/adapter/proxy options exist
# on decorator only.
assert wrapt.function_wrapper is not wrapt.decorator
assert list(inspect.signature(wrapt.function_wrapper).parameters) == ["wrapper"]
assert list(inspect.signature(wrapt.decorator).parameters) == [
    "wrapper",
    "enabled",
    "adapter",
    "proxy",
]

# Subclass state still needs the _self_ prefix on the 2.x base: any other name
# is written through onto the wrapped object rather than kept on the proxy.
class Counter(BASE):
    def __init__(self, wrapped):
        super().__init__(wrapped)
        self._self_hits = 0

    def bump(self):
        self._self_hits += 1
        return self._self_hits


counter = Counter(Bare())
counter.bump()
assert counter.bump() == 2
assert not hasattr(counter.__wrapped__, "_self_hits")


class Leaky(BASE):
    def __init__(self, wrapped):
        super().__init__(wrapped)
        self.hits = 0


leaky = Leaky(Bare())
assert leaky.__wrapped__.hits == 0, "plain attribute should leak to the wrapped object"

print("CONTRACT PASS: wrapt 2.x proxy bases differ on __iter__ and isinstance lies")

오리진 시더

anonymous