Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# How to Contribute Code

This document describes how the BO4E Python implementation is written and what to watch out for.

## Technical Setup in your IDE

We're using tox.
Please follow the instructions in our [Python Template Repository](https://github.com/Hochfrequenz/python_template_repository#how-to-use-this-repository-on-your-machine).
Feel free to open an issue if you run into any kind of problems.

## Coding Style and Guidelines

### General Rules

- We use (and enforce in the CI):
- black for formatting
- pylint for linting
- mypy for static type checking
- pytest for unittests
- Sphinx for documentation
- Technical Documentation is in English; For example: "Don't use the builtin validator here because …"
- But data model docstrings are in German; For example: "Ist das Ende nicht gesetzt, so ist der Zeitraum als offen zu verstehen."
- Docstrings should not be trivial/useless
- Bad: "Energiemenge ist eine Klasse zur Abbildung von Energiemengen." ❌ (no shit sherlock)
- Good: "Eine Energiemenge ordnet einer :class:`Marktlokation` oder :class:`Messlokation`, die über die `lokations_id` referenziert werden, einen oder mehrere Energieverbräuche zu." ✔
- Only sentences have a fullstop at the end.
- We use `snake_case` internally but serialize as `camelCase` by overriding the `data_key` property of the schema fields.

### How to Define an ENUM?

All Enums inherit from `bo4e.enum.StrEnum`.
The latter is just a usual Enum with a `str` mixin (see [the official docs](https://docs.python.org/3/library/enum.html?highlight=strenum#others) for details).
This allows us to precisly define how an enum value will be serialized.
All enum values have UPPER_CASE names.

```python
# pylint:disable=missing-module-docstring

from bo4e.enum.strenum import StrEnum


class MyBo4eEnum(StrEnum):
"""
Hier sollte ein deutscher Text stehen, der Sinn und Zweck des Enums beschreibt.
"""

FOO = "FOO" #: FOO ist, wenn der Himmel blau ist
BAR = "BAR"
"""
Der Docstring für BAR kann auch hier drunter stehen, wenn er besonders lang ist und mehr sagen will,
als dass BAR für die grüne Wiese steht. Denn manchmal braucht es mehr als hundert Zeichen.
"""
EIGENSCHAFT_0815 = "0815" #: manchmal heißen eigenschaften anders (EIGENSCHAFT_0815) als sie serialisiert werden ("0815")
# this typically happens for annoying enum values that contains "-" or start with digits
```

### How to Define `COM`s or `BO`s

All COMponents inherit from `bo4e.com.COM`.
All Business Objects inherit from `bo4e.bo.Geschaeftsobjekt`.

The classes are defined with the help of [`attrs`](https://www.attrs.org/).

For the de/serialization we use [`marshmallow`](https://marshmallow.readthedocs.io/).
Each Python attr-decorated class comes with a corresponding marshmallow schema.

All COMpontent schemas inherit from `bo4e.com.com.COMSchema`.
All Business Object schemas inherit from `bo4e.bo.Geschaeftsobjekt.GeschaeftsobjektSchema`.

```python
"""
Give the module a docstring to describe what it contains
"""

from datetime import datetime
from typing import Optional

import attr
from marshmallow import fields

from bo4e.bo.geschaeftsobjekt import Geschaeftsobjekt, GeschaeftsobjektSchema
from bo4e.com.menge import Menge, MengeSchema
from bo4e.enum.botyp import BoTyp


# pylint: disable=too-few-public-methods
@attr.s(auto_attribs=True, kw_only=True)
class MeinBo(Geschaeftsobjekt):
"""
MeinBo ist ein ganz besonderes Business Objekt.
Es kommt nur bei meinem Strom-Lieferanten zum Einsatz und beschreibt dort all die tollen Eingeschaften, die mein Verbrauchsverhalten hat.
"""

bo_typ: BoTyp = attr.ib(default=BoTyp.MEINBO)
# required attributes
#: Der Lieferbeginn beschreibt den Zeitpunkt ab dem (inklusiv) mich ein Versorger seinen Kunden nennen darf
lieferbeginn: datetime = attr.ib(validator=attr.validators.instance_of(datetime))

anzahl_freudenspruenge: int = attr.ib(validator=attr.validators.instance_of(int))
"""
Anzahl Freudensprünge beschreibt, wie oft der CEO des Stromkonzerns in die Luft gesprungen ist, als ich den Vertrag unterschrieben habe.
Dieser Wert sollte im Normalfall mindestens 5 sein.
"""
# todo: write a validator for anzahl_freudensprunge to be >5 and raise a ValidationError otherwise
# we can help you with anything you might be missing or unable to implement.
# ToDo comments are just fine.
# You don't need to be a perfect programmer to contribute to bo4e :)

#: Optionale Menge (Elektrische Energie oder Gas oder Wärme), die ich zum Lieferbeginn umsonst erhalte
freimenge: Optional[Menge] = attr.ib(
validator=attr.validators.optional(attr.validators.instance_of(Menge)), default=None
)


class MeinBoSchema(GeschaeftsobjektSchema):
"""
Schema for de-/serialization of :class:`MeinBo`
"""

# you don't need to copy paste the docstrings to the schema

class_name = MeinBo
# this is used so that the Python object created on deserialization/loading has the correct type

# required attributes
lieferbeginn = fields.DateTime()
# the camelCase serialization is enforced via he data_key kwarg
anzahl_freudenspruenge = fields.Int(data_key="anzahlFreudenspruenge")

# optional attributes
# when nesting you need to reference the schema here (not Menge but MengeSchema!)
freimenge = fields.Nested(MengeSchema, load_default=None)

```

### Unittests

Ideally provide unittests that show:

- that the BO/COM can be instantiated
- with only the required attributes
- with all attributes
- can be serialized and deserialized again
- with only the required attributes
- with all attributes

Therefore copy one of the existing "roundtrip" tests, see f.e. `TestTarifeinschraenkung`.

## Pull Request

Open a Pull Request against the main/default branch of this repository.
We'd appreciate if you allowed maintainer edits.

## Release Workflow

- Check with tox all tests and lintings: `tox`
- Check with tox if the packaging works fine: `tox -e test_packaging`
- Squash Merge all your changes you would like to have in the release into the main/default branch
- Check that all Github Actions for tests and linting do pass (should be automatically enforced for PRs against main)
- Go to the repositorys right side bar and click on "[Draft a new release](https://github.com/Hochfrequenz/BO4E-python/releases/new)"
- Write in the _Tag version_ field and in the _Release title_ your new version, i.e. `v0.0.6`
- Add a describtion to the release (or just autogenerate the change log which will be fine for 95% of cases)
- Publish the release

There is a Github Action which gets triggered by a release event.
It will run all default tests with tox.
If they pass, it will take the tag title to replace the version information in the _setup.cfg_ file.
After checking the package with `twine check` it will finally upload the new package release.
34 changes: 4 additions & 30 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,10 @@ Other Noteworthy BO4E Implementations
* `Kotlin`_
* `TypeScript`_

Contributing
Code Beiträge
============
Contributions are welcome.
Feel free to open a Pull Request against the master branch of this repository.
Please provide unit tests if you contribute logic beyond bare bare business object definitions.
For business objects please provide a roundtrip test that shows that your code produces serializable and deseriazable objects.

To enhance this BO4E implementation and contribute to this project check out the `master branch`_, install `tox`_ and set the virtual environment created by the command

.. code-block:: Shell

tox -e dev

The created venv should be located somewhere around .tox/dev/Scripts.
Änderungsvorschläge können direkt als Code in Form von Pull Requests eingereicht werden.
Details dazu finden sich im `Contribution Guide`_.


Versioning
Expand Down Expand Up @@ -94,21 +84,6 @@ To achieve this, we have to implement `Retrieving package version at runtime`_.

If you follow the instruction in the *release workflow*, you will get the version number which you define with the label name.

Release workflow
================
* Check with tox all tests and lintings: `tox`
* Check with tox if the packaging works fine: `tox -e test_packaging`
* Merge all your changes you would like to have in the release into the master branch
* Check that all Github actions for tests and linting do pass (should be automatically enforced for PRs against master)
* Go to `BO4E-python`_ and click on "`Draft a new release`_" in the right sidebar
* Write in the *Tag version* field and in the *Release title* your new version, i.e. `v0.0.6`
* Add a describtion to the release
* Publish the release

There is a Github Action which gets triggered by a release event.
It will run all default tests with tox. If they pass, it will take the tag title to replace the version information in the *setup.cfg* file.
After checking the package with `twine check` it will finally upload the new package release.

Hochfrequenz
============
`Hochfrequenz Unternehmensberatung GmbH`_ is a Grünwald (near Munich) based consulting company with offices in Berlin and Bremen.
Expand All @@ -126,7 +101,6 @@ Applications of talented developers are welcome at any time! Please consider vis
.. _`career page`: https://www.hochfrequenz.de/karriere/stellenangebote/full-stack-entwickler/
.. _`master branch`: https://github.com/Hochfrequenz/BO4E-python/tree/master
.. _`tox`: https://pypi.org/project/tox/
.. _`BO4E-python`: https://github.com/Hochfrequenz/BO4E-python
.. _`Draft a new release`: https://github.com/Hochfrequenz/BO4E-python/releases/new
.. _`Contribution Guide`: CONTRIBUTING.md
.. _`Retrieving package version at runtime`: https://pypi.org/project/setuptools-scm/
.. _`regex101`: https://regex101.com/r/JWeb51/2