remapper


Nameremapper JSON
Version 1.0.0.post0 PyPI version JSON
download
home_pageNone
SummaryTransform objects to and from similar structural mappings.
upload_time2025-01-26 07:33:14
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseNone
keywords mapping dataclass presentation models util
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # remapper

![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/thearchitector/remapper/test.yaml?label=tests&style=flat-square)
![PyPI - Downloads](https://img.shields.io/pypi/dm/remapper?style=flat-square)
![GitHub](https://img.shields.io/github/license/thearchitector/remapper?style=flat-square)

Transform objects to and from similar structural mappings. Useful for translating between sources of truth and presentational models of data.

Supports Python 3.8+. No dependencies.

## Installation

```sh
$ pdm add remapper
# or
$ python -m pip install --user remapper
```

## Usage

> The examples below use dataclasses because they're easy, but `remap` works with any destination type that exposes attributes via its `__init__`.

A trivial example for `remap` is converting one dataclass into another without having to manually pass attributes:

```python
from dataclasses import dataclass

from remapper import remap


@dataclass
class Source:
    a: int
    b: int
    c: int


@dataclass
class Destination:
    a: int
    b: int
    c: int


dest = remap(Source(a=1, b=2, c=3), Destination)
# >> Destination(a=1, b=2, c=3)
```

A more useful example would be in mapping an internal database model to an externally-facing dataclass:

```python
from dataclasses import dataclass

from remapper import remap
from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, mapped_column


class Base(DeclarativeBase, MappedAsDataclass):
    pass


class Source(Base):
    id: Mapped[int] = mapped_column(init=False, primary_key=True)

    a: Mapped[int]
    b: Mapped[int]
    c: Mapped[int]


@dataclass
class Destination:
    a: int
    b: int
    c: int

    @property
    def d(self) -> int:
        return self.a + self.b + self.c


source = Source(a=1, b=2, c=3)
await session.commit(source)

dest = remap(source, Destination)
dest.d
# >> 6
```

### Overrides

There's a non-zero chance that a destination may require more data than available on a source. In those cases, you can manually provide values via the `overrides` keyword argument. Values in `overrides` will take precedence over values on the source:

```python
from dataclasses import dataclass

from remapper import remap


@dataclass
class Source:
    a: int
    b: int


@dataclass
class Destination:
    a: int
    b: int
    c: int


dest = remap(Source(a=1, b=2), Destination, overrides={"b": 0, "c": 3})
# >> Destination(a=1, b=0, c=3)
```

### Defaults

If a destination defines more attributes than available on a source, but the destination has default values for those additional attributes, you don't need to supply overrides:

```python
from dataclasses import dataclass

from remapper import remap


@dataclass
class Source:
    a: int
    b: int


@dataclass
class Destination:
    a: int
    b: int
    c: int = 3


dest = remap(Source(a=1, b=2), Destination)
# >> Destination(a=1, b=2, c=3)
```

### Nested Types

Some complex sources may have attributes that themselves need to be converted. Rather than remapping in several steps, you can use the `nested_types` keyword argument to specify inner types of the destination attributes:

```python
from dataclasses import dataclass

from remapper import remap


@dataclass
class Source:
    b: int


@dataclass
class Destination:
    b: int


@dataclass
class ParentSource:
    a: int
    child: Source


@dataclass
class ParentDestination:
    a: int
    child: Destination


## this works but is kind of clunky
source = ParentSource(a=1, child=Source(b=1))
dest_child = remap(source.child, Destination)
dest = remap(source, ParentDestination, overrides={"child": dest_child})
# >> ParentDestination(a=1,child=Destination(b=1))

## so use this instead
dest = remap(
    ParentSource(a=1, child=Source(b=1)),
    ParentDestination,
    nested_types={"child": Destination},
)
# >> ParentDestination(a=1,child=Destination(b=1))
```

## License

This software is licensed under the [BSD 3-Clause Clear License](LICENSE).

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "remapper",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "mapping, dataclass, presentation, models, util",
    "author": null,
    "author_email": "Elias Gabriel <oss@eliasfgabriel.com>",
    "download_url": "https://files.pythonhosted.org/packages/05/73/27f05b3127ba4fe16226a04074d8ce715596619d9a5742ed1045575343bd/remapper-1.0.0.post0.tar.gz",
    "platform": null,
    "description": "# remapper\n\n![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/thearchitector/remapper/test.yaml?label=tests&style=flat-square)\n![PyPI - Downloads](https://img.shields.io/pypi/dm/remapper?style=flat-square)\n![GitHub](https://img.shields.io/github/license/thearchitector/remapper?style=flat-square)\n\nTransform objects to and from similar structural mappings. Useful for translating between sources of truth and presentational models of data.\n\nSupports Python 3.8+. No dependencies.\n\n## Installation\n\n```sh\n$ pdm add remapper\n# or\n$ python -m pip install --user remapper\n```\n\n## Usage\n\n> The examples below use dataclasses because they're easy, but `remap` works with any destination type that exposes attributes via its `__init__`.\n\nA trivial example for `remap` is converting one dataclass into another without having to manually pass attributes:\n\n```python\nfrom dataclasses import dataclass\n\nfrom remapper import remap\n\n\n@dataclass\nclass Source:\n    a: int\n    b: int\n    c: int\n\n\n@dataclass\nclass Destination:\n    a: int\n    b: int\n    c: int\n\n\ndest = remap(Source(a=1, b=2, c=3), Destination)\n# >> Destination(a=1, b=2, c=3)\n```\n\nA more useful example would be in mapping an internal database model to an externally-facing dataclass:\n\n```python\nfrom dataclasses import dataclass\n\nfrom remapper import remap\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, mapped_column\n\n\nclass Base(DeclarativeBase, MappedAsDataclass):\n    pass\n\n\nclass Source(Base):\n    id: Mapped[int] = mapped_column(init=False, primary_key=True)\n\n    a: Mapped[int]\n    b: Mapped[int]\n    c: Mapped[int]\n\n\n@dataclass\nclass Destination:\n    a: int\n    b: int\n    c: int\n\n    @property\n    def d(self) -> int:\n        return self.a + self.b + self.c\n\n\nsource = Source(a=1, b=2, c=3)\nawait session.commit(source)\n\ndest = remap(source, Destination)\ndest.d\n# >> 6\n```\n\n### Overrides\n\nThere's a non-zero chance that a destination may require more data than available on a source. In those cases, you can manually provide values via the `overrides` keyword argument. Values in `overrides` will take precedence over values on the source:\n\n```python\nfrom dataclasses import dataclass\n\nfrom remapper import remap\n\n\n@dataclass\nclass Source:\n    a: int\n    b: int\n\n\n@dataclass\nclass Destination:\n    a: int\n    b: int\n    c: int\n\n\ndest = remap(Source(a=1, b=2), Destination, overrides={\"b\": 0, \"c\": 3})\n# >> Destination(a=1, b=0, c=3)\n```\n\n### Defaults\n\nIf a destination defines more attributes than available on a source, but the destination has default values for those additional attributes, you don't need to supply overrides:\n\n```python\nfrom dataclasses import dataclass\n\nfrom remapper import remap\n\n\n@dataclass\nclass Source:\n    a: int\n    b: int\n\n\n@dataclass\nclass Destination:\n    a: int\n    b: int\n    c: int = 3\n\n\ndest = remap(Source(a=1, b=2), Destination)\n# >> Destination(a=1, b=2, c=3)\n```\n\n### Nested Types\n\nSome complex sources may have attributes that themselves need to be converted. Rather than remapping in several steps, you can use the `nested_types` keyword argument to specify inner types of the destination attributes:\n\n```python\nfrom dataclasses import dataclass\n\nfrom remapper import remap\n\n\n@dataclass\nclass Source:\n    b: int\n\n\n@dataclass\nclass Destination:\n    b: int\n\n\n@dataclass\nclass ParentSource:\n    a: int\n    child: Source\n\n\n@dataclass\nclass ParentDestination:\n    a: int\n    child: Destination\n\n\n## this works but is kind of clunky\nsource = ParentSource(a=1, child=Source(b=1))\ndest_child = remap(source.child, Destination)\ndest = remap(source, ParentDestination, overrides={\"child\": dest_child})\n# >> ParentDestination(a=1,child=Destination(b=1))\n\n## so use this instead\ndest = remap(\n    ParentSource(a=1, child=Source(b=1)),\n    ParentDestination,\n    nested_types={\"child\": Destination},\n)\n# >> ParentDestination(a=1,child=Destination(b=1))\n```\n\n## License\n\nThis software is licensed under the [BSD 3-Clause Clear License](LICENSE).\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "Transform objects to and from similar structural mappings.",
    "version": "1.0.0.post0",
    "project_urls": {
        "changelog": "https://github.com/thearchitector/remapper/releases",
        "homepage": "https://github.com/thearchitector/remapper",
        "issues": "https://github.com/thearchitector/remapper/issues",
        "source": "https://github.com/thearchitector/remapper"
    },
    "split_keywords": [
        "mapping",
        " dataclass",
        " presentation",
        " models",
        " util"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "a7d1920d8c553423e3b1590c171bbefbb18419c691eb9c4278ac0ed3a87e3585",
                "md5": "598ba21607b2106e942dca4456661543",
                "sha256": "61ba9d6321b62d6487f354864599a6402e1f58deafe703c8e50c509429f16058"
            },
            "downloads": -1,
            "filename": "remapper-1.0.0.post0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "598ba21607b2106e942dca4456661543",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 5415,
            "upload_time": "2025-01-26T07:33:12",
            "upload_time_iso_8601": "2025-01-26T07:33:12.326863Z",
            "url": "https://files.pythonhosted.org/packages/a7/d1/920d8c553423e3b1590c171bbefbb18419c691eb9c4278ac0ed3a87e3585/remapper-1.0.0.post0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "057327f05b3127ba4fe16226a04074d8ce715596619d9a5742ed1045575343bd",
                "md5": "cfa95cb165d076f52bdaf1f5f278466d",
                "sha256": "1801103b38801de5a00f1c54b8af62e6d43250dde90b5469a75380a8159b889d"
            },
            "downloads": -1,
            "filename": "remapper-1.0.0.post0.tar.gz",
            "has_sig": false,
            "md5_digest": "cfa95cb165d076f52bdaf1f5f278466d",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 5559,
            "upload_time": "2025-01-26T07:33:14",
            "upload_time_iso_8601": "2025-01-26T07:33:14.057599Z",
            "url": "https://files.pythonhosted.org/packages/05/73/27f05b3127ba4fe16226a04074d8ce715596619d9a5742ed1045575343bd/remapper-1.0.0.post0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-01-26 07:33:14",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "thearchitector",
    "github_project": "remapper",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "remapper"
}
        
Elapsed time: 0.83826s