repromancy 0.0.1
Utilize dark magic to unify reprs.
Requires Python: >=3.6

[![License: MIT](https://img.shields.io/badge/license-MIT-brightgreen?style=for-the-badge)](https://opensource.org/licenses/MIT)
[![Python 3.6 and Up](https://img.shields.io/badge/python->=3.6-blue?style=for-the-badge)](https://python.org)
[![Tests Status](https://img.shields.io/github/workflow/status/esoma/repromancy/Tests/master?style=for-the-badge&label=test)](https://github.com/esoma/repromancy/actions?query=workflow%3ATest)

#  :mage: repromancy :zombie:
**repromancy** utilizes dark magic to make better reprs, especially when
dynamically creating **classes** and **functions**.

## What?
In certain circumstances Python can give ugly reprs. For example, if we had a
package *spam* that had a submodule *ham* that defined the *Ham* class. The
repr is:
```python
<class 'spam.ham.Ham'>
```
All good, but let's say we actually import *Ham* directly in the *spam*
package, such that it is accessible as *spam.Ham*. In fact, this is the
interface we advertise to users, but when they do reprs on their *Hams* they
get this extra *ham* module that they don't really care about! What we would
like to see is:
```python
<class 'spam.Ham'>
```
Since that is how we define our public API, the underlying modules aren't
really relevant.

**repromancy** fixes this, and more. *at a price...*

## How?
**repromancy.cast** is used to perform the dark magic required to fix your
reprs. For a module, it's as simple as adding the following to the *bottom*
of your module:
```python
if __debug__:
    import repromancy
    repromancy.cast(repromancy.CallingModule)
    del repromancy
```

This will perform repromancy on everything within current module namespace.
Note that cast is recursive, it will follow everything it can, but will only
modify things in the same package as what was **cast** on.

**repromancy.cast** can be used on most python objects (not builtins).

## Dynamic Classes and Functions
Let's say we have a **function** called *factory*, that returns a newly
created **class** (or it could be another **function**, but we'll use a
**class** in this example):
```python
>>> def factory():
...     class Class:
...         pass
...     return Class
...
>>> factory()
<class '__main__.factory.<locals>.Class'>
```

The default repr is not terribly useful, especially because all **classes*
generated by the factory will have the same repr unless explicitly defined
by the factory function itself. Let's say that in our module we call this
factory multiple times and give the results local names, then
**cast repromancy** on the module:
```python
>>> ClassA = factory()
>>> ClassB = factory()
>>> import repromancy
>>> repromancy.cast(repromancy.CallingModule)
>>> ClassA
<class '__main__.ClassA'>
>>> ClassB
<class '__main__.ClassB'>
```
**repromancy** makes the reprs more clear.

## :goat: Dark magic? :biohazard:
**repromancy**, as its name implies, make use of some questionable methods to
get the results it does. Specifically, it monkey patches some cpython builtin
types, altering their \_\_repr\_\_ methods. This is a **global** change to
the python interpreter which affects all modules and subinterpreters. As such,
it is recommended to only perform repromancy when \_\_debug\_\_ is defined.

The specific changes are as follows:
* Instances of **type** will not display their __package__ in their repr if the
  fully qualified name starts with '<' (another repr)
* **functions** include the module in which they were defined in their repr, this is
  to be consistent with **object** and **type** reprs
* **methods** show the repr for their **function**, rather than just the name