XMLAccessor


NameXMLAccessor JSON
Version 1.0.0 PyPI version JSON
download
home_pageNone
SummaryPopulate Python Classes from XML
upload_time2025-09-05 00:08:41
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseApache NON-AI License, Version 2.0 TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. “License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. “Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. “Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. “You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License. “Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. “Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. “Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). “Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. “Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.” “Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form, under the following conditions: 2.1. You shall not use the Covered Software in the creation of an Artificial Intelligence training dataset, including but not limited to any use that contributes to the training or development of an AI model or algorithm, unless You obtain explicit written permission from the Contributor to do so. 2.2. You acknowledge that the Covered Software is not intended for use in the creation of an Artificial Intelligence training dataset, and that the Contributor has no obligation to provide support or assistance for any use that violates this license. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 1. You must give any other recipients of the Work or Derivative Works a copy of this License; and 2. You must cause any modified files to carry prominent notices stating that You changed the files; and 3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 4. If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS
keywords xml xml lxml cast populate nested xmlaccessor
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # XMLAccessor
This is a tool for loading python classes using XML.  

Loading xml to python classes sounds simple to begin with, XML is a nested structure and classes 
can be nested.  
The issue with directly casting to classes, or generating them, is you get highly nested structures, and cleaning them up afterwards is just as much work as parsing it yourself.  
Also if you only want a subsection of the data you end up needing to define a lot of classes just to pull it out.  

This library allows you to define the class structure you would like, then load the XML into it using a simple syntax.    
It is intended to be used in conjunction with the <tt>xml</tt> library and its elements, which are also used by the <tt>lxml</tt> library.   

## Install
<tt>pip install  XMLAccessor</tt>
### Include in file
<tt>from XMLAccesor import Accessor, SubAccessor, Transformer</tt>  

XMLAccessor uses 3 of its own classes as tools, <b>Accessor</b>, <b>SubAccessor</b> and <b>Transformer</b>.

(sidenote, what if they already have an __init__?)  
Let's say you have an XML file that looks like this 
```XML
<Application>
    <Section1>
        <Inner Type="test">Text Value</Inner>
        <Other>Something</Other>
    </Section1>
<Application>
```  
And you wanted to populate a class that looked like this 
```python
class Section1
    Type: str
    InnerText: str
    OtherText: str
```  
In order to populate those values, start by adding a LOADER_DICT attribute to the class.  
The LOADER_DICT has keys that should match your class attributes, and the values are list of Accessors, followed by any Transformers. That looks like this  
(explanation of those accessors is coming imminently)
```python
LOADER_DICT = {
    'Type' : [Accessor(tag='Section1'), Accessor(tag='Inner', attribute='Type')],
    'InnerText': [Accessor(tag='Section1'), Accessor(tag='Inner')],
    'Other' : [Accessor(tag='Section1'), Accessor(tag='Other')]
}
```  
So your resulting class looks like this   
```python
class Section1
    Type: str
    InnerText: str
    OtherText: str

    LOADER_DICT = {
    'Type' : [Accessor(tag='Section1'), Accessor(tag='Inner', attribute='Type')],
    'InnerText': [Accessor(tag='Section1'), Accessor(tag='Inner')],
    'Other' : [Accessor(tag='Section1'), Accessor(tag='Other')]
    }
```

