Metadata-Version: 2.1
Name: prefab-classes
Version: 0.10.0
Summary: Boilerplate Generator for Classes
Author: David C Ellis
License: # License #
        # Prefab Classes License #
        
        ```
        MIT License
        
        Copyright (c) 2022-2023 David C Ellis
        
        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.
        ```
        
        
        ## Additional licenses ##
        
        
        ### David Beazley's Cluegen License ###
        
        Cluegen can be found here: https://github.com/dabeaz/cluegen/blob/master/cluegen.py
        
        Code from Cluegen is used in:
        
        * src/prefab_classes/live/autogen.py
        * src/prefab_classes/live/method_generators.py
        
        ```
        Classes generated from type clues.
        
            https://github.com/dabeaz/cluegen
        
        Author: David Beazley (@dabeaz).
                 http://www.dabeaz.com
        
        Copyright (C) 2018-2021.
        
        Permission is granted to use, copy, and modify this code in any
        manner as long as this copyright message and disclaimer remain in
        the source code.  There is no warranty.  Try to use the code for the
        greater good.
        ```
        
        
        ### PSF License for Python ###
        
        This isn't here just because this is a python project:
        the `PrefabHacker.get_code` method in `src/prefab_classes_hook/__init__.py`
        is a modified copy of the `SourceLoader.get_code` method in
        [_bootstrap_external.py](https://github.com/python/cpython/blob/85dd6cb6df996b1197266d1a50ecc9187a91e481/Lib/importlib/_bootstrap_external.py#L1074)
        
        The whole method needed to be modified to generate the different hash for mutual
        invalidation.
        
        Full License: https://github.com/python/cpython/blob/main/LICENSE
        
        ```
        PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
        --------------------------------------------
        
        1. This LICENSE AGREEMENT is between the Python Software Foundation
        ("PSF"), and the Individual or Organization ("Licensee") accessing and
        otherwise using this software ("Python") in source or binary form and
        its associated documentation.
        
        2. Subject to the terms and conditions of this License Agreement, PSF hereby
        grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
        analyze, test, perform and/or display publicly, prepare derivative works,
        distribute, and otherwise use Python alone or in any derivative version,
        provided, however, that PSF's License Agreement and PSF's notice of copyright,
        i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
        2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Python Software Foundation;
        All Rights Reserved" are retained in Python alone or in any derivative version
        prepared by Licensee.
        
        3. In the event Licensee prepares a derivative work that is based on
        or incorporates Python or any part thereof, and wants to make
        the derivative work available to others as provided herein, then
        Licensee hereby agrees to include in any such work a brief summary of
        the changes made to Python.
        
        4. PSF is making Python available to Licensee on an "AS IS"
        basis.  PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
        IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
        DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
        FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
        INFRINGE ANY THIRD PARTY RIGHTS.
        
        5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
        FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
        A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
        OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
        
        6. This License Agreement will automatically terminate upon a material
        breach of its terms and conditions.
        
        7. Nothing in this License Agreement shall be deemed to create any
        relationship of agency, partnership, or joint venture between PSF and
        Licensee.  This License Agreement does not grant permission to use PSF
        trademarks or trade name in a trademark sense to endorse or promote
        products or services of Licensee, or any third party.
        
        8. By copying, installing or otherwise using Python, Licensee
        agrees to be bound by the terms and conditions of this License
        Agreement.
        ```
Project-URL: Homepage, https://github.com/davidcellis/PrefabClasses
Project-URL: Documentation, https://prefabclasses.readthedocs.io/
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE.md
Requires-Dist: ducktools-lazyimporter >=0.2.2
Provides-Extra: build
Requires-Dist: build ; extra == 'build'
Requires-Dist: twine ; extra == 'build'
Provides-Extra: docs
Requires-Dist: sphinx ; extra == 'docs'
Requires-Dist: myst-parser ; extra == 'docs'
Requires-Dist: sphinx-rtd-theme ; extra == 'docs'
Provides-Extra: performance
Requires-Dist: attrs ; extra == 'performance'
Requires-Dist: pydantic ; extra == 'performance'
Requires-Dist: cattrs ; extra == 'performance'
Provides-Extra: testing
Requires-Dist: pytest ; extra == 'testing'
Requires-Dist: pytest-cov ; extra == 'testing'

# PrefabClasses - Python Class Boilerplate Generator  #
![PrefabClasses Test Status](https://github.com/DavidCEllis/PrefabClasses/actions/workflows/auto_test.yml/badge.svg?branch=main)

Writes the class boilerplate code so you don't have to. 
Yet another variation on attrs/dataclasses.

Unlike `dataclasses` or `attrs`, `prefab_classes` has a
focus on performance and startup time in particular.
This includes trying to minimise the impact of importing
the module itself.

Classes are written lazily when you first access the methods or
eagerly when the module is compiled into a .pyc or rewritten out
to a new .py source file.

The dynamic method of evaluating lazily is more flexible, while
the compiled method is faster (once the .pyc file has been generated).

For more detail look at the [documentation](https://prefabclasses.readthedocs.io).

## Usage ##

Define the class using plain assignment and `attribute` function calls:

```python
from prefab_classes import prefab, attribute

@prefab
class Settings:
    hostname = attribute(default="localhost")
    template_folder = attribute(default='base/path')
    template_name = attribute(default='index')
```

Or with type hinting:

```python
from prefab_classes import prefab

@prefab
class Settings:
    hostname: str = "localhost"
    template_folder: str = 'base/path'
    template_name: str = 'index'
```

In either case the result behaves the same.

```python
>>> from prefab_classes.funcs import to_json
>>> s = Settings()
>>> print(s)
Settings(hostname='localhost', template_folder='base/path', template_name='index')
>>> to_json(s)
'{"hostname": "localhost", "template_folder": "base/path", "template_name": "index"}'
```

For further details see the `usage` pages in the documentation.

## Why not just use attrs/dataclasses? ##

If attrs or dataclasses solves your problem then you should use them.
They are thoroughly tested, well supported packages. This is a new
project and has not had the rigorous real world testing of either
of those.

Dataclasses/attrs/pydantic all impose some overhead on startup time.
Prefab classes aims to minimise startup time and performance impact
of class generation and in doing so sacrifices some features or 
nice internals of these other implementations.

Import time example:

| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `python -c "pass"` | 26.6 ± 1.2 | 25.0 | 30.6 | 1.00 |
| `python -c "import prefab_classes"` | 28.4 ± 0.4 | 27.6 | 29.5 | 1.07 ± 0.05 |
| `python -c "import dataclasses"` | 48.4 ± 1.0 | 46.5 | 51.5 | 1.82 ± 0.09 |
| `python -c "import attrs"` | 67.3 ± 0.7 | 65.9 | 71.1 | 2.53 ± 0.11 |
| `python -c "import pydantic"` | 105.4 ± 3.4 | 100.3 | 115.7 | 3.96 ± 0.22 |

For more detailed tests you can look at the
[performance section of the docs](https://prefabclasses.readthedocs.io/en/latest/extra/performance_tests.html).

## How does it work ##

The `@prefab` decorator either rewrites the class dynamically, putting methods
in place that will be generated as they are first accessed **OR** it acts
as a marker to indicate the class should be transformed for the compiled
classes.

Compiled classes can both be imported directly or converted back to new .py
files. Direct import will perform the conversion before creating the .pyc file.

example.py
```python
# COMPILE_PREFABS
from prefab_classes import prefab, attribute
from pathlib import Path


@prefab(compile_prefab=True)
class SettingsPath:
    hostname = attribute(default="localhost")
    template_folder = attribute(default='base/path')
    template_name = attribute(default='index')
    file_types = attribute(default_factory=list)

    def __prefab_post_init__(self, template_folder, file_types):
        self.template_folder = Path(template_folder)
        file_types.extend(['.md', '.html'])
        self.file_types = file_types

```

Direct import using prefab_compiler

```python
from prefab_classes import prefab_compiler

with prefab_compiler():
    from example import SettingsPath

# Use normally from here
```

Compile to a new .py file using rewrite_to_py:

```python
>>> from prefab_classes.compiled import rewrite_to_py
>>> rewrite_to_py('example.py', 'example_compiled.py', use_black=True)
```

Using black to format for ease of reading.

example_compiled.py
```python
# DO NOT MANUALLY EDIT THIS FILE
# MODULE: example_compiled.py
# GENERATED FROM: example.py
# USING prefab_classes VERSION: v0.9.1

from pathlib import Path


class SettingsPath:
    COMPILED = True
    PREFAB_FIELDS = ["hostname", "template_folder", "template_name", "file_types"]
    __match_args__ = ("hostname", "template_folder", "template_name", "file_types")

    def __init__(
        self,
        hostname="localhost",
        template_folder="base/path",
        template_name="index",
        file_types=None,
    ):
        self.hostname = hostname
        self.template_name = template_name
        file_types = file_types if file_types is not None else list()
        self.__prefab_post_init__(
            template_folder=template_folder, file_types=file_types
        )

    def __repr__(self):
        return f"{type(self).__qualname__}(hostname={self.hostname!r}, template_folder={self.template_folder!r}, template_name={self.template_name!r}, file_types={self.file_types!r})"

    def __eq__(self, other):
        return (
            (self.hostname, self.template_folder, self.template_name, self.file_types)
            == (
                other.hostname,
                other.template_folder,
                other.template_name,
                other.file_types,
            )
            if self.__class__ == other.__class__
            else NotImplemented
        )

    def __prefab_post_init__(self, template_folder, file_types):
        self.template_folder = Path(template_folder)
        file_types.extend([".md", ".html"])
        self.file_types = file_types
```

If `compile_plain=True` is provided as an argument to `@prefab` the `COMPILED`
and `PREFAB_FIELD` variables will not be set on the class.

## Credit ##

`autogen` function and some magic method definitions taken from 
[David Beazley's Cluegen](https://github.com/dabeaz/cluegen)

General design based on previous experience using
[dataclasses](https://docs.python.org/3/library/dataclasses.html)
and [attrs](https://www.attrs.org/en/stable/) and trying to match the 
requirements for [PEP 681](https://peps.python.org/pep-0681/).
