py-automapper


Namepy-automapper JSON
Version 2.1.0 PyPI version JSON
download
home_pageNone
SummaryLibrary for automatically mapping one object to another
upload_time2025-02-04 22:54:19
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseMIT License Copyright (c) 2021 Andrii Nikolaienko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keywords utils dto object-mapper mapping development
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage
            <img src="logo.png" style="width:128px; margin-right: 20px;" />

# py-automapper <!-- omit in toc -->

> [!IMPORTANT]
> Renewing maintanance of this library!
>
> After a long pause, I see the library is still in use and receives more stars. Thank you all who likes and uses it. So, I renew the py-automapper maintanance.
> Expect fixes and new version soon.

**Build Status**
[![Main branch status](https://github.com/anikolaienko/py-automapper/actions/workflows/run_code_checks.yml/badge.svg?branch=main)](https://github.com/anikolaienko/py-automapper/actions?query=branch%3Amain)

---

Table of Contents:
- [Versions](#versions)
- [About](#about)
- [Contribute](#contribute)
- [Usage](#usage)
  - [Installation](#installation)
  - [Get started](#get-started)
  - [Map dictionary source to target object](#map-dictionary-source-to-target-object)
  - [Different field names](#different-field-names)
  - [Overwrite field value in mapping](#overwrite-field-value-in-mapping)
  - [Disable Deepcopy](#disable-deepcopy)
  - [Extensions](#extensions)
  - [Pydantic/FastAPI Support](#pydanticfastapi-support)
  - [TortoiseORM Support](#tortoiseorm-support)
  - [SQLAlchemy Support](#sqlalchemy-support)
  - [Create your own extension (Advanced)](#create-your-own-extension-advanced)

# Versions
Check [CHANGELOG.md](/CHANGELOG.md)

# About

**Python auto mapper** is useful for multilayer architecture which requires constant mapping between objects from separate layers (data layer, presentation layer, etc).

Inspired by: [object-mapper](https://github.com/marazt/object-mapper)

The major advantage of py-automapper is its extensibility, that allows it to map practically any type, discover custom class fields and customize mapping rules. Read more in [documentation](https://anikolaienko.github.io/py-automapper).

# Contribute
Read [CONTRIBUTING.md](/CONTRIBUTING.md) guide.

# Usage
## Installation
Install package:
```bash
pip install py-automapper
```

## Get started
Let's say we have domain model `UserInfo` and its API representation `PublicUserInfo` without exposing user `age`:
```python
class UserInfo:
    def __init__(self, name: str, profession: str, age: int):
        self.name = name
        self.profession = profession
        self.age = age

class PublicUserInfo:
    def __init__(self, name: str, profession: str):
        self.name = name
        self.profession = profession

user_info = UserInfo("John Malkovich", "engineer", 35)
```
To create `PublicUserInfo` object:
```python
from automapper import mapper

public_user_info = mapper.to(PublicUserInfo).map(user_info)

print(vars(public_user_info))
# {'name': 'John Malkovich', 'profession': 'engineer'}
```
You can register which class should map to which first:
```python
# Register
mapper.add(UserInfo, PublicUserInfo)

public_user_info = mapper.map(user_info)

print(vars(public_user_info))
# {'name': 'John Malkovich', 'profession': 'engineer'}
```

## Map dictionary source to target object
If source object is dictionary:
```python
source = {
    "name": "John Carter",
    "profession": "hero"
}
public_info = mapper.to(PublicUserInfo).map(source)

print(vars(public_info))
# {'name': 'John Carter', 'profession': 'hero'}
```

## Different field names
If your target class field name is different from source class.
```python
class PublicUserInfo:
    def __init__(self, full_name: str, profession: str):
        self.full_name = full_name       # UserInfo has `name` instead
        self.profession = profession
```
Simple map:
```python
public_user_info = mapper.to(PublicUserInfo).map(user_info, fields_mapping={
    "full_name": user_info.name
})
```
Preregister and map. Source field should start with class name followed by period sign and field name:
```python
mapper.add(UserInfo, PublicUserInfo, fields_mapping={"full_name": "UserInfo.name"})
public_user_info = mapper.map(user_info)

print(vars(public_user_info))
# {'full_name': 'John Malkovich', 'profession': 'engineer'}
```

## Overwrite field value in mapping
Very easy if you want to field just have different value, you provide a new value:
```python
public_user_info = mapper.to(PublicUserInfo).map(user_info, fields_mapping={
    "full_name": "John Cusack"
})

print(vars(public_user_info))
# {'full_name': 'John Cusack', 'profession': 'engineer'}
```

## Disable Deepcopy
By default, py-automapper performs a recursive `copy.deepcopy()` call on all attributes when copying from source object into target class instance.
This makes sure that changes in the attributes of the source do not affect the target and vice versa.
If you need your target and source class share same instances of child objects, set `use_deepcopy=False` in `map` function.

```python
from dataclasses import dataclass
from automapper import mapper

@dataclass
class Address:
    street: str
    number: int
    zip_code: int
    city: str
  
class PersonInfo:
    def __init__(self, name: str, age: int, address: Address):
        self.name = name
        self.age = age
        self.address = address

class PublicPersonInfo:
    def __init__(self, name: str, address: Address):
        self.name = name
        self.address = address

address = Address(street="Main Street", number=1, zip_code=100001, city='Test City')
info = PersonInfo('John Doe', age=35, address=address)

# default deepcopy behavior
public_info = mapper.to(PublicPersonInfo).map(info)
print("Target public_info.address is same as source address: ", address is public_info.address)
# Target public_info.address is same as source address: False

# disable deepcopy
public_info = mapper.to(PublicPersonInfo).map(info, use_deepcopy=False)
print("Target public_info.address is same as source address: ", address is public_info.address)
# Target public_info.address is same as source address: True
```

## Extensions
`py-automapper` has few predefined extensions for mapping support to classes for frameworks:
* [FastAPI](https://github.com/tiangolo/fastapi) and [Pydantic](https://github.com/samuelcolvin/pydantic)
* [TortoiseORM](https://github.com/tortoise/tortoise-orm)
* [SQLAlchemy](https://www.sqlalchemy.org/)

## Pydantic/FastAPI Support
Out of the box Pydantic models support:
```python
from pydantic import BaseModel
from typing import List
from automapper import mapper

class UserInfo(BaseModel):
    id: int
    full_name: str
    public_name: str
    hobbies: List[str]

class PublicUserInfo(BaseModel):
    id: int
    public_name: str
    hobbies: List[str]

obj = UserInfo(
    id=2,
    full_name="Danny DeVito",
    public_name="dannyd",
    hobbies=["acting", "comedy", "swimming"]
)

result = mapper.to(PublicUserInfo).map(obj)
# same behaviour with preregistered mapping

print(vars(result))
# {'id': 2, 'public_name': 'dannyd', 'hobbies': ['acting', 'comedy', 'swimming']}
```

## TortoiseORM Support
Out of the box TortoiseORM models support:
```python
from tortoise import Model, fields
from automapper import mapper

class UserInfo(Model):
    id = fields.IntField(pk=True)
    full_name = fields.TextField()
    public_name = fields.TextField()
    hobbies = fields.JSONField()

class PublicUserInfo(Model):
    id = fields.IntField(pk=True)
    public_name = fields.TextField()
    hobbies = fields.JSONField()

obj = UserInfo(
    id=2,
    full_name="Danny DeVito",
    public_name="dannyd",
    hobbies=["acting", "comedy", "swimming"],
    using_db=True
)

result = mapper.to(PublicUserInfo).map(obj)
# same behaviour with preregistered mapping

# filtering out protected fields that start with underscore "_..."
print({key: value for key, value in vars(result) if not key.startswith("_")})
# {'id': 2, 'public_name': 'dannyd', 'hobbies': ['acting', 'comedy', 'swimming']}
```

## SQLAlchemy Support
Out of the box SQLAlchemy models support:
```python
from sqlalchemy.orm import declarative_base
from sqlalchemy import Column, Integer, String
from automapper import mapper

Base = declarative_base()

class UserInfo(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    full_name = Column(String)
    public_name = Column(String)
    hobbies = Column(String)
    def __repr__(self):
        return "<User(full_name='%s', public_name='%s', hobbies='%s')>" % (
            self.full_name,
            self.public_name,
            self.hobbies,
        )

class PublicUserInfo(Base):
    __tablename__ = 'public_users'
    id = Column(Integer, primary_key=True)
    public_name = Column(String)
    hobbies = Column(String)
    
obj = UserInfo(
            id=2,
            full_name="Danny DeVito",
            public_name="dannyd",
            hobbies="acting, comedy, swimming",
        )

result = mapper.to(PublicUserInfo).map(obj)
# same behaviour with preregistered mapping

# filtering out protected fields that start with underscore "_..."
print({key: value for key, value in vars(result) if not key.startswith("_")})
# {'id': 2, 'public_name': 'dannyd', 'hobbies': "acting, comedy, swimming"}
```

## Create your own extension (Advanced)
When you first time import `mapper` from `automapper` it checks default extensions and if modules are found for these extensions, then they will be automatically loaded for default `mapper` object.

**What does extension do?** To know what fields in Target class are available for mapping, `py-automapper` needs to know how to extract the list of fields. There is no generic way to do that for all Python objects. For this purpose `py-automapper` uses extensions.

List of default extensions can be found in [/automapper/extensions](/automapper/extensions) folder. You can take a look how it's done for a class with `__init__` method or for Pydantic or TortoiseORM models.

You can create your own extension and register in `mapper`:
```python
from automapper import mapper

class TargetClass:
    def __init__(self, **kwargs):
        self.name = kwargs["name"]
        self.age = kwargs["age"]
    
    @staticmethod
    def get_fields(cls):
        return ["name", "age"]

source_obj = {"name": "Andrii", "age": 30}

try:
    # Map object
    target_obj = mapper.to(TargetClass).map(source_obj)
except Exception as e:
    print(f"Exception: {repr(e)}")
    # Output:
    # Exception: KeyError('name')

    # mapper could not find list of fields from BaseClass
    # let's register extension for class BaseClass and all inherited ones
    mapper.add_spec(TargetClass, TargetClass.get_fields)
    target_obj = mapper.to(TargetClass).map(source_obj)

    print(f"Name: {target_obj.name}; Age: {target_obj.age}")
```

You can also create your own clean Mapper without any extensions and define extension for very specific classes, e.g. if class accepts `kwargs` parameter in `__init__` method and you want to copy only specific fields. Next example is a bit complex but probably rarely will be needed:
```python
from typing import Type, TypeVar

from automapper import Mapper

# Create your own Mapper object without any predefined extensions
mapper = Mapper()

class TargetClass:
    def __init__(self, **kwargs):
        self.data = kwargs.copy()

    @classmethod
    def fields(cls):
        return ["name", "age", "profession"]

source_obj = {"name": "Andrii", "age": 30, "profession": None}

try:
    target_obj = mapper.to(TargetClass).map(source_obj)
except Exception as e:
    print(f"Exception: {repr(e)}")
    # Output:
    # Exception: MappingError("No spec function is added for base class of <class 'type'>")

# Instead of using base class, we define spec for all classes that have `fields` property
T = TypeVar("T")

def class_has_fields_property(target_cls: Type[T]) -> bool:
    return callable(getattr(target_cls, "fields", None))
    
mapper.add_spec(class_has_fields_property, lambda t: getattr(t, "fields")())

target_obj = mapper.to(TargetClass).map(source_obj)
print(f"Name: {target_obj.data['name']}; Age: {target_obj.data['age']}; Profession: {target_obj.data['profession']}")
# Output:
# Name: Andrii; Age: 30; Profession: None

# Skip `None` value
target_obj = mapper.to(TargetClass).map(source_obj, skip_none_values=True)
print(f"Name: {target_obj.data['name']}; Age: {target_obj.data['age']}; Has profession: {hasattr(target_obj, 'profession')}")
# Output:
# Name: Andrii; Age: 30; Has profession: False
```

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "py-automapper",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": "Andrii Nikolaienko <anikolaienko14@gmail.com>",
    "keywords": "utils, dto, object-mapper, mapping, development",
    "author": null,
    "author_email": "Andrii Nikolaienko <anikolaienko14@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/03/b3/b6ac4788d3f3de40328cbf1f3728901e9301608f8a6eb6f44df8dfa51999/py_automapper-2.1.0.tar.gz",
    "platform": null,
    "description": "<img src=\"logo.png\" style=\"width:128px; margin-right: 20px;\" />\n\n# py-automapper <!-- omit in toc -->\n\n> [!IMPORTANT]\n> Renewing maintanance of this library!\n>\n> After a long pause, I see the library is still in use and receives more stars. Thank you all who likes and uses it. So, I renew the py-automapper maintanance.\n> Expect fixes and new version soon.\n\n**Build Status**\n[![Main branch status](https://github.com/anikolaienko/py-automapper/actions/workflows/run_code_checks.yml/badge.svg?branch=main)](https://github.com/anikolaienko/py-automapper/actions?query=branch%3Amain)\n\n---\n\nTable of Contents:\n- [Versions](#versions)\n- [About](#about)\n- [Contribute](#contribute)\n- [Usage](#usage)\n  - [Installation](#installation)\n  - [Get started](#get-started)\n  - [Map dictionary source to target object](#map-dictionary-source-to-target-object)\n  - [Different field names](#different-field-names)\n  - [Overwrite field value in mapping](#overwrite-field-value-in-mapping)\n  - [Disable Deepcopy](#disable-deepcopy)\n  - [Extensions](#extensions)\n  - [Pydantic/FastAPI Support](#pydanticfastapi-support)\n  - [TortoiseORM Support](#tortoiseorm-support)\n  - [SQLAlchemy Support](#sqlalchemy-support)\n  - [Create your own extension (Advanced)](#create-your-own-extension-advanced)\n\n# Versions\nCheck [CHANGELOG.md](/CHANGELOG.md)\n\n# About\n\n**Python auto mapper** is useful for multilayer architecture which requires constant mapping between objects from separate layers (data layer, presentation layer, etc).\n\nInspired by: [object-mapper](https://github.com/marazt/object-mapper)\n\nThe major advantage of py-automapper is its extensibility, that allows it to map practically any type, discover custom class fields and customize mapping rules. Read more in [documentation](https://anikolaienko.github.io/py-automapper).\n\n# Contribute\nRead [CONTRIBUTING.md](/CONTRIBUTING.md) guide.\n\n# Usage\n## Installation\nInstall package:\n```bash\npip install py-automapper\n```\n\n## Get started\nLet's say we have domain model `UserInfo` and its API representation `PublicUserInfo` without exposing user `age`:\n```python\nclass UserInfo:\n    def __init__(self, name: str, profession: str, age: int):\n        self.name = name\n        self.profession = profession\n        self.age = age\n\nclass PublicUserInfo:\n    def __init__(self, name: str, profession: str):\n        self.name = name\n        self.profession = profession\n\nuser_info = UserInfo(\"John Malkovich\", \"engineer\", 35)\n```\nTo create `PublicUserInfo` object:\n```python\nfrom automapper import mapper\n\npublic_user_info = mapper.to(PublicUserInfo).map(user_info)\n\nprint(vars(public_user_info))\n# {'name': 'John Malkovich', 'profession': 'engineer'}\n```\nYou can register which class should map to which first:\n```python\n# Register\nmapper.add(UserInfo, PublicUserInfo)\n\npublic_user_info = mapper.map(user_info)\n\nprint(vars(public_user_info))\n# {'name': 'John Malkovich', 'profession': 'engineer'}\n```\n\n## Map dictionary source to target object\nIf source object is dictionary:\n```python\nsource = {\n    \"name\": \"John Carter\",\n    \"profession\": \"hero\"\n}\npublic_info = mapper.to(PublicUserInfo).map(source)\n\nprint(vars(public_info))\n# {'name': 'John Carter', 'profession': 'hero'}\n```\n\n## Different field names\nIf your target class field name is different from source class.\n```python\nclass PublicUserInfo:\n    def __init__(self, full_name: str, profession: str):\n        self.full_name = full_name       # UserInfo has `name` instead\n        self.profession = profession\n```\nSimple map:\n```python\npublic_user_info = mapper.to(PublicUserInfo).map(user_info, fields_mapping={\n    \"full_name\": user_info.name\n})\n```\nPreregister and map. Source field should start with class name followed by period sign and field name:\n```python\nmapper.add(UserInfo, PublicUserInfo, fields_mapping={\"full_name\": \"UserInfo.name\"})\npublic_user_info = mapper.map(user_info)\n\nprint(vars(public_user_info))\n# {'full_name': 'John Malkovich', 'profession': 'engineer'}\n```\n\n## Overwrite field value in mapping\nVery easy if you want to field just have different value, you provide a new value:\n```python\npublic_user_info = mapper.to(PublicUserInfo).map(user_info, fields_mapping={\n    \"full_name\": \"John Cusack\"\n})\n\nprint(vars(public_user_info))\n# {'full_name': 'John Cusack', 'profession': 'engineer'}\n```\n\n## Disable Deepcopy\nBy default, py-automapper performs a recursive `copy.deepcopy()` call on all attributes when copying from source object into target class instance.\nThis makes sure that changes in the attributes of the source do not affect the target and vice versa.\nIf you need your target and source class share same instances of child objects, set `use_deepcopy=False` in `map` function.\n\n```python\nfrom dataclasses import dataclass\nfrom automapper import mapper\n\n@dataclass\nclass Address:\n    street: str\n    number: int\n    zip_code: int\n    city: str\n  \nclass PersonInfo:\n    def __init__(self, name: str, age: int, address: Address):\n        self.name = name\n        self.age = age\n        self.address = address\n\nclass PublicPersonInfo:\n    def __init__(self, name: str, address: Address):\n        self.name = name\n        self.address = address\n\naddress = Address(street=\"Main Street\", number=1, zip_code=100001, city='Test City')\ninfo = PersonInfo('John Doe', age=35, address=address)\n\n# default deepcopy behavior\npublic_info = mapper.to(PublicPersonInfo).map(info)\nprint(\"Target public_info.address is same as source address: \", address is public_info.address)\n# Target public_info.address is same as source address: False\n\n# disable deepcopy\npublic_info = mapper.to(PublicPersonInfo).map(info, use_deepcopy=False)\nprint(\"Target public_info.address is same as source address: \", address is public_info.address)\n# Target public_info.address is same as source address: True\n```\n\n## Extensions\n`py-automapper` has few predefined extensions for mapping support to classes for frameworks:\n* [FastAPI](https://github.com/tiangolo/fastapi) and [Pydantic](https://github.com/samuelcolvin/pydantic)\n* [TortoiseORM](https://github.com/tortoise/tortoise-orm)\n* [SQLAlchemy](https://www.sqlalchemy.org/)\n\n## Pydantic/FastAPI Support\nOut of the box Pydantic models support:\n```python\nfrom pydantic import BaseModel\nfrom typing import List\nfrom automapper import mapper\n\nclass UserInfo(BaseModel):\n    id: int\n    full_name: str\n    public_name: str\n    hobbies: List[str]\n\nclass PublicUserInfo(BaseModel):\n    id: int\n    public_name: str\n    hobbies: List[str]\n\nobj = UserInfo(\n    id=2,\n    full_name=\"Danny DeVito\",\n    public_name=\"dannyd\",\n    hobbies=[\"acting\", \"comedy\", \"swimming\"]\n)\n\nresult = mapper.to(PublicUserInfo).map(obj)\n# same behaviour with preregistered mapping\n\nprint(vars(result))\n# {'id': 2, 'public_name': 'dannyd', 'hobbies': ['acting', 'comedy', 'swimming']}\n```\n\n## TortoiseORM Support\nOut of the box TortoiseORM models support:\n```python\nfrom tortoise import Model, fields\nfrom automapper import mapper\n\nclass UserInfo(Model):\n    id = fields.IntField(pk=True)\n    full_name = fields.TextField()\n    public_name = fields.TextField()\n    hobbies = fields.JSONField()\n\nclass PublicUserInfo(Model):\n    id = fields.IntField(pk=True)\n    public_name = fields.TextField()\n    hobbies = fields.JSONField()\n\nobj = UserInfo(\n    id=2,\n    full_name=\"Danny DeVito\",\n    public_name=\"dannyd\",\n    hobbies=[\"acting\", \"comedy\", \"swimming\"],\n    using_db=True\n)\n\nresult = mapper.to(PublicUserInfo).map(obj)\n# same behaviour with preregistered mapping\n\n# filtering out protected fields that start with underscore \"_...\"\nprint({key: value for key, value in vars(result) if not key.startswith(\"_\")})\n# {'id': 2, 'public_name': 'dannyd', 'hobbies': ['acting', 'comedy', 'swimming']}\n```\n\n## SQLAlchemy Support\nOut of the box SQLAlchemy models support:\n```python\nfrom sqlalchemy.orm import declarative_base\nfrom sqlalchemy import Column, Integer, String\nfrom automapper import mapper\n\nBase = declarative_base()\n\nclass UserInfo(Base):\n    __tablename__ = \"users\"\n    id = Column(Integer, primary_key=True)\n    full_name = Column(String)\n    public_name = Column(String)\n    hobbies = Column(String)\n    def __repr__(self):\n        return \"<User(full_name='%s', public_name='%s', hobbies='%s')>\" % (\n            self.full_name,\n            self.public_name,\n            self.hobbies,\n        )\n\nclass PublicUserInfo(Base):\n    __tablename__ = 'public_users'\n    id = Column(Integer, primary_key=True)\n    public_name = Column(String)\n    hobbies = Column(String)\n    \nobj = UserInfo(\n            id=2,\n            full_name=\"Danny DeVito\",\n            public_name=\"dannyd\",\n            hobbies=\"acting, comedy, swimming\",\n        )\n\nresult = mapper.to(PublicUserInfo).map(obj)\n# same behaviour with preregistered mapping\n\n# filtering out protected fields that start with underscore \"_...\"\nprint({key: value for key, value in vars(result) if not key.startswith(\"_\")})\n# {'id': 2, 'public_name': 'dannyd', 'hobbies': \"acting, comedy, swimming\"}\n```\n\n## Create your own extension (Advanced)\nWhen you first time import `mapper` from `automapper` it checks default extensions and if modules are found for these extensions, then they will be automatically loaded for default `mapper` object.\n\n**What does extension do?** To know what fields in Target class are available for mapping, `py-automapper` needs to know how to extract the list of fields. There is no generic way to do that for all Python objects. For this purpose `py-automapper` uses extensions.\n\nList of default extensions can be found in [/automapper/extensions](/automapper/extensions) folder. You can take a look how it's done for a class with `__init__` method or for Pydantic or TortoiseORM models.\n\nYou can create your own extension and register in `mapper`:\n```python\nfrom automapper import mapper\n\nclass TargetClass:\n    def __init__(self, **kwargs):\n        self.name = kwargs[\"name\"]\n        self.age = kwargs[\"age\"]\n    \n    @staticmethod\n    def get_fields(cls):\n        return [\"name\", \"age\"]\n\nsource_obj = {\"name\": \"Andrii\", \"age\": 30}\n\ntry:\n    # Map object\n    target_obj = mapper.to(TargetClass).map(source_obj)\nexcept Exception as e:\n    print(f\"Exception: {repr(e)}\")\n    # Output:\n    # Exception: KeyError('name')\n\n    # mapper could not find list of fields from BaseClass\n    # let's register extension for class BaseClass and all inherited ones\n    mapper.add_spec(TargetClass, TargetClass.get_fields)\n    target_obj = mapper.to(TargetClass).map(source_obj)\n\n    print(f\"Name: {target_obj.name}; Age: {target_obj.age}\")\n```\n\nYou can also create your own clean Mapper without any extensions and define extension for very specific classes, e.g. if class accepts `kwargs` parameter in `__init__` method and you want to copy only specific fields. Next example is a bit complex but probably rarely will be needed:\n```python\nfrom typing import Type, TypeVar\n\nfrom automapper import Mapper\n\n# Create your own Mapper object without any predefined extensions\nmapper = Mapper()\n\nclass TargetClass:\n    def __init__(self, **kwargs):\n        self.data = kwargs.copy()\n\n    @classmethod\n    def fields(cls):\n        return [\"name\", \"age\", \"profession\"]\n\nsource_obj = {\"name\": \"Andrii\", \"age\": 30, \"profession\": None}\n\ntry:\n    target_obj = mapper.to(TargetClass).map(source_obj)\nexcept Exception as e:\n    print(f\"Exception: {repr(e)}\")\n    # Output:\n    # Exception: MappingError(\"No spec function is added for base class of <class 'type'>\")\n\n# Instead of using base class, we define spec for all classes that have `fields` property\nT = TypeVar(\"T\")\n\ndef class_has_fields_property(target_cls: Type[T]) -> bool:\n    return callable(getattr(target_cls, \"fields\", None))\n    \nmapper.add_spec(class_has_fields_property, lambda t: getattr(t, \"fields\")())\n\ntarget_obj = mapper.to(TargetClass).map(source_obj)\nprint(f\"Name: {target_obj.data['name']}; Age: {target_obj.data['age']}; Profession: {target_obj.data['profession']}\")\n# Output:\n# Name: Andrii; Age: 30; Profession: None\n\n# Skip `None` value\ntarget_obj = mapper.to(TargetClass).map(source_obj, skip_none_values=True)\nprint(f\"Name: {target_obj.data['name']}; Age: {target_obj.data['age']}; Has profession: {hasattr(target_obj, 'profession')}\")\n# Output:\n# Name: Andrii; Age: 30; Has profession: False\n```\n",
    "bugtrack_url": null,
    "license": "MIT License  Copyright (c) 2021 Andrii Nikolaienko  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.",
    "summary": "Library for automatically mapping one object to another",
    "version": "2.1.0",
    "project_urls": {
        "Changelog": "https://github.com/anikolaienko/py-automapper/blob/main/CHANGELOG.md",
        "Homepage": "https://github.com/anikolaienko/py-automapper",
        "Issues": "https://github.com/anikolaienko/py-automapper/issues",
        "Repository": "https://github.com/anikolaienko/py-automapper.git"
    },
    "split_keywords": [
        "utils",
        " dto",
        " object-mapper",
        " mapping",
        " development"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "8e2475b0e5ef92b5c07a6f0f62827775e299d74301b6062609f653febbf7b389",
                "md5": "ac0a3b5482c1e2f7bee6c8dd6c86848f",
                "sha256": "7f5976726375feb4977bf41065d8a1332e1ca6a45662617f53ef12a131405eb2"
            },
            "downloads": -1,
            "filename": "py_automapper-2.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "ac0a3b5482c1e2f7bee6c8dd6c86848f",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 13804,
            "upload_time": "2025-02-04T22:54:17",
            "upload_time_iso_8601": "2025-02-04T22:54:17.759785Z",
            "url": "https://files.pythonhosted.org/packages/8e/24/75b0e5ef92b5c07a6f0f62827775e299d74301b6062609f653febbf7b389/py_automapper-2.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "03b3b6ac4788d3f3de40328cbf1f3728901e9301608f8a6eb6f44df8dfa51999",
                "md5": "7d269b7a423b44c55978141aef3af502",
                "sha256": "ed7357a0a48124e72454d30a453dc5e80819d09f6060c80de8cc52f0a671e024"
            },
            "downloads": -1,
            "filename": "py_automapper-2.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "7d269b7a423b44c55978141aef3af502",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 20440,
            "upload_time": "2025-02-04T22:54:19",
            "upload_time_iso_8601": "2025-02-04T22:54:19.739006Z",
            "url": "https://files.pythonhosted.org/packages/03/b3/b6ac4788d3f3de40328cbf1f3728901e9301608f8a6eb6f44df8dfa51999/py_automapper-2.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-02-04 22:54:19",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "anikolaienko",
    "github_project": "py-automapper",
    "travis_ci": false,
    "coveralls": true,
    "github_actions": true,
    "lcname": "py-automapper"
}
        
Elapsed time: 0.45820s