Here the loader_dict is specifying how to access the values we'll store in each attribute of the dataclass.  
It does this through a chain of Accessors. 
## Accessor
Each Accessor is a class with 3 optional attributes
```python
tag: str
attribute: str 
subs: list[SubAccessor]
```
### tag attribute
Specifying a tag means it will find the first element with that tag. If you end your Accessor list with an Accessor that has only its .tag attribute populated, the text found within that tag is returned as a value.  
As seen above, the Accessors all look inside the 'Section1' element, then keep looking, in the case of InnerText and Other, the next Accessor only has a tag, so the values inside these tags are returned.  
### attribute attribute
At the end of your accessors you can specify an attribute of an element to retrieve.  
As you can't nest any further elements inside the attribute we take this as the end of the chain.   
In the above example, the second Accessor under the 'Type' key, gets the attribute inside the Inner tag.  
### Subs 
These are for loading classes within classes, they're explained in [SubAccessors](#subaccessors), but these docs make more sense in order. 
## load_class_from_element
This is the function you'll need to call to populate the class. Once you have a class with a LOADER_DICT, and an xml string or file loaded into an ET.Element, run:  
```python 
resulting_class = XMLAccessor.load_class_from_element({resulting class}, {xml element})
```
The syntax is:
```python
def load_class_from_element(cast_class: any, element: ET.Element)
```
(ET is an alias for xml.etree.ElementTree, these are interchangable for the lxml library elements.)
## SubAccessors
XML often re-uses elements, so you might want to replicate that in your class structure.  
Say we have XML like this 
```XML
<Family>
    <Person>
        <Age>50</Age>
        <Name>
            <FirstName>First</FirstName>
            <MiddleName>Middle</MiddleName>
            <LastName>Last</LastName>
        <Name>
    </Person>
    <Cat>
        <Colour>Orange</Colour>
        <Name>
            <FirstName>Moofie</FirstName>
            <MiddleName>The</MiddleName>
            <LastName>Cat</LastName>
        </Name>
</Family>
```  
You'd probably want to define your classes so that Person and Cat don't have all the name fields, but they have a .Name attribute to store a Name instance.   
You might make your class structure like this. 
```python
class Name:
    FirstName: str
    MiddleName: str
    LastName: str

class Person
    Age: int
    Name: Name

class Cat:
    Colour: str
    Name: Name
```
This is when we'll make use of the <tt>subs</tt> atribute of the Accessor. 
The SubAccessor has three attributes 
```python
tag_name: str
cast_class: any
is_list: bool = False
```
<b>tag_name</b> tells it what tag to look for  
<b>cast_class</b> tells it what the resulting class will be 
<b>is_list</b> tells it whether to look for a number of these elements and return a list, it defaults to False.  
To allow the nested class to be populated, we create an Accessor that looks like this <tt>Accessor(subs=[SubAccessor(tag_name='Name', cast_class=Name, is_list=False)])  </tt>
So the resulting loading classes would look like this (assuming you load Family as the root node)  
```python
class Name:
    FirstName: str
    MiddleName: str
    LastName: str

    LOADER_DICT = {
        'FirstName': [Accessor('FirstName')],
        'MiddleName': [Accessor('MiddleName')],
        'LastName': [Accessor('LastName')]
    }

class Person
    Age: int
    Name: Name

    LOADER_DICT = {
        'Age': [Accessor('Age'), Transformer(int)],
        'Name': [Accessor(subs=[SubAccessor('Name', Name)])],
    }

class Cat:
    Colour: str
    Name: Name

    LOADER_DICT = {
        'Colour': [Accessor('Colour')],
        'Name': [Accessor(subs=[SubAccessor('Name', Name)])]
    }
```
You'll also note that example of a Transformer. It's turning the str (all XML values must be strings) into an int. They're explained in the [Transformer section](#transformer).  
### Lists of elements
Let's we're looking at a person, who may have several phone numbers. 
```xml
<Person>
    <Name>Phillip</Name>
    <Phones>
        <Phone>
            <Mobile>04758294353</Mobile>
        </Phone>
        <Phone>
            <FixedPhone AreaCode="+61">12345678</FixedPhone>
        </Phone>
    </Phones>
</Person>
```   
I would want to populate the .phones on the Person class with a list of Phone instances.   
Create your innermost class first, so in this case Phone class, and its LOADER_DICT.  
The LOADER_DICT loading is non-strict, so if it can't find a value it won't populate it. This allows you to make more flexible classes, like in this example a phone with a Mobile and a Phone with a FixedPhone can share the same class.  
```python
class Phone:
    Mobile: str
    FixedPhone: str
    AreaCode: str
    
    LOADER_DICT = {
        'Mobile': [Accessor('Mobile')],
        'FixedPhone': [Accessor('FixedPhone')],
        'AreaCode': [Accessor('FixedPhone', 'AreaCode')]
    }
```   
And the person
```python
class Person:
    Name: str
    PhoneList: List[Phone]

    LOADER_DICT = {
        'Name' = [Accessor('Name')],
        'PhoneList': [Accessor(tag='Phones', subs=[SubAccessor('Phone', Phone, True)])]
    }
```  
See here the PhoneList accessor accesses the Phones element, and then within that we search for a list of 'Phone' tags, casting them to Phone class.
## Transformer
Transformers allow you to modify the value found by the Accessors.  
They just have one attribute '.func'. This can be any callable, as long as it returns a value.   
Add them to the LOADER_DICT at the end of a list of Accessors:  
```python
class Person
    Age: int

    LOADER_DICT = {
        'Age' : [Accessort('Person', 'Age'), Transformer(int)]
    }
```
(this works because int is a function in python). Remember to pass in the function without the (),  
You can add as many of them as you want, they just have to occur <b>AFTER</b> the Accessors for a given key, and must return a value.  
## Utility Functions
I've thrown in a couple of utility functions that work well in conjunction with casting xml to python classes.   
There are currently 2 of them, if you want to use them change your import to 
<tt>from XMLAccessor import XMLAccessor, Accessor, SubAccessor, Transformer</tt>
The utils are 2 static methods from the XMLAccessor class. 
### to_dict 
Once you populate nested classes in python it's surprisingly difficult to turn them into a printable structure.  
If you directly print it, you get one layer deep; if you try to json dump it you get a un-serialisable error. This makes it difficult to debug.  
I created a function to recursively convert the classes into dicts, so then you can print this or write it to a file. 
Call it with 
```python 
resulting_dict = XMLAccessor.to_dict({YOUR CLASS})
```
### find_class_by_value
In XML you often have one element refer to another using a unique ID. For example: 
```xml
<Family>
    <Person>
        <ID>IDABC</ID>
        <Name>Peter</Name>
        <Relative type="Son">IDXYZ</Relationship>
    </Person>
    <Person>
        <ID>IDXYZ</ID>
        <Name>Paul</Name>
        <Relative type="Father">IDABC</Relationship>
    </Person>
</Family>
``` 
(Usually the relationships are one-way, but it's just an example).  
Say you wanted to get all father-son pairs in your incoming data, you could read the relative field on a person, and if 
its type = 'Son', go get the Person with the following ID as their ID.  
This is where you would use find_class_by_value.  
Once you've cast this xml into classes, call 
<tt>resultingperson = XMLAccessor.find_class_by_value(Family, 'ID', 'IDABC')</tt>  
The syntax is 
```python
def find_class_by_value(root_class: any, attribute: str, match_value: any):
```  
So you pass in the starting point, then the attribute you want to match on, and the value you want to find. It will recursively go through
all the attributes to find the resulting class.  
### Note about __init__
To maintain flexibility the load_class_from_element needs to be able to instantiate an empty class then add attributes to it. For this reason you can't 
define an __init__ function on your classes with any arguments, otherwise we'll get a missing argument error when the function runs.  

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "XMLAccessor",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "XML, xml, lxml, cast, populate, nested, xmlaccessor",
    "author": null,
    "author_email": "Phillip Harry T <philliptinsley44@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/88/f3/645c3bf55b983b229ba929675e7a967bd3b89c8857eb3dae6d821a6476d6/xmlaccessor-1.0.0.tar.gz",
    "platform": null,
    "description": "# XMLAccessor\nThis is a tool for loading python classes using XML.  \n\nLoading xml to python classes sounds simple to begin with, XML is a nested structure and classes \ncan be nested.  \nThe issue with directly casting to classes, or generating them, is you get highly nested structures, and cleaning them up afterwards is just as much work as parsing it yourself.  \nAlso if you only want a subsection of the data you end up needing to define a lot of classes just to pull it out.  \n\nThis library allows you to define the class structure you would like, then load the XML into it using a simple syntax.    \nIt is intended to be used in conjunction with the <tt>xml</tt> library and its elements, which are also used by the <tt>lxml</tt> library.   \n\n## Install\n<tt>pip install  XMLAccessor</tt>\n### Include in file\n<tt>from XMLAccesor import Accessor, SubAccessor, Transformer</tt>  \n\nXMLAccessor uses 3 of its own classes as tools, <b>Accessor</b>, <b>SubAccessor</b> and <b>Transformer</b>.\n\n(sidenote, what if they already have an __init__?)  \nLet's say you have an XML file that looks like this \n```XML\n<Application>\n    <Section1>\n        <Inner Type=\"test\">Text Value</Inner>\n        <Other>Something</Other>\n    </Section1>\n<Application>\n```  \nAnd you wanted to populate a class that looked like this \n```python\nclass Section1\n    Type: str\n    InnerText: str\n    OtherText: str\n```  \nIn order to populate those values, start by adding a LOADER_DICT attribute to the class.  \nThe LOADER_DICT has keys that should match your class attributes, and the values are list of Accessors, followed by any Transformers. That looks like this  \n(explanation of those accessors is coming imminently)\n```python\nLOADER_DICT = {\n    'Type' : [Accessor(tag='Section1'), Accessor(tag='Inner', attribute='Type')],\n    'InnerText': [Accessor(tag='Section1'), Accessor(tag='Inner')],\n    'Other' : [Accessor(tag='Section1'), Accessor(tag='Other')]\n}\n```  \nSo your resulting class looks like this   \n```python\nclass Section1\n    Type: str\n    InnerText: str\n    OtherText: str\n\n    LOADER_DICT = {\n    'Type' : [Accessor(tag='Section1'), Accessor(tag='Inner', attribute='Type')],\n    'InnerText': [Accessor(tag='Section1'), Accessor(tag='Inner')],\n    'Other' : [Accessor(tag='Section1'), Accessor(tag='Other')]\n    }\n```\n\nHere the loader_dict is specifying how to access the values we'll store in each attribute of the dataclass.  \nIt does this through a chain of Accessors. \n## Accessor\nEach Accessor is a class with 3 optional attributes\n```python\ntag: str\nattribute: str \nsubs: list[SubAccessor]\n```\n### tag attribute\nSpecifying a tag means it will find the first element with that tag. If you end your Accessor list with an Accessor that has only its .tag attribute populated, the text found within that tag is returned as a value.  \nAs seen above, the Accessors all look inside the 'Section1' element, then keep looking, in the case of InnerText and Other, the next Accessor only has a tag, so the values inside these tags are returned.  \n### attribute attribute\nAt the end of your accessors you can specify an attribute of an element to retrieve.  \nAs you can't nest any further elements inside the attribute we take this as the end of the chain.   \nIn the above example, the second Accessor under the 'Type' key, gets the attribute inside the Inner tag.  \n### Subs \nThese are for loading classes within classes, they're explained in [SubAccessors](#subaccessors), but these docs make more sense in order. \n## load_class_from_element\nThis is the function you'll need to call to populate the class. Once you have a class with a LOADER_DICT, and an xml string or file loaded into an ET.Element, run:  \n```python \nresulting_class = XMLAccessor.load_class_from_element({resulting class}, {xml element})\n```\nThe syntax is:\n```python\ndef load_class_from_element(cast_class: any, element: ET.Element)\n```\n(ET is an alias for xml.etree.ElementTree, these are interchangable for the lxml library elements.)\n## SubAccessors\nXML often re-uses elements, so you might want to replicate that in your class structure.  \nSay we have XML like this \n```XML\n<Family>\n    <Person>\n        <Age>50</Age>\n        <Name>\n            <FirstName>First</FirstName>\n            <MiddleName>Middle</MiddleName>\n            <LastName>Last</LastName>\n        <Name>\n    </Person>\n    <Cat>\n        <Colour>Orange</Colour>\n        <Name>\n            <FirstName>Moofie</FirstName>\n            <MiddleName>The</MiddleName>\n            <LastName>Cat</LastName>\n        </Name>\n</Family>\n```  \nYou'd probably want to define your classes so that Person and Cat don't have all the name fields, but they have a .Name attribute to store a Name instance.   \nYou might make your class structure like this. \n```python\nclass Name:\n    FirstName: str\n    MiddleName: str\n    LastName: str\n\nclass Person\n    Age: int\n    Name: Name\n\nclass Cat:\n    Colour: str\n    Name: Name\n```\nThis is when we'll make use of the <tt>subs</tt> atribute of the Accessor. \nThe SubAccessor has three attributes \n```python\ntag_name: str\ncast_class: any\nis_list: bool = False\n```\n<b>tag_name</b> tells it what tag to look for  \n<b>cast_class</b> tells it what the resulting class will be \n<b>is_list</b> tells it whether to look for a number of these elements and return a list, it defaults to False.  \nTo allow the nested class to be populated, we create an Accessor that looks like this <tt>Accessor(subs=[SubAccessor(tag_name='Name', cast_class=Name, is_list=False)])  </tt>\nSo the resulting loading classes would look like this (assuming you load Family as the root node)  \n```python\nclass Name:\n    FirstName: str\n    MiddleName: str\n    LastName: str\n\n    LOADER_DICT = {\n        'FirstName': [Accessor('FirstName')],\n        'MiddleName': [Accessor('MiddleName')],\n        'LastName': [Accessor('LastName')]\n    }\n\nclass Person\n    Age: int\n    Name: Name\n\n    LOADER_DICT = {\n        'Age': [Accessor('Age'), Transformer(int)],\n        'Name': [Accessor(subs=[SubAccessor('Name', Name)])],\n    }\n\nclass Cat:\n    Colour: str\n    Name: Name\n\n    LOADER_DICT = {\n        'Colour': [Accessor('Colour')],\n        'Name': [Accessor(subs=[SubAccessor('Name', Name)])]\n    }\n```\nYou'll also note that example of a Transformer. It's turning the str (all XML values must be strings) into an int. They're explained in the [Transformer section](#transformer).  \n### Lists of elements\nLet's we're looking at a person, who may have several phone numbers. \n```xml\n<Person>\n    <Name>Phillip</Name>\n    <Phones>\n        <Phone>\n            <Mobile>04758294353</Mobile>\n        </Phone>\n        <Phone>\n            <FixedPhone AreaCode=\"+61\">12345678</FixedPhone>\n        </Phone>\n    </Phones>\n</Person>\n```   \nI would want to populate the .phones on the Person class with a list of Phone instances.   \nCreate your innermost class first, so in this case Phone class, and its LOADER_DICT.  \nThe LOADER_DICT loading is non-strict, so if it can't find a value it won't populate it. This allows you to make more flexible classes, like in this example a phone with a Mobile and a Phone with a FixedPhone can share the same class.  \n```python\nclass Phone:\n    Mobile: str\n    FixedPhone: str\n    AreaCode: str\n    \n    LOADER_DICT = {\n        'Mobile': [Accessor('Mobile')],\n        'FixedPhone': [Accessor('FixedPhone')],\n        'AreaCode': [Accessor('FixedPhone', 'AreaCode')]\n    }\n```   \nAnd the person\n```python\nclass Person:\n    Name: str\n    PhoneList: List[Phone]\n\n    LOADER_DICT = {\n        'Name' = [Accessor('Name')],\n        'PhoneList': [Accessor(tag='Phones', subs=[SubAccessor('Phone', Phone, True)])]\n    }\n```  \nSee here the PhoneList accessor accesses the Phones element, and then within that we search for a list of 'Phone' tags, casting them to Phone class.\n## Transformer\nTransformers allow you to modify the value found by the Accessors.  \nThey just have one attribute '.func'. This can be any callable, as long as it returns a value.   \nAdd them to the LOADER_DICT at the end of a list of Accessors:  \n```python\nclass Person\n    Age: int\n\n    LOADER_DICT = {\n        'Age' : [Accessort('Person', 'Age'), Transformer(int)]\n    }\n```\n(this works because int is a function in python). Remember to pass in the function without the (),  \nYou can add as many of them as you want, they just have to occur <b>AFTER</b> the Accessors for a given key, and must return a value.  \n## Utility Functions\nI've thrown in a couple of utility functions that work well in conjunction with casting xml to python classes.   \nThere are currently 2 of them, if you want to use them change your import to \n<tt>from XMLAccessor import XMLAccessor, Accessor, SubAccessor, Transformer</tt>\nThe utils are 2 static methods from the XMLAccessor class. \n### to_dict \nOnce you populate nested classes in python it's surprisingly difficult to turn them into a printable structure.  \nIf you directly print it, you get one layer deep; if you try to json dump it you get a un-serialisable error. This makes it difficult to debug.  \nI created a function to recursively convert the classes into dicts, so then you can print this or write it to a file. \nCall it with \n```python \nresulting_dict = XMLAccessor.to_dict({YOUR CLASS})\n```\n### find_class_by_value\nIn XML you often have one element refer to another using a unique ID. For example: \n```xml\n<Family>\n    <Person>\n        <ID>IDABC</ID>\n        <Name>Peter</Name>\n        <Relative type=\"Son\">IDXYZ</Relationship>\n    </Person>\n    <Person>\n        <ID>IDXYZ</ID>\n        <Name>Paul</Name>\n        <Relative type=\"Father\">IDABC</Relationship>\n    </Person>\n</Family>\n``` \n(Usually the relationships are one-way, but it's just an example).  \nSay you wanted to get all father-son pairs in your incoming data, you could read the relative field on a person, and if \nits type = 'Son', go get the Person with the following ID as their ID.  \nThis is where you would use find_class_by_value.  \nOnce you've cast this xml into classes, call \n<tt>resultingperson = XMLAccessor.find_class_by_value(Family, 'ID', 'IDABC')</tt>  \nThe syntax is \n```python\ndef find_class_by_value(root_class: any, attribute: str, match_value: any):\n```  \nSo you pass in the starting point, then the attribute you want to match on, and the value you want to find. It will recursively go through\nall the attributes to find the resulting class.  \n### Note about __init__\nTo maintain flexibility the load_class_from_element needs to be able to instantiate an empty class then add attributes to it. For this reason you can't \ndefine an __init__ function on your classes with any arguments, otherwise we'll get a missing argument error when the function runs.  \n",
    "bugtrack_url": null,
    "license": "Apache NON-AI License, Version 2.0\n        \n        TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n        \n        1. Definitions.\n        \n        \u201cLicense\u201d shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.\n        \n        \u201cLicensor\u201d shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.\n        \n        \u201cLegal Entity\u201d shall mean the union of the acting entity and all other entities that control, are controlled by,\n        or are under common control with that entity. For the purposes of this definition, \u201ccontrol\u201d means (i) the power, direct or indirect,\n        to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%)\n        or more of the outstanding shares, or (iii) beneficial ownership of such entity.\n        \n        \u201cYou\u201d (or \u201cYour\u201d) shall mean an individual or Legal Entity exercising permissions granted by this License.\n        \n        \u201cSource\u201d form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source,\n        and configuration files.\n        \n        \u201cObject\u201d form shall mean any form resulting from mechanical transformation or translation of a Source form, including\n        but not limited to compiled object code, generated documentation, and conversions to other media types.\n        \n        \u201cWork\u201d shall mean the work of authorship, whether in Source or Object form, made available under the License,\n        as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).\n        \n        \u201cDerivative Works\u201d shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and\n        for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship.\n        For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name)\n        to the interfaces of, the Work and Derivative Works thereof.\n        \n        \u201cContribution\u201d shall mean any work of authorship, including the original version of the Work and any modifications or additions to\n        that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or\n        by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, \u201csubmitted\u201d\n        means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to\n        communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of,\n        the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated\n        in writing by the copyright owner as \u201cNot a Contribution.\u201d\n        \n        \u201cContributor\u201d shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and\n        subsequently incorporated within the Work.\n        \n        2. Grant of Copyright License.\n        \n        Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,\n        no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform,\n        sublicense, and distribute the Work and such Derivative Works in Source or Object form,\n        under the following conditions:\n        \n          2.1. You shall not use the Covered Software in the creation of an Artificial Intelligence training dataset,\n          including but not limited to any use that contributes to the training or development of an AI model or algorithm,\n          unless You obtain explicit written permission from the Contributor to do so.\n        \n          2.2. You acknowledge that the Covered Software is not intended for use in the creation of an Artificial Intelligence training dataset,\n          and that the Contributor has no obligation to provide support or assistance for any use that violates this license.\n        \n        3. Grant of Patent License.\n        \n        Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,\n        royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import,\n        and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are\n        necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which\n        such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit)\n        alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement,\n        then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.\n        \n        4. Redistribution.\n        \n        You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications,\n        and in Source or Object form, provided that You meet the following conditions:\n        \n          1. You must give any other recipients of the Work or Derivative Works a copy of this License; and\n        \n          2. You must cause any modified files to carry prominent notices stating that You changed the files; and\n        \n          3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark,\n          and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and\n          \n          4. If the Work includes a \u201cNOTICE\u201d text file as part of its distribution, then any Derivative Works that You distribute\n          must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that\n          do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works;\n          or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear.\n          The contents of the NOTICE file are for informational purposes only and do not modify the License.\n        \n        You may add Your own attribution notices within Derivative Works that You distribute,\n        alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as\n        modifying the License.\n        You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use,\n        reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction,\n        and distribution of the Work otherwise complies with the conditions stated in this License.\n        \n        5. Submission of Contributions.\n        \n        Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor\n        shall be under the terms and conditions of this License, without any additional terms or conditions.\n        Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed\n        with Licensor regarding such Contributions.\n        \n        6. Trademarks.\n        \n        This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor,\n        except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.\n        \n        7. Disclaimer of Warranty.\n        \n        Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions)\n        on an \u201cAS IS\u201d BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation,\n        any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.\n        You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated\n        with Your exercise of permissions under this License.\n        \n        8. Limitation of Liability.\n        \n        In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise,\n        unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing,\n        shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages\n        of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages\n        for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses),\n        even if such Contributor has been advised of the possibility of such damages.\n        \n        9. Accepting Warranty or Additional Liability.\n        \n        While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support,\n        warranty, indemnity, or other liability obligations and/or rights consistent with this License.\n        However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility,\n        not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by,\n        or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.\n        \n        END OF TERMS AND CONDITIONS",
    "summary": "Populate Python Classes from XML",
    "version": "1.0.0",
    "project_urls": null,
    "split_keywords": [
        "xml",
        " xml",
        " lxml",
        " cast",
        " populate",
        " nested",
        " xmlaccessor"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "f5caf7d76852b2000b8abfd9e0969951b96d8a2fee64cb97b7168b8f259107ad",
                "md5": "9abc287038c5ed8fcf4574eb4820065c",
                "sha256": "f464eb8ec6875d7046ff095416b854bea5e2cb53d13b80d3bf6362af1dc22fe5"
            },
            "downloads": -1,
            "filename": "xmlaccessor-1.0.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "9abc287038c5ed8fcf4574eb4820065c",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 14996,
            "upload_time": "2025-09-05T00:08:38",
            "upload_time_iso_8601": "2025-09-05T00:08:38.062560Z",
            "url": "https://files.pythonhosted.org/packages/f5/ca/f7d76852b2000b8abfd9e0969951b96d8a2fee64cb97b7168b8f259107ad/xmlaccessor-1.0.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "88f3645c3bf55b983b229ba929675e7a967bd3b89c8857eb3dae6d821a6476d6",
                "md5": "66206e1ffb986d8382a3051fde4efc80",
                "sha256": "2343097bdf4a91aa3637213e212a05db8fe01cef58a5a217536bb1993e98f2bc"
            },
            "downloads": -1,
            "filename": "xmlaccessor-1.0.0.tar.gz",
            "has_sig": false,
            "md5_digest": "66206e1ffb986d8382a3051fde4efc80",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 18766,
            "upload_time": "2025-09-05T00:08:41",
            "upload_time_iso_8601": "2025-09-05T00:08:41.424338Z",
            "url": "https://files.pythonhosted.org/packages/88/f3/645c3bf55b983b229ba929675e7a967bd3b89c8857eb3dae6d821a6476d6/xmlaccessor-1.0.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-09-05 00:08:41",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "xmlaccessor"
}
        
Elapsed time: 0.45726s