While the best way to understand how spec-classes works is to look at the code,
this section of the documentation provides a high-level overview of how things
fit together so that looking at the code is less daunting.

At the end of the day, all that spec-classes does is add a few extra methods to
a class to make it behave in a consistent and convenient manner. Exactly which
methods get added depends on what is already implemented by the user (it never
clobbers user-specified methods), and the type of attribute for which methods
are being added (e.g. collection attributes). The functionality of these methods
can be broken down into two categories:

1. Magic methods that perform type-checking and value preparation/type-casting.
2. Regular methods that help with copy-on-write/builder-pattern mutations.

We'll deal with these separately below, but **all** methods added by
spec-classes are defined in [`spec_classes.methods`](https://github.com/matthewwardrop/spec-classes/tree/main/spec_classes/methods).
Rather than directly adding functions to the class, we add [`MethodDescriptor`](https://github.com/matthewwardrop/spec-classes/blob/main/spec_classes/methods/base.py)
instances that lazily generate the helper methods as they are required, and then
replace themselves with the actual implementation (except for magic methods
which we pre-materialize from the descriptors), reducing unnecessary overhead
and allowing the code to be more modular and extensible. Most methods are
generated by [`spec_classes.utils.method_builder.MethodBuilder`](https://github.com/matthewwardrop/spec-classes/blob/main/spec_classes/utils/method_builder.py),
which allows us to build methods with useful generated documentation (including
type-annotations) that users can introspect using standard `help()` calls.

Apart from the methods themselves, you should also know that all spec-class
specific metadata is stored in a `SpecClassMetadata` instance as the
`__spec_class__` attribute of a spec-class. This is regularly used by methods
to tailor their behaviour to the method being mutated.

## Magic Methods

The [Python data model](https://docs.python.org/3/reference/datamodel.html) is
very rich and allows for any class to customize all aspects of a class'
life-cycle using double-underscore (aka. "dunder") or "magic" methods.
Spec-classes leverages this data-model to intercept certain class instance
operations. All magic methods are implemented in [`spec_classes.methods.core`](https://github.com/matthewwardrop/spec-classes/tree/main/spec_classes/methods/core.py)

The most important magic method is `__setattr__`, which is exploited to
validate incoming values at run-time. In practice this method is defined such
that calls to `__setattr__` are redirected to the equivalent of calling the
`.with_<attr>` helper method so that direct attribute mutation follows the same
logic as the helper methods (except that mutations are done in-place by
default). This results in all values being type-checked (unless disabled
explicitly by some parameterization of the code pathway). Attributes that are
not managed by spec-classes are passed through to the default implementation of
`__setattr__`.

Another very important magic method is `__init__`, which is the default instance
constructor for Python classes. The implementation provided by spec-classes
ensures that every attribute is set correctly and is mutation safe, and respects
any constructor implementations defined on super classes.

The other methods implemented by spec-classes are:

- `__eq__`: Checks for equality of two instances of a spec-classes.
- `__repr__`: Provides a human-friendly representation of the spec-class state.
- `__getattr__`: A light-weight implementation that raises a user-friendly error
    if a managed spec-class attribute does not yet have a value, but otherwise
    introduces no new functionality.
- `__delattr__`: Deleting an attribute on a class instance should remove local
    attributes and unmask the base class attribute (if it exists). In
    spec-classes this is achieved by resetting the attribute to its original
    default value.
- `__deepcopy__`: During non-inplace mutations, spec-classes copies the entire
    class using `copy.deepcopy`. This magic method customizes the copying
    behavior to respect the options set on the spec-class.

## Helper methods

The helper method implementations are pretty straight-forward. The scalar
methods are defined in [spec_classes.methods.scalar](https://github.com/matthewwardrop/spec-classes/blob/main/spec_classes/methods/scalar.py),
and the collection-specific methods in [spec_classes.methods.collections](https://github.com/matthewwardrop/spec-classes/blob/main/spec_classes/methods/collections/).
All of these methods use the low level `mutate_attr` and `mutate_value` methods
defined in [spec_classes.utils.mutation](https://github.com/matthewwardrop/spec-classes/blob/main/spec_classes/utils/mutation.py).

## Stitching it all together

The main logic that stitches together the right metadata and methods is
contained within the [`spec_class` decorator](https://github.com/matthewwardrop/spec-classes/blob/main/spec_classes/spec_class.py). It is implemented as a class
so that users can subclass it downsteam, and to keep the logic in smaller
understandable chunks. This body of code is also responsible for setting up
the [deferred bootstrapping](../usage/advanced.md#immediate-bootstrapping) (if
necessary).
