lxml-dataclass


Namelxml-dataclass JSON
Version 1.0.2 PyPI version JSON
download
home_pageNone
SummaryCreate LXML Elements as dataclasses representations
upload_time2024-04-17 12:50:18
maintainerNone
docs_urlNone
authorNone
requires_python>=3.11
licenseMIT License Copyright (c) 2024 Sebastian Salinas Del Rio 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 lxml dataclass xml
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # LXML-DATACLASS

Utility to mirror Python Classes with lxml.etree Elements using class annotations and field descriptors.

```
⚠️ Warning: this package was created to help the owner with big xml file description and manipulation.
Use under your own risk. 
```

```
🆘 Help wanted. If you are insterested in continue the development of this package, send me a PM.
```

## Installation

```bash
pip install lxml-dataclass
```

## Basic usage

Lxml-dataclass uses big part of dataclasses implementation with some modifications that allow the implemmentation of two utility methods to the classes that inherit from `Element` base class `to_lxml_element` and `from_lxml_element`. Using the `element_field` function allows the metaclass to keep tracking of many lxml.etree attributes. 

Lets start with some basic examples:

```python
from lxml_dataclass import Element, element_field


class Author(Element):
    __tag__ = 'Author'
    
    name: str = element_field('Name')
    last: str = element_field('LastName', default='Doe')

    def fullname(self) -> str:
        return self.name + self.last

author = Author('John')
author.fullname()
# John Doe
```

As you can see in the given example we defined a class called `Author` with the attributes `name` and `last` as you can see these attributes where defined with their respectives types annotations and the field descriptor function `element_field` this functions accepts almost the same arguments the original dataclass `field` function uses except you must give as first argument the tag name the given attribute. The init method is automatically generated and you can overload it.

In the example above the tag for `name` will be `Name` and for `last` will be `LastName`

Now we will call the `to_lxml_element()` method to obtain the `lxml.etree.Element` representing this object

```python
import lxml.etree as ET

author_element = author.to_lxml_element() 
ET.tostring(author_element)
# b'<Author><Name>John</Name><LastName>Doe</LastName></Author>'
```

The Element class also includes another utility method called `to_string_element()` which calls the `ET.tostring()` function on the representing element and accepts the same keyword arguments. 

```python
author.to_string_element(pretty_print=True).decode('utf-8')
#<Author>
#  <Name>John</Name>
#  <LastName>Doe</LastName>
#</Author>
```

## Attribs and Nsmap
As you know all lxml Element accetps the `attrib` and `nsmap` arguments.
For Element classes you will define those attributes on `__attrib__` and `__nsmap__` class or instance attributes and for class attributes you will define them on the `element_field` method with the `attrib` and `nsmap` key word arguments. 

Lets add some attributes:

```python
class Author(Element):
    __tag__ = 'Author'
    __attrib__ = {'ID': 'Test ID'}
    
    name: str = element_field('Name', nsmap={None: 'suffix'})
    last: str = element_field('LastName', default='Doe')
```
Before we get the element string we will change the ID attribute.

```bash python
author.__attrib__['ID'] = 'Changed ID'
author.to_string_element(pretty_print=True).decode('utf-8')
#<Author ID="Changed ID">
#  <Name xmlns="suffix">John</Name>
#  <LastName>Doe</LastName>
#</Author>
```

# Class inheritance and composition

Inheritance functions exactly the same as dataclasses so you can inherit from other `Element` classes within the rules of dataclass and init generation.

Lets create an Element mixin that will add an ID to every instance who inherit it.

```python
from uuid import UUID, uuid4

class HasIDMixin(Element):

    id: UUID = element_field('Id', default_factory=uuid4)


class Author(HasIdMixin, Element):
    __tag__ = 'Author'
    
    name: str = element_field('Name')
    last: str = element_field('LastName', default='Doe')
```

```python
author = Author('John', 'Mayer')
author.to_string_element(pretty_print=True).decode('utf-8')
#<Author>
#  <Name>John</Name>
#  <LastName>Mayer</LastName>
#  <Id>a6ff6e02-eeb7-4ca5-8e4d-efedc40ee9ae</Id>
#</Author>
```

Now lets create a book element that will be a child of author

```python

class Book(Element):
    __tag__ = 'Book'

    name: str = element_field('Name')
    pages: int = element_field('Pages', default=50)


class Author(Element):
    __tag__ = 'Author'
    
    name: str = element_field('Name')
    last: str = element_field('LastName', default='Doe')
    book: Book | None = element_field('book', default=None) # Notice the tag is the same as the attribute name
```
```python
author = Author('John')
book = Book('My cool book', 80)
author.book = book
author.to_string_element(pretty_print=True).decode('utf-8')
#<Author>
#  <Name>John</Name>
#  <LastName>Doe</LastName>
#  <Book>
#    <Name>My cool book</Name>
#    <Pages>80</Pages>
#  </Book>
#</Author>
```

Now what if we want an Author to have multiple Books. Then we stablish the `is_iterable` special keyword on `element_field` function as follows

