Loading files
# Class Programming Guidelines
Object-orientation in Python is more akin to that of C++ than that of Java. For
example, what is an interface in Java is an abstract class with no properties
in C++ or Python.
The "interfaces" are not limited to methods- public properties are also part of
the interface.
## Mutability
LibreLane classes are based on the principle that objects that are passed
between contexts are either **immutable** on termination of construction or
**replicably modifiable**.
Immutable objects are just as described: they cannot be modified in-place.
They may be updated only by creating a copy. This is to prevent surprises
resulting from passing-by-reference endemic to imperative and object-oriented
programming.
They may, however, offer any number of read-only functions for convenience.
On the other hand, the replicably modifiable objects are those that handle
non-trivial computation, e.g. Steps or Flows. Classes in this hierarchy may have
a maximum of **one** public modifier adhering to these properties:
* This modifier may alter one or more properties of a class
* The modifier shall not depend on the initial values of any altered
property and may overwrite them if called again
* If called again, it is expected to return the same result (within reason-
not every aspect can be controlled, for example external filesystem
modification and/or timestamps.)
* This modifier may depend on any number of private or internal modifiers
* This modifier's implementation shall be split into two:
* A **public** part that is marked `@final`, i.e., it is not overridable.
This will handle validation of inputs and outputs and thus must not be
left to the whims of subclassers. It is responsible for calling the the
internal part.
* An **internal** part that is freely subclassable, however, it cannot be
called from outside the public part.
For {py:class}`librelane.flows.Flow`, for example, the public and internal parts
are {py:meth}`librelane.flows.Flow.start` and {py:meth}`librelane.flows.Flow.run`
respectively.
## Access Control
Python has almost no access control, subscribing to the notion of
"we're all adults," presuming said adults cannot be trusted to indent their own
code.
Unfortunately, it is difficult to write good object oriented code and have
a stable API without access control strictures. So we have decided to adopt
a convention:
### Private
Private properties and methods are prefixed by `__` (two underscores.) They may
be only be used inside the specific class they are declared in, and not its
super or subclasses.
### Internal
Internal properties, and methods are intended for things that are too
specific/hacky to be included as part of the API. They can be:
* Prefixed by `_` (one underscore)
* Not documented, i.e., a method or dynamic `@property` without a docstring
or a property not mentioned in the class's docstring.
See {doc}`/reference/api_stability_policy` for more information.
### Protected
Protected methods are marked with the `@protected` decorator. They may be used
inside the specific class they are declared in and any subclasses. The `protected`
decorator will append the docstring to clarify that fact.
```{note}
Protected methods are part of the LibreLane API and they are guaranteed to
remain functional even within external subclasses within the same major version.
```
### Public
Any methods or properties that are documented and lack prefixing underscores
are considered public, i.e., they may be used by any class or function inside
or outside the LibreLane codebase proper.
## Hierarchy and "Virtual" Public Variables/Methods
Classes in LibreLane rely on heavy use of polymorphism to define interface by
which multiple classes can interact with each other.
By our immutability standard, setters are by definition not in
consideration for this codebase. The choice of whether to use a getter or a
variable, however, is more involved and objects the following taxonomy:
```{digraph} taxonomy
start [label="Is the value expected to remain constant across all instances of a class?"]
class_dependent [label="Is the value computed from any other class variables or getters?"]
class_method [label="Class Method", peripheries=2]
class_property [label="Class Property", peripheries=2]
start -> class_dependent [label="Yes"]
class_dependent -> class_method [label="Yes"]
class_dependent -> class_property [label="No"]
dynamic [label="Is the value computed from any other instance variables or getters?"]
method [label="Instance Method", peripheries=2]
property [label="Instance Property", peripheries=2]
start -> dynamic [label="No"]
dynamic -> method [label="Yes"]
dynamic -> property [label="No"]
```
Furthermore, if the method is not useful in a base class, the method
(whether for class or instance) must be declared abstract using the
`@abstractmethod` decorator. Similarly, properties must be assigned
`NotImplemented` to declare that they are abstract. The former
is programmatically enforced, but the latter is currently not due to
technical limitations.