Skip to content

Commit

Permalink
feat: attrs support (#151)
Browse files Browse the repository at this point in the history
  • Loading branch information
15r10nk committed Dec 14, 2024
1 parent 651b69a commit 87a546a
Show file tree
Hide file tree
Showing 5 changed files with 244 additions and 2 deletions.
62 changes: 62 additions & 0 deletions changelog.d/20241214_071645_15r10nk-git_attrs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<!--
A new scriv changelog fragment.
Uncomment the section that is right (remove the HTML comment wrapper).
-->

<!--
### Removed
- A bullet item for the Removed category.
-->
### Added

- [attrs](https://www.attrs.org/en/stable/index.html) can now contain unmanaged values

``` python
import datetime as dt
import uuid
import attrs
from dirty_equals import IsDatetime
from inline_snapshot import Is, snapshot


@attrs.define
class Attrs:
ts: dt.datetime
id: uuid.UUID


def test():
id = uuid.uuid4()

assert Attrs(dt.datetime.now(), id) == snapshot(
Attrs(ts=IsDatetime(), id=Is(id))
)
```

<!--
### Changed

- A bullet item for the Changed category.

-->
<!--
### Deprecated

- A bullet item for the Deprecated category.

-->
<!--
### Fixed

- A bullet item for the Fixed category.

-->
<!--
### Security

- A bullet item for the Security category.

-->
3 changes: 2 additions & 1 deletion docs/eq_snapshot.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ inline-snapshot is able to handle these types within the following containers:
* tuple
* dict
* namedtuple
* dataclass
* [dataclass](https://docs.python.org/3/library/dataclasses.html)
* [attrs](https://www.attrs.org/en/stable/index.html)
<!--
* pydantic models
* attrs
Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ extra-dependencies = [
"pytest-subtests>=0.11.0",
"pytest-freezer>=0.4.8",
"pydantic",
"attrs",
"pytest-mock>=3.14.0"
]
env-vars.TOP = "{root}"
Expand All @@ -132,7 +133,8 @@ extra-dependencies = [
"mypy>=1.0.0",
"pytest",
"hypothesis>=6.75.5",
"pydantic"
"pydantic",
"attrs"
]

[tool.hatch.envs.release]
Expand Down
49 changes: 49 additions & 0 deletions src/inline_snapshot/_adapter/generic_call_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,55 @@ def argument(self, value, pos_or_name):
return getattr(value, pos_or_name)


try:
import attrs
except ImportError: # pragma: no cover
pass
else:

class AttrAdapter(GenericCallAdapter):

@classmethod
def check_type(cls, value):
return attrs.has(value)

@classmethod
def arguments(cls, value):

kwargs = {}

for field in attrs.fields(type(value)):
if field.repr:
field_value = getattr(value, field.name)
is_default = False

if field.default is not attrs.NOTHING:

default_value = (
field.default
if not isinstance(field.default, attrs.Factory)
else (
field.default.factory()
if not field.default.takes_self
else field.default.factory(value)
)
)

if default_value == field_value:

is_default = True

kwargs[field.name] = Argument(
value=field_value, is_default=is_default
)

return ([], kwargs)

def argument(self, value, pos_or_name):
assert isinstance(pos_or_name, str)
return getattr(value, pos_or_name)


try:
from pydantic import BaseModel
except ImportError: # pragma: no cover
Expand Down
128 changes: 128 additions & 0 deletions tests/adapter/test_dataclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,134 @@ def test_something():
)


def test_attrs_default_value():
Example(
"""\
from inline_snapshot import snapshot,Is
import attrs
@attrs.define
class A:
a:int
b:int=2
c:list=attrs.field(factory=list)
d:int=attrs.field(default=attrs.Factory(lambda self:self.a+10,takes_self=True))
def test_something():
assert A(a=1) == snapshot(A(a=1,b=2,c=[],d=11))
assert A(a=2,b=3) == snapshot(A(a=1,b=2,c=[],d=11))
"""
).run_inline(
["--inline-snapshot=fix"],
changed_files=snapshot(
{
"test_something.py": """\
from inline_snapshot import snapshot,Is
import attrs
@attrs.define
class A:
a:int
b:int=2
c:list=attrs.field(factory=list)
d:int=attrs.field(default=attrs.Factory(lambda self:self.a+10,takes_self=True))
def test_something():
assert A(a=1) == snapshot(A(a=1,b=2,c=[],d=11))
assert A(a=2,b=3) == snapshot(A(a=2,b=3,c=[],d=11))
"""
}
),
).run_inline(
["--inline-snapshot=update"],
changed_files=snapshot(
{
"test_something.py": """\
from inline_snapshot import snapshot,Is
import attrs
@attrs.define
class A:
a:int
b:int=2
c:list=attrs.field(factory=list)
d:int=attrs.field(default=attrs.Factory(lambda self:self.a+10,takes_self=True))
def test_something():
assert A(a=1) == snapshot(A(a=1))
assert A(a=2,b=3) == snapshot(A(a=2,b=3))
"""
}
),
)


def test_attrs_field_repr():

Example(
"""\
from inline_snapshot import snapshot
from pydantic import BaseModel,Field
import attrs
@attrs.define
class container:
a: int
b: int = attrs.field(default=5,repr=False)
assert container(a=1,b=5) == snapshot()
"""
).run_inline(
["--inline-snapshot=create"],
changed_files=snapshot(
{
"test_something.py": """\
from inline_snapshot import snapshot
from pydantic import BaseModel,Field
import attrs
@attrs.define
class container:
a: int
b: int = attrs.field(default=5,repr=False)
assert container(a=1,b=5) == snapshot(container(a=1))
"""
}
),
).run_inline()


def test_attrs_unmanaged():
Example(
"""\
import datetime as dt
import uuid
import attrs
from dirty_equals import IsDatetime
from inline_snapshot import Is, snapshot
@attrs.define
class Attrs:
ts: dt.datetime
id: uuid.UUID
def test():
id = uuid.uuid4()
assert snapshot(Attrs(ts=IsDatetime(), id=Is(id))) == Attrs(
dt.datetime.now(), id
)
"""
).run_inline(
["--inline-snapshot=create,fix"],
changed_files=snapshot({}),
).run_inline()


def test_disabled(executing_used):
Example(
"""\
Expand Down

0 comments on commit 87a546a

Please sign in to comment.