```python
class Author(Element):
    __tag__ = 'Author'
    
    name: str = element_field('Name')
    last: str = element_field('LastName', default='Doe')
    books: list[Book] = element_field('books', default_factory=list, is_iterable=True) # Notice the tag is the same as the attribute name

```
Now you could do 

```python
author = Author('John')
book_a = Book('My cool book A', 80)
book_b = Book('My cool book B')
author.books.append(book_a)
author.books.append(book_b)
author.to_string_element(pretty_print=True).decode('utf-8')
#<Author>
#  <Name>John</Name>
#  <LastName>Doe</LastName>
#  <Book>
#    <Name>My cool book A</Name>
#    <Pages>80</Pages>
#  </Book>
#  <Book>
#    <Name>My cool book B</Name>
#    <Pages>50</Pages>
#  </Book>
#</Author>
```

Now you can create really complex xml-class-representations with a simple and known dataclass approach, you can overload the utility methods to suit your needs.











            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "lxml-dataclass",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.11",
    "maintainer_email": null,
    "keywords": "lxml, dataclass, xml",
    "author": null,
    "author_email": "Sebastian Salinas <seba.salinas.delrio@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/68/30/23a2ffc14db098389c824befeb327d432c657b99a68502b0b588b3949e19/lxml_dataclass-1.0.2.tar.gz",
    "platform": null,
    "description": "# LXML-DATACLASS\n\nUtility to mirror Python Classes with lxml.etree Elements using class annotations and field descriptors.\n\n```\n\u26a0\ufe0f Warning: this package was created to help the owner with big xml file description and manipulation.\nUse under your own risk. \n```\n\n```\n\ud83c\udd98 Help wanted. If you are insterested in continue the development of this package, send me a PM.\n```\n\n## Installation\n\n```bash\npip install lxml-dataclass\n```\n\n## Basic usage\n\nLxml-dataclass uses big part of dataclasses implementation with some modifications that allow the implemmentation of two utility methods to the classes that inherit from `Element` base class `to_lxml_element` and `from_lxml_element`. Using the `element_field` function allows the metaclass to keep tracking of many lxml.etree attributes. \n\nLets start with some basic examples:\n\n```python\nfrom lxml_dataclass import Element, element_field\n\n\nclass Author(Element):\n    __tag__ = 'Author'\n    \n    name: str = element_field('Name')\n    last: str = element_field('LastName', default='Doe')\n\n    def fullname(self) -> str:\n        return self.name + self.last\n\nauthor = Author('John')\nauthor.fullname()\n# John Doe\n```\n\nAs you can see in the given example we defined a class called `Author` with the attributes `name` and `last` as you can see these attributes where defined with their respectives types annotations and the field descriptor function `element_field` this functions accepts almost the same arguments the original dataclass `field` function uses except you must give as first argument the tag name the given attribute. The init method is automatically generated and you can overload it.\n\nIn the example above the tag for `name` will be `Name` and for `last` will be `LastName`\n\nNow we will call the `to_lxml_element()` method to obtain the `lxml.etree.Element` representing this object\n\n```python\nimport lxml.etree as ET\n\nauthor_element = author.to_lxml_element() \nET.tostring(author_element)\n# b'<Author><Name>John</Name><LastName>Doe</LastName></Author>'\n```\n\nThe Element class also includes another utility method called `to_string_element()` which calls the `ET.tostring()` function on the representing element and accepts the same keyword arguments. \n\n```python\nauthor.to_string_element(pretty_print=True).decode('utf-8')\n#<Author>\n#  <Name>John</Name>\n#  <LastName>Doe</LastName>\n#</Author>\n```\n\n## Attribs and Nsmap\nAs you know all lxml Element accetps the `attrib` and `nsmap` arguments.\nFor Element classes you will define those attributes on `__attrib__` and `__nsmap__` class or instance attributes and for class attributes you will define them on the `element_field` method with the `attrib` and `nsmap` key word arguments. \n\nLets add some attributes:\n\n```python\nclass Author(Element):\n    __tag__ = 'Author'\n    __attrib__ = {'ID': 'Test ID'}\n    \n    name: str = element_field('Name', nsmap={None: 'suffix'})\n    last: str = element_field('LastName', default='Doe')\n```\nBefore we get the element string we will change the ID attribute.\n\n```bash python\nauthor.__attrib__['ID'] = 'Changed ID'\nauthor.to_string_element(pretty_print=True).decode('utf-8')\n#<Author ID=\"Changed ID\">\n#  <Name xmlns=\"suffix\">John</Name>\n#  <LastName>Doe</LastName>\n#</Author>\n```\n\n# Class inheritance and composition\n\nInheritance functions exactly the same as dataclasses so you can inherit from other `Element` classes within the rules of dataclass and init generation.\n\nLets create an Element mixin that will add an ID to every instance who inherit it.\n\n```python\nfrom uuid import UUID, uuid4\n\nclass HasIDMixin(Element):\n\n    id: UUID = element_field('Id', default_factory=uuid4)\n\n\nclass Author(HasIdMixin, Element):\n    __tag__ = 'Author'\n    \n    name: str = element_field('Name')\n    last: str = element_field('LastName', default='Doe')\n```\n\n```python\nauthor = Author('John', 'Mayer')\nauthor.to_string_element(pretty_print=True).decode('utf-8')\n#<Author>\n#  <Name>John</Name>\n#  <LastName>Mayer</LastName>\n#  <Id>a6ff6e02-eeb7-4ca5-8e4d-efedc40ee9ae</Id>\n#</Author>\n```\n\nNow lets create a book element that will be a child of author\n\n```python\n\nclass Book(Element):\n    __tag__ = 'Book'\n\n    name: str = element_field('Name')\n    pages: int = element_field('Pages', default=50)\n\n\nclass Author(Element):\n    __tag__ = 'Author'\n    \n    name: str = element_field('Name')\n    last: str = element_field('LastName', default='Doe')\n    book: Book | None = element_field('book', default=None) # Notice the tag is the same as the attribute name\n```\n```python\nauthor = Author('John')\nbook = Book('My cool book', 80)\nauthor.book = book\nauthor.to_string_element(pretty_print=True).decode('utf-8')\n#<Author>\n#  <Name>John</Name>\n#  <LastName>Doe</LastName>\n#  <Book>\n#    <Name>My cool book</Name>\n#    <Pages>80</Pages>\n#  </Book>\n#</Author>\n```\n\nNow what if we want an Author to have multiple Books. Then we stablish the `is_iterable` special keyword on `element_field` function as follows\n\n```python\nclass Author(Element):\n    __tag__ = 'Author'\n    \n    name: str = element_field('Name')\n    last: str = element_field('LastName', default='Doe')\n    books: list[Book] = element_field('books', default_factory=list, is_iterable=True) # Notice the tag is the same as the attribute name\n\n```\nNow you could do \n\n```python\nauthor = Author('John')\nbook_a = Book('My cool book A', 80)\nbook_b = Book('My cool book B')\nauthor.books.append(book_a)\nauthor.books.append(book_b)\nauthor.to_string_element(pretty_print=True).decode('utf-8')\n#<Author>\n#  <Name>John</Name>\n#  <LastName>Doe</LastName>\n#  <Book>\n#    <Name>My cool book A</Name>\n#    <Pages>80</Pages>\n#  </Book>\n#  <Book>\n#    <Name>My cool book B</Name>\n#    <Pages>50</Pages>\n#  </Book>\n#</Author>\n```\n\nNow you can create really complex xml-class-representations with a simple and known dataclass approach, you can overload the utility methods to suit your needs.\n\n\n\n\n\n\n\n\n\n\n",
    "bugtrack_url": null,
    "license": "MIT License  Copyright (c) 2024 Sebastian Salinas Del Rio  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": "Create LXML Elements as dataclasses representations",
    "version": "1.0.2",
    "project_urls": {
        "Homepage": "https://github.com/sebasalinass/lxml-dataclass"
    },
    "split_keywords": [
        "lxml",
        " dataclass",
        " xml"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "2580fd0fe29251f0fe319f47706737a3443d276596cfcea5e0028e42aa11a80f",
                "md5": "18c1b354b185aeb2da7c448fc90c06ff",
                "sha256": "d2eb131edf6e6d0eb3f7676272b56568097d06734c31f1279ae29c3183b417c1"
            },
            "downloads": -1,
            "filename": "lxml_dataclass-1.0.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "18c1b354b185aeb2da7c448fc90c06ff",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.11",
            "size": 11179,
            "upload_time": "2024-04-17T12:50:11",
            "upload_time_iso_8601": "2024-04-17T12:50:11.216080Z",
            "url": "https://files.pythonhosted.org/packages/25/80/fd0fe29251f0fe319f47706737a3443d276596cfcea5e0028e42aa11a80f/lxml_dataclass-1.0.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "683023a2ffc14db098389c824befeb327d432c657b99a68502b0b588b3949e19",
                "md5": "ac262b214baee2babd5ea1e981435057",
                "sha256": "7421d510863bce0d1b87a45a7cd205418b189f624343053c3505fea541259ddf"
            },
            "downloads": -1,
            "filename": "lxml_dataclass-1.0.2.tar.gz",
            "has_sig": false,
            "md5_digest": "ac262b214baee2babd5ea1e981435057",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.11",
            "size": 13050,
            "upload_time": "2024-04-17T12:50:18",
            "upload_time_iso_8601": "2024-04-17T12:50:18.276913Z",
            "url": "https://files.pythonhosted.org/packages/68/30/23a2ffc14db098389c824befeb327d432c657b99a68502b0b588b3949e19/lxml_dataclass-1.0.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-04-17 12:50:18",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "sebasalinass",
    "github_project": "lxml-dataclass",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [],
    "lcname": "lxml-dataclass"
}
        
Elapsed time: 0.25799s