diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 2b7af01..22fd24b 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -5,62 +5,61 @@ # This workflow uploads a Python Package using Twine when a release is created. # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries -# name: Upload Python Package +name: Upload Python Package -# on: -# release: -# types: [created, edited] +on: + release: + types: [ created, edited ] -# jobs: -# tests: -# if: startsWith(github.ref, 'refs/tags/v') -# runs-on: ${{ matrix.os }} -# strategy: -# matrix: -# python-version: ["3.12"] -# os: [ubuntu-latest] -# steps: -# - uses: actions/checkout@v4 -# - name: Set up Python ${{ matrix.python-version }} -# uses: actions/setup-python@v5 -# with: -# python-version: ${{ matrix.python-version }} -# - name: Install tox -# run: | -# python -m pip install --upgrade pip -# pip install tox -# - name: Run tox -# run: | -# tox - -# build-n-publish: -# name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI -# runs-on: ${{ matrix.os }} -# strategy: -# matrix: -# python-version: [ "3.12" ] -# os: [ ubuntu-latest ] -# # Specifying a GitHub environment, # Specifying a GitHub environment, which is strongly recommended by PyPI: https://docs.pypi.org/trusted-publishers/adding-a-publisher/ -# # you have to create an environment in your repository settings and add the environment name here -# environment: release -# permissions: -# # IMPORTANT: this permission is mandatory for trusted publishing -# id-token: write -# needs: tests -# steps: -# - uses: actions/checkout@v4 -# - name: Set up Python ${{ matrix.python-version }} -# uses: actions/setup-python@v5 -# with: -# python-version: ${{ matrix.python-version }} -# - name: Install dependencies -# run: | -# python -m pip install --upgrade pip -# pip install -r dev_requirements/requirements-packaging.txt -# - name: Build wheel and source distributions -# run: | -# python -m build -# - name: Publish distribution 📦 to PyPI -# if: startsWith(github.ref, 'refs/tags/v') -# uses: pypa/gh-action-pypi-publish@release/v1 +jobs: + tests: + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ${{ matrix.os }} + strategy: + matrix: + python-version: [ "3.12" ] + os: [ ubuntu-latest ] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install tox + run: | + python -m pip install --upgrade pip + pip install tox + - name: Run tox + run: | + tox + build-n-publish: + name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI + runs-on: ${{ matrix.os }} + strategy: + matrix: + python-version: [ "3.12" ] + os: [ ubuntu-latest ] + # Specifying a GitHub environment, # Specifying a GitHub environment, which is strongly recommended by PyPI: https://docs.pypi.org/trusted-publishers/adding-a-publisher/ + # you have to create an environment in your repository settings and add the environment name here + environment: release + permissions: + # IMPORTANT: this permission is mandatory for trusted publishing + id-token: write + needs: tests + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r dev_requirements/requirements-packaging.txt + - name: Build wheel and source distributions + run: | + python -m build + - name: Publish distribution 📦 to PyPI + if: startsWith(github.ref, 'refs/tags/v') + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/README.md b/README.md index d5a7cd8..8ff24df 100644 --- a/README.md +++ b/README.md @@ -1,271 +1,53 @@ -# Python Template Repository including a `tox.ini`, Unittests&Coverage, Pylint & MyPy Linting Actions and a PyPI Publishing Workflow +# MaLo Ident Python Models - +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) +![Python Versions (officially) supported](https://img.shields.io/pypi/pyversions/maloident.svg) +![Pypi status badge](https://img.shields.io/pypi/v/maloident) +![Unittests status badge](https://github.com/Hochfrequenz/malo-ident-python-models/workflows/Unittests/badge.svg) +![Coverage status badge](https://github.com/Hochfrequenz/malo-ident-python-models/workflows/Coverage/badge.svg) +![Linting status badge](https://github.com/Hochfrequenz/malo-ident-python-models/workflows/Linting/badge.svg) +![Black status badge](https://github.com/Hochfrequenz/malo-ident-python-models/workflows/Formatting/badge.svg) -![Unittests status badge](https://github.com/Hochfrequenz/python_template_repository/workflows/Unittests/badge.svg) -![Coverage status badge](https://github.com/Hochfrequenz/python_template_repository/workflows/Coverage/badge.svg) -![Linting status badge](https://github.com/Hochfrequenz/python_template_repository/workflows/Linting/badge.svg) -![Black status badge](https://github.com/Hochfrequenz/python_template_repository/workflows/Formatting/badge.svg) +This package provides mostly autogenerated [pydantic](https://docs.pydantic.dev/latest/)-based model classes for the MaLo ident API. -This is a template repository. -It doesn't contain any useful code but only a minimal working setup for a Python project including: - -- a basic **project structure** with - - tox.ini - - `pyproject.toml` where the project metadata and dependencies are defined - - and a requirements.txt derived from it - - an example class - - an example unit test (using pytest) -- ready to use **Github Actions** for - - [pytest](https://pytest.org) - - [code coverage measurement](https://coverage.readthedocs.io) (fails below 80% by default) - - [pylint](https://pylint.org/) (only accepts 10/10 code rating by default) - - [mypy](https://github.com/python/mypy) (static type checks where possible) - - [black](https://github.com/psf/black) code formatter check - - [isort](https://pycqa.github.io/isort/) import order check - - [codespell](https://github.com/codespell-project/codespell) spell check (including an ignore list) - - autoresolve dev-dependencies with `tox -e compile_requirements` - - ready-to-use publishing workflow for pypi (see readme section below) - -By default, it uses Python version 3.12. - -This repository uses a [`src`-based layout](https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/). -This approach has many advantages and basically means for developers, that all business logic lives in the `src` directory. - -## How to use this Repository on Your Machine - -### Installation of Tox / Creating the tox base venv -If you ever set up your toxbase virtual environment already, skip this first step and continue with the project-specific setup. - -
- - Creating the toxbase from scratch (windows) - - -You can either follow the [installation instructions](https://tox.readthedocs.io/en/latest/installation.html)) and that a `.toxbase` environment has been created. -Here we repeat the most important steps. - -#### Enure you are allowed to execute scripts in powershell (Windows only) -On new Windows machines it is possible that the execution policy is set to restricted and you are not allowed execute scripts. You can find detailed information [here](https://learn.microsoft.com/de-de/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-7.3). - -The quickest way to solve this problem: Open an Administrator Powershell (e.g. Windows PowerShell App, right click: 'Run as Adminstrator') -```ps -Set-ExecutionPolicy -ExecutionPolicy AllSigned -``` -Then close the admin powershell and continue in the regular shell. - -#### Create the `.toxbase` environment -`.toxbase` is a project independent virtual environment-template for all the tox environments on your machine. If anything is weird during the tox installation or after the installation, try turning your computer off and on again before getting too frustrated. -Ask your Hochfrequenz colleagues for help. - -```ps -# Change to your user directory, create tools directory if it does not exist -$ cd C:\Users\YourUserName -# Create a virtual environment called .toxbase -$ python -m venv .toxbase -``` - -then -```ps -# Windows Powershell -$ .\.toxbase\Scripts\Activate.ps1 -# XOR Windows default (e.g. cmder) -λ .toxbase\Scripts\activate.bat -# the virtual environment is active -# if you see the environment name at the beginning of the line -(.toxbase) $ python -m pip install --upgrade pip -(.toxbase) $ pip install tox -(.toxbase) $ tox --version -``` - -#### Add the toxbase interpreter to the Path environment variable -Finally, we need to make the tox command available in all future terminal sessions. -There are ways to achieve this goal using only the powershell commands, but we just use the "regular" way: - -* Type systemvariable in the search field of your windows taskbar. -* Click on Edit system variables, then on environment variables. -* In the next window select Path in the upper part (User variables for YourUserName) and click on edit. -* Add a new path with `C:\Users\YourUserName\.toxbase\Scripts\` - * ⚠️ You have to replace YourUserName with your actual username in the path! - the path up to .toxbase has already been printed to the CLI in the tox --version command above - -* Save the settings. -* Now you have to sign out and in again to make the changes work. - -You should now be able to type the following and get a reasonable answer -``` -tox --version -``` -in every shell, no matter if you activated the toxbase again. - -#### Umlaute in Paths -Tox has an issue if you have an umlaut in your username. [This issue](https://github.com/tox-dev/tox/issues/1550#issuecomment-727824763) is well known. - -To solve it you have to add another environment variable `PYTHONIOENCODING` with the value `utf-8` ([source](https://github.com/tox-dev/tox/issues/1550#issuecomment-1011952057)). - -Start a new PowerShell session and try to run tox -e dev in your repository again. - -
- -
- - Creating the toxbase from scratch (unix) - -Open a terminal and execute the following commands - -```sh -# Change to your user directory -$ cd ~ -# Create a virtual environment called .toxbase -$ python -m venv .toxbase -``` -Now we activate the virtual environment, update pip and install tox: - -``` -$ source .toxbase/bin/activate -# the virtual environment is active -# if you see the environment name at the beginning of the line -(.toxbase) $ python -m pip install --upgrade pip -(.toxbase) $ pip install tox -(.toxbase) $ tox --version -``` -Create a new folder bin in the home directory and add a symbolic link inside -``` -cd -# create a `bin` directory -mkdir bin -# set link to ~/bin/tox -ln -s ~/.toxbase/bin/tox ~/bin/tox -``` -Set the PATH variable - -``` -cd -# open the config file .bashrc -nano .bashrc -# Go to the bottom of the file and insert -# make tox accessible in each session from everywhere -PATH = "${HOME}/bin:${PATH}" -export PATH -# save and close the file with CTRL+O and CTRL+X -``` -#### fish -``` -cd -# open the config.fish file -nano ~/.config/fish/config.fish -# Go to the bottom of the file and insert -# make tox accessible in each session from everywhere -set PATH {$HOME}/bin $PATH -# save and close the file with CTRL+O and CTRL+X -``` -Check if everything works by opening a new terminal window and run -```bash -tox --version -``` - -
- -### Creating the project-specific dev environment. -If tox is set up, you're ready to start: - 1. clone the repository, you want to work in - 2. create the `dev` environment on your machine. To do this: - a) Open a Powershell - b) change directory to your repository -and finally type +It does not provide you with an HTTP client. +## Installation +Install it from [PyPI](https://pypi.org/projects/maloident) ```bash -tox -e dev +pip install maloident ``` -You have now created the development environment (dev environment). It is the environment which contains both the usual requirements as well as the testing and linting tools. +Then use it: -### How to use with PyCharm - -1. You have cloned the repository, you want to work in, and have created the virtual environment, in which the repository should be executed (`your_repo/.tox/dev`). Now, to actually work inside the newly created environment, you need to tell PyCharm (your IDE) that it should use the virtual environment - to be more precise: the interpreter of this dev environment. How to do this: -a) navigate to: File ➡ Settings (Strg + Alt + S) ➡ Project: your_project ➡ Python Interpreter ➡ Add interpreter ➡ Existing -b) Choose as interpreter: `your_repo\.tox\dev\Scripts\python.exe` (under windows) -2. Set the default test runner of your project to pytest. How to do it: -a) navigate to Files ➡ Settings ➡ Tools ➡ Python integrated tools ➡ Testing: Default test runner -b) Change to "pytest" -If this doesn't work anymore, see [the PyCharm docs](https://www.jetbrains.com/help/pycharm/choosing-your-testing-framework.html) -3. Set the `src` directory as sources root. How to do this: -right click on 'src' ➡ "Mark directory as…" ➡ sources root -If this doesn't work anymore, see: [PyCharm docs](https://www.jetbrains.com/help/pycharm/content-root.html). -Setting the `src` directory right, allows PyCharm to effectively suggest import paths. -If you ever see something like `from src.mypackage.mymodule import ...`, then you probably forgot this step. -5. Set the working directory of the unit tests to the project root (instead of the unittest directory). How to do this: -a) Open any test file whose name starts with `test_` in unit tests/tests -b) Right click inside the code ➡ More Run/Debug ➡ Modify Run Configuration ➡ expand Environment collapsible ➡ Working directory -c) Change to `your_repo` instead of `your_repo\unittests` -By doing so, the import and other file paths in the tests are relative to the repo root. -If this doesn't work anymore, see: [working directory of the unit tests](https://www.jetbrains.com/help/pycharm/creating-run-debug-configuration-for-tests.html) - -### How to use with VS Code -All paths mentioned in this section are relative to the repository root. - -1. Open the folder with VS Code. -2. **Select the python interpreter** ([official docs](https://code.visualstudio.com/docs/python/environments#_manually-specify-an-interpreter)) which is created by tox. Open the command pallett with `CTRL + P` and type `Python: Select Interpreter`. Select the interpreter which is placed in `.tox/dev/Scripts/python.exe` under Windows or `.tox/dev/bin/python` under Linux and macOS. -3. **Set up pytest and pylint**. Therefore we open the file `.vscode/settings.json` which should be automatically generated during the interpreter setup. If it doesn't exist, create it. Insert the following lines into the settings: - -```json -{ - "python.testing.unittestEnabled": false, - "python.testing.nosetestsEnabled": false, - "python.testing.pytestEnabled": true, - "pythonTestExplorer.testFramework": "pytest", - "python.testing.pytestArgs": ["unittests"], - "python.linting.pylintEnabled": true +```python +from maloident.models import ResultNegative +my_json = { + "decisionTree": "E_0594", + "responseCode": "A10", + "reason": "Ich bin ein Freitext.", + "networkOperator": 9900987654321, } +result = ResultNegative.model_validate(my_json) ``` -4. Create a `.env` file and insert the following line +The request payload type for the Lieferant➡️Netzbetreiber identification request is `maloident.models.IdentificationParameter`. -For Windows: +See the [tests](unittests/test_models.py) for more examples. -``` -PYTHONPATH=src;${PYTHONPATH} -``` +## Project Structure +This project is based on [`datamodel-code-generator`](https://github.com/koxudaxi/datamodel-code-generator/). +Most of the classes are autogenerated from the [`openapi.yml`](openapi/openapi.yml) which can be found on [SwaggerHub](https://app.swaggerhub.com/apis/edi-energy/MaLoIdent_2024-07-03/v1.0.0). -For Linux and Mac: +Note that we fixed some errors in the official OpenAPI spec. +Our changes are mentioned at the beginning of the [`openapi.yml`](openapi/openapi.yml) file. +After updating the `openapi.yml` file, use +```bash +tox -e codegen ``` -PYTHONPATH=src:${PYTHONPATH} -``` - -This makes sure, that the imports are working for the unittests. -At the moment I am not totally sure that it is the best practise, but it's getting the job done. - -5. Enjoy 🤗 - -## Publishing on PyPI - -This repository contains all necessary CI steps to publish any project created from it on PyPI. -It uses the trusted publishers workflow as described in the [official Python documentation](https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/). -It just requires some manual adjustments/settings depending on your project: - -1. Fill out the metadata in the [`pyproject.toml`](pyproject.toml); Namely the package name and the dependencies which should be in sync with your `requirements.in`. -2. Uncomment the lines in [`.github/workflows/python-publish.yml`](.github/workflows/python-publish.yml) -3. Create a [new environment in your GitHub repository](https://github.com/Hochfrequenz/python_template_repository/settings/environments) and call it `release`. -4. Set up a new trusted publisher [in your PYPI account](https://pypi.org/manage/account/publishing/). - 1. PyPI Project Name: The name which you defined in the `pyproject.toml` is the name of the project which you have to enter here. - 2. Owner: The GitHub organization name or GitHub username that owns the repository - 3. Repository name: The name of the GitHub repository that contains the publishing workflow - 4. Workflow name: The filename of the publishing workflow. This file should exist in the .github/workflows/ directory in the repository configured above. Here in our case: `python-publish.yml` - 5. Environment name: The name of the GitHub Actions environment that the above workflow uses for publishing. Here in our case: `release` -5. Now create a release by clicking on "Create new release" in the right Github sidebar (or visit `github.com/your-username/your-reponame/releases/new`). This should trigger the workflow (see the "Actions" tab of your repo). -6. Check if the action failed. If it succeeded your PyPI account should now show the new project. It might take some minutes until the package can be installed via `pip install packagename` because the index has to be updated. -7. Now create another PyPI token with limited scope and update the Github repository secret accordingly. +to re-generate the model classes. ## Contribute You are very welcome to contribute to this template repository by opening a pull request against the main branch. - -### GitHub Actions - -- Dependabot auto-approve / -merge: - - If the actor is the Dependabot bot (i.e. on every commit by Dependabot) - the pull request is automatically approved and auto merge gets activated - (using squash merge). - Note that if you haven't enabled "auto merge" for your repository, the auto merge activation will fail. - If you want to use a merge type other than "squash merge" you have to edit the workflow. diff --git a/domain-specific-terms.txt b/domain-specific-terms.txt index ee2a9a2..165ecbb 100644 --- a/domain-specific-terms.txt +++ b/domain-specific-terms.txt @@ -1 +1,7 @@ # contains 1 lower case word per line which are ignored in the spell_check +ressource +titels +als +dokument +ist +oder diff --git a/openapi/openapi.yml b/openapi/openapi.yml new file mode 100644 index 0000000..599053a --- /dev/null +++ b/openapi/openapi.yml @@ -0,0 +1,949 @@ +# This file has been copied from swagger hub. +# It's used in the auto-code generation with tox -e codegen +# Because - as always with EDI@Energy - things don't work out of the box, we adjusted some parts of the yaml to get working code: +# 1. The enum 'marketLocationProperty': +# a) It's missing the 'measured' valued which is given as example but not part of the enum members. +# b) there is a typo: 'nonActice' should be 'nonActive' + + +openapi: 3.0.0 +servers: + # Added by API Auto Mocking Plugin + - description: SwaggerHub API Auto Mocking + url: https://virtserver.swaggerhub.com/edi-energy/MaLoIdent_2024-07-03/1.0.0 +info: + title: EDI@Energy API-Webdienste zur Ermittlung der MaLo-ID der Marktlokation + description: " Publikationsdatum: 03.07.2024\n

Ziel

\n\n Gemäß Festlegung für einen beschleunigten werktäglichen Lieferantenwechsel in 24 Stunden (LFW24) der Bundesnetzagentur vom 21. März 2024 [BNetzA-Beschluss BK6-22-024](https://www.bundesnetzagentur.de/DE/Beschlusskammern/1_GZ/BK6-GZ/2022/BK6-22-024/BK6-22-024_Verfahren.html?nn=660086), ist der Use-Case GPKE Teil 2 - Fokus Zuordnungsprozesse, Kapitel 1.1. Ermittlung der MaLo-ID der Marktlokation, über API-Webdienste zu realisieren. + + + Hinweis: IT-Systeme zur Umsetzung der API müssen grundsätzlich in der Lage sein, gleichzeitig allen berechtigten Kommunikationspartnern einen Verbindungausbau (TCP-Connenct) zu ermöglichen." + + version: v1.0.0 +tags: +- name: Anfrage MaLo-ID der Marktlokation + externalDocs: + description: Find out more + url: https://edi-energy.de +- name: Rückmeldung auf Anfrage + externalDocs: + description: Find out more + url: https://edi-energy.de +paths: + /maloId/request/v1: + post: + tags: + - Anfrage MaLo-ID der Marktlokation + summary: Anfrage MaLo-ID der Marktlokation + description: "Inhaltsdatensicherungsebene: +Der API-Aufruf mit der enthaltenen Payload der hier spezifizierten API-Schnittstellen wird signiert." + operationId: uploadFile + parameters: + - name: transactionId + in: header + description: Externe Transaktions-Id zur eindeutigen Identifikation der Anfrage der MaLo-ID der Marktlokation des sendenden Marktpartners. + required: true + style: simple + explode: false + schema: + $ref: '#/components/schemas/transactionId' + - name: creationDateTime + in: header + description: Zeitpunkt an dem der Aufruf erstellt wurde + required: true + style: simple + explode: false + schema: + $ref: '#/components/schemas/creationDateTime' + - name: initialTransactionId + in: header + description: Zur Angabe des Idempodenzschlüssel im Falle eines Retry. + required: false + style: simple + explode: false + schema: + $ref: '#/components/schemas/initialTransactionId' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/identificationParameter' + responses: + "202": + description: accepted + "400": + description: Bad Request + "401": + description: Unauthorized + "404": + description: Not Found + "405": + description: Method not allowed + "500": + description: Internal Server Error + /maloId/dataForMarketLocationNegative/v1: + post: + tags: + - Rückmeldung auf Anfrage + summary: Negative Rückmeldung auf die Anfrage der MaLo-ID der Marktlokation. + description: "Inhaltsdatensicherungsebene: +Der API-Aufruf mit der enthaltenen Payload der hier spezifizierten API-Schnittstellen wird signiert." + parameters: + - name: transactionId + in: header + description: Externe Transaktions-Id zur eindeutigen Identifikation der negativen Rückmeldung auf die Anfrage der MaLo-ID der Marktlokation des sendenden Marktpartners. + required: true + style: simple + explode: false + schema: + $ref: '#/components/schemas/transactionId' + - name: creationDateTime + in: header + description: Zeitpunkt an dem der Aufruf erstellt wurde + required: true + style: simple + explode: false + schema: + $ref: '#/components/schemas/creationDateTime' + - name: initialTransactionId + in: header + description: Zur Angabe des Idempodenzschlüssel im Falle eines Retry. + required: false + style: simple + explode: false + schema: + $ref: '#/components/schemas/initialTransactionId' + - name: referenceId + in: query + description: "Externe Vorgangsreferenz zur eindeutigen Identifikation des ursprünglichen Aufrufs (Wert aus transactionId, Schritt1 des UC Ermittlung der MaLo-ID der Marktlokation)." + required: true + style: form + explode: false + schema: + $ref: '#/components/schemas/referenceId' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/resultNegative' + + + responses: + "202": + description: accepted + "400": + description: Bad Request + "401": + description: Unauthorized + "404": + description: Not Found + "405": + description: Method not allowed + "500": + description: Internal Server Error + /maloId/dataForMarketLocationPositive/v1: + post: + tags: + - Rückmeldung auf Anfrage + summary: Postive Rückmeldung auf die Anfrage der MaLo-ID der Marktlokation. + description: "Inhaltsdatensicherungsebene: +Der API-Aufruf mit der enthaltenen Payload der hier spezifizierten API-Schnittstellen wird signiert." + operationId: "" + parameters: + - name: transactionId + in: header + description: Externe Transaktions-Id zur eindeutigen Identifikation der postiven Rückmeldung auf die Anfrage der MaLo-ID der Marktlokation des sendenden Marktpartners. + required: true + style: simple + explode: false + schema: + $ref: '#/components/schemas/transactionId' + - name: creationDateTime + in: header + description: Zeitpunkt an dem der Aufruf erstellt wurde + required: true + style: simple + explode: false + schema: + $ref: '#/components/schemas/creationDateTime' + - name: initialTransactionId + in: header + description: Zur Angabe des Idempodenzschlüssel im Falle eines Retry. + required: false + style: simple + explode: false + schema: + $ref: '#/components/schemas/initialTransactionId' + - name: referenceId + in: query + description: "Externe Vorgangsreferenz zur eindeutigen Identifikation der ursprünglichen Anfrage (Wert aus transactionId, Schritt1 des UC Ermittlung der MaLo-ID der Marktlokation)." + required: true + style: form + explode: false + schema: + $ref: '#/components/schemas/referenceId' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/resultPositive' + responses: + "202": + description: accepted + "400": + description: Bad Request + "401": + description: Unauthorized + "404": + description: Not Found + "405": + description: Method not allowed + "500": + description: Internal Server Error +components: + schemas: + + address: + type: object + properties: + countryCode: + $ref: '#/components/schemas/countryCode' + zipCode: + $ref: '#/components/schemas/zipCode' + city: + $ref: '#/components/schemas/city' + street: + $ref: '#/components/schemas/street' + houseNumber: + $ref: '#/components/schemas/houseNumber' + houseNumberAddition: + $ref: '#/components/schemas/houseNumberAddition' + + + + + + + + + + city: + type: string + description: Angabe des Ortes der Marktlokationsadresse + example: Berlin + company: + type: string + description: Angabe des Firmennamen + example: BDEW & Co. KG + + + countryCode: + type: string + pattern: "[A-Z]{2}" + description: Angabe des Ländercodes nach ISO-3166-1 Alpha-2 + example: DE + + + + customerNumber: + type: string + description: Zur Angabe der Kundennummer des Kunden beim bisherigen Lieferanten (LFA) + example: V567345345 + + + + + + + + creationDateTime: + pattern: "20(\\d{2}(\\-(0[13578]|1[02])\\-(0[1-9]|[12]\\d|3[01])|\\-02\\-(0[1-9]|1\\d|2[0-8])|\\-(0[469]|11)\\-(0[1-9]|[12]\\d|30))|([02468][048]|[13579][26])\\-02\\-(29))T([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(\\.[\\d]{1,4})?Z" + type: string + description: Zeitpunkt an dem der Aufruf erstellt wurde + example: 2023-08-01T12:30:00.1704Z + dataControllableResource: + required: + - dataMControllableResourceMarketPartner + - srId + type: object + properties: + srId: + $ref: '#/components/schemas/srId' + dataControllableResourceMeasuringPointOperators: + $ref: '#/components/schemas/dataControllableResourceMeasuringPointOperators' + dataControllableResources: + type: array + items: + $ref: '#/components/schemas/dataControllableResource' + dataControllableResourceMeasuringPointOperators: + type: array + items: + $ref: '#/components/schemas/srMarketPartner' + dataMarketLocation: + required: + - dataMarketLocationNetworkOperators + - dataMarketLocationTransmissionSystemOperators + - energyDirection + - maloId + - measurementTechnologyClassification + - optionalChangeForecastBasis + - dataMarketLocationProperties + type: object + properties: + maloId: + $ref: '#/components/schemas/maloId' + energyDirection: + $ref: '#/components/schemas/energyDirection' + measurementTechnologyClassification: + $ref: '#/components/schemas/measurementTechnologyClassification' + optionalChangeForecastBasis: + $ref: '#/components/schemas/optionalChangeForecastBasis' + dataMarketLocationProperties: + $ref: '#/components/schemas/dataMarketLocationProperties' + dataMarketLocationNetworkOperators: + $ref: '#/components/schemas/dataMarketLocationNetworkOperators' + dataMarketLocationMeasuringPointOperators: + $ref: '#/components/schemas/dataMarketLocationMeasuringPointOperators' + dataMarketLocationTransmissionSystemOperators: + $ref: '#/components/schemas/dataMarketLocationTransmissionSystemOperators' + dataMarketLocationSuppliers: + $ref: '#/components/schemas/dataMarketLocationSuppliers' + dataMarketLocationName: + $ref: '#/components/schemas/name' + dataMarketLocationAddress: + $ref: '#/components/schemas/address' + dataMarketLocationLandParcels: + $ref: '#/components/schemas/landParcels' + dataMarketLocationGeographicCoordinates: + $ref: '#/components/schemas/geographicCoordinates' + + dataMarketLocationMeasuringPointOperators: + type: array + items: + $ref: '#/components/schemas/marketLocationMeasuringPointOperator' + + dataMarketLocationProperties: + type: array + items: + $ref: '#/components/schemas/marketLocationProperties' + + + dataMarketLocationNetworkOperators: + type: array + items: + $ref: '#/components/schemas/marketLocationNetworkOperator' + + dataMarketLocationSuppliers: + type: array + items: + $ref: '#/components/schemas/marketLocationSupplier' + + + + dataMarketLocationTransmissionSystemOperators: + type: array + items: + $ref: '#/components/schemas/marketLocationTransmissionSystemOperator' + + + dataMeterLocation: + required: + - dataMeterLocationMeasuringPointOperators + - meloId + - meterNumber + type: object + properties: + meloId: + $ref: '#/components/schemas/meloId' + meterNumber: + $ref: '#/components/schemas/meterNumber' + dataMeterLocationMeasuringPointOperators: + $ref: '#/components/schemas/dataMeterLocationMeasuringPointOperators' + dataMeterLocations: + type: array + items: + $ref: '#/components/schemas/dataMeterLocation' + dataMeterLocationMeasuringPointOperators: + type: array + items: + $ref: '#/components/schemas/meterLocationMeasuringPointOperator' + dataNetworkLocation: + required: + - dataNetworkLocationMeasuringPointOperators + - neloId + type: object + properties: + neloId: + $ref: '#/components/schemas/neloId' + dataNetworkLocationMeasuringPointOperators: + $ref: '#/components/schemas/dataNetworkLocationMeasuringPointOperators' + dataNetworkLocations: + type: array + items: + $ref: '#/components/schemas/dataNetworkLocation' + dataNetworkLocationMeasuringPointOperators: + type: array + items: + $ref: '#/components/schemas/networkLocationMeasuringPointOperator' + dataTechnicalResource: + required: + - trId + type: object + properties: + trId: + $ref: '#/components/schemas/trId' + dataTechnicalResources: + type: array + items: + $ref: '#/components/schemas/dataTechnicalResource' + dataTranche: + required: + - dataTrancheSuppliers + - proportion + - tranchenId + type: object + properties: + tranchenId: + $ref: '#/components/schemas/tranchenId' + proportion: + $ref: '#/components/schemas/proportion' + percent: + $ref: '#/components/schemas/percent' + dataTrancheSuppliers: + $ref: '#/components/schemas/dataTrancheSuppliers' + dataTranches: + type: array + items: + $ref: '#/components/schemas/dataTranche' + dataTrancheSuppliers: + type: array + items: + $ref: '#/components/schemas/trancheSupplier' + + districtName: + type: string + description: Angabe des Gemarkungsnamens (des Flurstücks) der Marktlokationsadresse + + + + east: + type: string + description: Angabe des UTM Ostwert nach WGS84. Umgerechneter Wert muss Min/Max für Länge/Breite entsprechen + + easting: + type: string + description: Gauß-Krüger Rechtswert. Umgerechneter Wert muss Min/Max für Länge/Breite entsprechen. + + energyDirection: + type: string + description: Energieflussrichtung der Marktlokation + example: consumption + enum: + - consumption + - production + executionTimeFrom: + pattern: "20(\\d{2}(\\-(0[13578]|1[02])\\-(0[1-9]|[12]\\d|3[01])|\\-02\\-(0[1-9]|1\\d|2[0-8])|\\-(0[469]|11)\\-(0[1-9]|[12]\\d|30))|([02468][048]|[13579][26])\\-02\\-(29))T([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\dZ" + type: string + description: Beginnzeitpunkt, Zeitpunkt zu dem die zugeordneten Marktpartner oder Lokationen zugeordnet werden. Dieser Zeitpunkt muss ein Tagesbeginn 00:00 Uhr gesetzlicher deutscher Zeit sein. + example: 2023-08-01T22:00:00Z + executionTimeUntil: + pattern: "20(\\d{2}(\\-(0[13578]|1[02])\\-(0[1-9]|[12]\\d|3[01])|\\-02\\-(0[1-9]|1\\d|2[0-8])|\\-(0[469]|11)\\-(0[1-9]|[12]\\d|30))|([02468][048]|[13579][26])\\-02\\-(29))T([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\dZ" + type: string + description: Endezeitpunkt, Zeitpunkt bis zu dem die zugeordneten Marktpartner oder Lokationen zugeordnet werden. Dieser Zeitpunkt muss ein Tagesbeginn 00:00 Uhr gesetzlicher deutscher Zeit sein. + example: 2023-08-01T22:00:00Z + reason: + type: string + description: Angabe der weiteren Erläuterung zum in responseCode genannten Antwortgrundes sofern dies gemäß dem in decisionTree genannten Entscheidungsbaums zulässig ist. + example: Ich bin ein Freitext. + firstnames: + type: string + description: Angabe des Vornamen des Kunden + example: Michael + + geographicCoordinates: + type: object + properties: + latitude: + $ref: '#/components/schemas/latitude' + longitude: + $ref: '#/components/schemas/longitude' + east: + $ref: '#/components/schemas/east' + north: + $ref: '#/components/schemas/north' + zone: + $ref: '#/components/schemas/zone' + northing: + $ref: '#/components/schemas/northing' + easting: + $ref: '#/components/schemas/easting' + + + + houseNumber: + type: integer + description: Angabe der Hausnummer der Marktlokationsadresse + example: 32 + houseNumberAddition: + type: string + description: Angabe der Hausnummernergänzung der Marktlokationsadresse + example: F + identificationDateTime: + pattern: "20(\\d{2}(\\-(0[13578]|1[02])\\-(0[1-9]|[12]\\d|3[01])|\\-02\\-(0[1-9]|1\\d|2[0-8])|\\-(0[469]|11)\\-(0[1-9]|[12]\\d|30))|([02468][048]|[13579][26])\\-02\\-(29))T([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\dZ" + type: string + description: Zeitpunkt zu dem die Identifikation stattfinden soll. Dieser Zeitpunkt muss ein Tagesbeginn 00:00 Uhr gesetzlicher deutscher Zeit sein. + example: 2023-08-02T22:00:00Z + identificationParameter: + required: + - identificationDateTime + - energyDirection + - identifiactionParameterAddress + type: object + properties: + identificationDateTime: + $ref: '#/components/schemas/identificationDateTime' + energyDirection: + $ref: '#/components/schemas/energyDirection' + identificationParameterId: + $ref: '#/components/schemas/identificationParameter_identificationParameterId' + identificationParameterAddress: + $ref: '#/components/schemas/identificationParameter_identificationParameterAddress' + initialTransactionId: + type: string + description: Zur Angabe des Idempodenzschlüssel im Falle eines Retry. + format: UUID RFC4122 + example: f81d4fae-7dec-11d0-a765-00a0c91e6bf6 + + lotNumber: + type: string + description: Angabe der Flurnummer (des Flurstücks) der Marktlokationsadresse + + landParcel: + required: + - districtName + - lotNumber + - subLotNumber + type: object + properties: + districtName: + $ref: '#/components/schemas/districtName' + lotNumber: + $ref: '#/components/schemas/lotNumber' + subLotNumber: + $ref: '#/components/schemas/subLotNumber' + landParcels: + type: array + items: + $ref: '#/components/schemas/landParcel' + + latitude: + type: string + description: Angabe der Breite (Breitengrad) nach WGS84. 46 <= X <= 59. °N + + longitude: + type: string + description: Angabe der Länge (Längengrad) nach WGS84. 4 <= X <= 18. °O + + + maloId: + pattern: "\\d{11}" + type: string + description: Identifiziert die Marktlokation mittels einer eindeutigen ID + example: "57685676748" + marketLocationDateTime: + required: + - executionTimeFrom + - maloId + type: object + properties: + maloId: + $ref: '#/components/schemas/maloId' + executionTimeFrom: + $ref: '#/components/schemas/executionTimeFrom' + executionTimeUntil: + $ref: '#/components/schemas/executionTimeUntil' + marketLocationDateTimes: + type: array + items: + $ref: '#/components/schemas/marketLocationDateTime' + + marketLocationMeasuringPointOperator: + required: + - executionTimeFrom + - marketPartnerId + type: object + properties: + marketPartnerId: + $ref: '#/components/schemas/marketPartnerId' + executionTimeFrom: + $ref: '#/components/schemas/executionTimeFrom' + executionTimeUntil: + $ref: '#/components/schemas/executionTimeUntil' + + + + marketLocationNetworkOperator: + required: + - executionTimeFrom + - marketPartnerId + type: object + properties: + marketPartnerId: + $ref: '#/components/schemas/marketPartnerId' + executionTimeFrom: + $ref: '#/components/schemas/executionTimeFrom' + executionTimeUntil: + $ref: '#/components/schemas/executionTimeUntil' + + marketLocationProperties: + required: + - executionTimeFrom + - marketLocationProperty + type: object + properties: + marketLocationProperty: + $ref: '#/components/schemas/marketLocationProperty' + executionTimeFrom: + $ref: '#/components/schemas/executionTimeFrom' + executionTimeUntil: + $ref: '#/components/schemas/executionTimeUntil' + + marketLocationProperty: + type: string + description: Eigenschaft der Marktlokation. nonActive=ruhende Marktlokation, customerFacility=Marktlokation ist eine Kundenanlage, standard=alle "Standard" Marktlokationen die nicht unter die Eigenschaft "ruhende Marktlokation" und "Kundenanlage" fallen. + example: measured # this is probably a copy-paste error + enum: + - customerFacility + - nonActive # was 'nonActice' before + - standard + + + + marketLocationSupplier: + required: + - executionTimeFrom + - marketPartnerId + type: object + properties: + marketPartnerId: + $ref: '#/components/schemas/marketPartnerId' + executionTimeFrom: + $ref: '#/components/schemas/executionTimeFrom' + executionTimeUntil: + $ref: '#/components/schemas/executionTimeUntil' + + + + + marketLocationTransmissionSystemOperator: + required: + - executionTimeFrom + - marketPartnerId + type: object + properties: + marketPartnerId: + $ref: '#/components/schemas/marketPartnerId' + executionTimeFrom: + $ref: '#/components/schemas/executionTimeFrom' + executionTimeUntil: + $ref: '#/components/schemas/executionTimeUntil' + + + + + + marketPartnerId: + pattern: "\\d{13}" + type: integer + description: Identifiziert den Marktpartner (Marktpartner-ID) + example: 9900987654321 + + measurementTechnologyClassification: + type: string + description: Angabe der messtechnischen Einordung der Marktlokation + example: intelligentMeasuringSystem + enum: + - intelligentMeasuringSystem + - conventionalMeasuringSystem + - noMeasurement + meloId: + pattern: "DE\\d{11}[A-Z,\\d]{20}" + type: string + description: Identifiziert die Messlokation mittels einer eindeutigen ID + example: DE00014545768S0000000000000003054 + meloIds: + type: array + items: + $ref: '#/components/schemas/meloId' + + meterLocationMeasuringPointOperator: + required: + - executionTimeFrom + - marketPartnerId + type: object + properties: + marketPartnerId: + $ref: '#/components/schemas/marketPartnerId' + executionTimeFrom: + $ref: '#/components/schemas/executionTimeFrom' + executionTimeUntil: + $ref: '#/components/schemas/executionTimeUntil' + + + + + + + + + meterNumber: + type: string + description: Identifiziert das Gerät der Messlokation mittels Gerätenummer + example: 1SM-8465929523 + + meterNumbers: + type: array + items: + $ref: '#/components/schemas/meterNumber' + + name: + type: object + properties: + surnames: + $ref: '#/components/schemas/surnames' + firstnames: + $ref: '#/components/schemas/firstnames' + title: + $ref: '#/components/schemas/title' + company: + $ref: '#/components/schemas/company' + + + + + + neloId: + pattern: "E[A-Z\\d]{9}\\d" + type: string + description: Identifiziert die Netzlokation mittels einer eindeutigen ID + example: E1234848431 + networkLocationMeasuringPointOperator: + required: + - executionTimeFrom + - marketPartnerId + type: object + properties: + marketPartnerId: + $ref: '#/components/schemas/marketPartnerId' + executionTimeFrom: + $ref: '#/components/schemas/executionTimeFrom' + executionTimeUntil: + $ref: '#/components/schemas/executionTimeUntil' + + north: + type: string + description: Angabe des UTM Nordwert nach WGS84. Umgerechneter Wert muss Min/Max für Länge/Breite entsprechen + + northing: + type: string + description: Gauß-Krüger Hochwert. Umgerechneter Wert muss Min/Max für Länge/Breite entsprechen. + + + + + optionalChangeForecastBasis: + type: string + description: Wahlrecht zur Änderung der Prognosegrundlage + example: possible + enum: + - possible + - notPossible + percent: + pattern: "\\d{1,2}(\\.[\\d]{1,3})?" + type: number + description: "Prozentualer Wert, wenn proportion mit percent vorhanden ist." + example: "75.912" + + subLotNumber: + type: string + description: Angabe der Flurstücksnummer (des Flurstücks) der Marktlokationsadresse + + + proportion: + type: string + description: Gibt die Bildungslogik der Tranche an + example: percent + enum: + - bilateralAgreement + - percent + responseCode: + pattern: "A[A-Z\\d]{2}" + type: string + description: Angabe des Antwortgrundes des in decisionTree genannten Entscheidungsbaums. + example: A10 + referenceId: + type: string + description: Externe Vorgangsreferenz zur eindeutigen Identifikation des ursprünglichen Vorgangs + format: UUID RFC4122 + example: f81d4fae-7dec-11d0-a765-00a0c91e6bf6 + resultNegative: + description: Hier wird bei einer neagtiven Antwort der Entscheidungsbaum und der entsprechende Antwortcode aus dem Entscheidungsbaumdiagramm angegeben. Wenn sich die Marktlokation nicht mehr im Netzgebiet befindet ist die MP-ID des NB als "networkOperator" anzugeben, an den die Marktlokation abgegeben wurde. + required: + - responseCode + - decisionTree + type: object + properties: + decisionTree: + $ref: '#/components/schemas/decisionTree' + responseCode: + $ref: '#/components/schemas/responseCode' + reason: + $ref: '#/components/schemas/reason' + networkOperator: + $ref: '#/components/schemas/marketPartnerId' + + resultPositive: + description: Hier wird bei einer positiven Antwort die identifizierte Marktlokation und alle dazugehörigen Parameter angegeben. Hierzu gehören die Marktpartner an der jeweiligen Lokation, die Kunden- und Adressdaten, die Lokationen, welche zu dieser Marktlokation gehören, etc. Die Informationen sind ab dem Anfragezeitpunkt "identificationDateTime" aus der Anfrage bis in die Zukunft mit allen beim NB zum Bearbeitungszeitpunkt vorliegenden Informationen ab dem Anfragezeitpunkt zu übermitteln.Hierzu gehören auch Informationen, welche nicht als "required" gekennzeichnet sind. So müssen alle vorliegenden Informationen zur Messlokation übermittelt werden. In der Schnittstellenbeschreibung sind diese jedoch nicht als "required" gekennzeichnet, da es sich auch um eine pauschale Marktlokation handeln kann, in welcher es keine Messlokation gibt. Dies gilt ebenfalls für alle weiteren Informationen die nicht als "required" gekennzeichnet sind. Wie der Lieferant "Supplier" an der Marktlokation. Da eine Marktlokation auch tranchiert sein kann, wechselt zu diesem Zeitpunkt der LF von der Zuordnung der Marktlokation zur Tranche, weshalb dieser in diesem Szenario als nicht "required" definiert wurde. + required: + - dataMarketLocation + type: object + properties: + dataMarketLocation: + $ref: '#/components/schemas/dataMarketLocation' + dataTranches: + $ref: '#/components/schemas/dataTranches' + dataMeterLocations: + $ref: '#/components/schemas/dataMeterLocations' + dataTechnicalResources: + $ref: '#/components/schemas/dataTechnicalResources' + dataControllableResources: + $ref: '#/components/schemas/dataControllableResources' + dataNetworkLocations: + $ref: '#/components/schemas/dataNetworkLocations' + + + + + + + + + srId: + pattern: "C[A-Z\\d]{9}\\d" + type: string + description: Identifiziert die Steuerbare Ressource mittels einer eindeutigen ID + example: C1234848431 + srMarketPartner: + required: + - executionTimeFrom + - marketPartnerId + - marketPartnerTypeSr + type: object + properties: + marketPartnerId: + $ref: '#/components/schemas/marketPartnerId' + executionTimeFrom: + $ref: '#/components/schemas/executionTimeFrom' + executionTimeUntil: + $ref: '#/components/schemas/executionTimeUntil' + decisionTree: + pattern: "E_\\d{4}" + type: string + description: Angabe des Entscheidungsbaums aus dem edi@energy Dokument "Entscheidungsbaum-Diagramme und Codelisten für die Antwortnachrichten" als Grundlage für den negativen Antwortgrund + example: E_0594 + street: + type: string + description: Angabe der Straße der Marktlokationsadresse + example: Reinhardtstraße + surnames: + type: string + description: Angabe des Namen des Kunden + example: Becker + + + title: + type: string + description: Angabe des Titels der natürlichen Person + example: Prof.Dr. + + + + + + + tranchenId: + pattern: "\\d{11}" + type: string + description: Identifiziert die Tranche mittels einer eindeutigen ID + example: "57685676742" + + + tranchenIds: + type: array + items: + $ref: '#/components/schemas/tranchenId' + + + + + + trancheSupplier: + required: + - executionTimeFrom + - marketPartnerId + type: object + properties: + marketPartnerId: + $ref: '#/components/schemas/marketPartnerId' + executionTimeFrom: + $ref: '#/components/schemas/executionTimeFrom' + executionTimeUntil: + $ref: '#/components/schemas/executionTimeUntil' + transactionId: + type: string + description: Externe Transaktionsnummer zur eindeutigen Identifikation des Vorgangs des sendenden Marktpartners + format: UUID RFC4122 + example: f81d4fae-7dec-11d0-a765-00a0c91e6bf6 + trId: + pattern: "D[A-Z\\d]{9}\\d" + type: string + description: Identifiziert die Technische Ressource mittels einer eindeutigen ID + example: D1234848431 + zipCode: + type: string + description: Angabe der Postleitzahl der Marktlokationsadresse + example: 12117 + + + zone: + type: string + description: Angabe des UTM Zonenwertes nach WGS84. + enum: + - UTMZone31 + - UTMZone32 + - UTMZone33 + + + identificationParameter_identificationParameterId: + type: object + properties: + maloId: + $ref: '#/components/schemas/maloId' + tranchenIds: + $ref: '#/components/schemas/tranchenIds' + meloIds: + $ref: '#/components/schemas/meloIds' + meterNumbers: + $ref: '#/components/schemas/meterNumbers' + customerNumber: + $ref: '#/components/schemas/customerNumber' + identificationParameter_identificationParameterAddress: + type: object + properties: + name: + $ref: '#/components/schemas/name' + address: + $ref: '#/components/schemas/address' + landParcels: + $ref: '#/components/schemas/landParcels' + geographicCoordinates: + $ref: '#/components/schemas/geographicCoordinates' diff --git a/pyproject.toml b/pyproject.toml index 7a3ba73..2534cb1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [project] -name = "your-favourite-package-name" -description = "Description of your package" +name = "maloident" +description = "Model classes for the Marktlokation ID Identification API by EDI@Energy (manually fixed)" license = { text = "MIT" } requires-python = ">=3.11" -authors = [{ name = "your name", email = "your@email.address" }] -keywords = ["your", "important", "keywords"] +authors = [{ name = "your name", email = "info+github@hochfrequenz.de" }] +keywords = ["MaLo Ident", "Marktlokation", "BDEW", "MaLo"] classifiers = [ "Development Status :: 4 - Beta", "Environment :: Console", @@ -16,7 +16,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", ] -dependencies = [] # add all the dependencies here +dependencies = ["pydantic>=2"] # add all the dependencies here dynamic = ["readme", "version"] [project.optional-dependencies] @@ -27,7 +27,7 @@ linting = [ "pylint==3.3.1" ] type_check = [ - "mypy==1.11.2" + "mypy[pydantic]==1.11.2" ] spell_check = [ "codespell==2.3.0" @@ -43,14 +43,17 @@ packaging = [ "build==1.2.2", "twine==5.1.1" ] +codegen = [ + "datamodel-code-generator==0.26.0" +] dev = [ "pip-tools" ] [project.urls] -Changelog = "https://github.com/Hochfrequenz/python_template_repository/releases" -Homepage = "https://github.com/Hochfrequenz/python_template_repository" +Changelog = "https://github.com/Hochfrequenz/malo-ident-python-models/releases" +Homepage = "https://github.com/Hochfrequenz/malo-ident-python-models" [tool.black] line-length = 120 @@ -69,6 +72,11 @@ truethy-bool = true [tool.mypy] disable_error_code = [] +[[tool.mypy.overrides]] +module = "maloident._autogenerated" +ignore_errors = true + + [build-system] requires = ["hatchling>=1.8.0", "hatch-vcs", "hatch-fancy-pypi-readme"] build-backend = "hatchling.build" @@ -81,7 +89,7 @@ fragments = [{ path = "README.md" }] source = "vcs" [tool.hatch.build.hooks.vcs] -version-file = "src/_your_package_version.py" +version-file = "src/_maloident_version.py" template = ''' version = "{version}" ''' diff --git a/requirements.txt b/requirements.txt index 79ca470..4bbe890 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,16 @@ # -# This file is autogenerated by pip-compile with Python 3.11 +# This file is _autogenerated by pip-compile with Python 3.12 # by the following command: # # pip-compile pyproject.toml # +annotated-types==0.7.0 + # via pydantic +pydantic==2.9.2 + # via your-favourite-package-name (pyproject.toml) +pydantic-core==2.23.4 + # via pydantic +typing-extensions==4.12.2 + # via + # pydantic + # pydantic-core diff --git a/src/maloident/__init__.py b/src/maloident/__init__.py new file mode 100644 index 0000000..e8be780 --- /dev/null +++ b/src/maloident/__init__.py @@ -0,0 +1,7 @@ +""" +The package maloident contains model classes that are used in the MaLo Ident API by EDI@Energy. +The raw data are published on Swagger Hub: https://app.swaggerhub.com/apis/edi-energy/MaLoIdent_2024-07-03/v1.0.0#/ +""" + +# The classes in _autogenerated.py in this sub-package are all autogenerated with +# tox -e codegen diff --git a/src/maloident/_autogenerated.py b/src/maloident/_autogenerated.py new file mode 100644 index 0000000..a05b70a --- /dev/null +++ b/src/maloident/_autogenerated.py @@ -0,0 +1,548 @@ +# generated by datamodel-codegen: +# filename: openapi.yml +# timestamp: 2024-09-25T17:15:02+00:00 + +from __future__ import annotations + +from enum import Enum +from typing import List, Optional + +from pydantic import BaseModel, Field, RootModel, constr + + +class City(RootModel[str]): + root: str = Field(..., description="Angabe des Ortes der Marktlokationsadresse", examples=["Berlin"]) + + +class Company(RootModel[str]): + root: str = Field(..., description="Angabe des Firmennamen", examples=["BDEW & Co. KG"]) + + +class CountryCode(RootModel[constr(pattern=r"[A-Z]{2}")]): + root: constr(pattern=r"[A-Z]{2}") = Field( + ..., description="Angabe des Ländercodes nach ISO-3166-1 Alpha-2", examples=["DE"] + ) + + +class CustomerNumber(RootModel[str]): + root: str = Field( + ..., + description="Zur Angabe der Kundennummer des Kunden beim bisherigen Lieferanten (LFA)", + examples=["V567345345"], + ) + + +class CreationDateTime( + RootModel[ + constr( + pattern=r"20(\d{2}(\-(0[13578]|1[02])\-(0[1-9]|[12]\d|3[01])|\-02\-(0[1-9]|1\d|2[0-8])|\-(0[469]|11)\-(0[1-9]|[12]\d|30))|([02468][048]|[13579][26])\-02\-(29))T([01]\d|2[0-3]):[0-5]\d:[0-5]\d(\.[\d]{1,4})?Z" + ) + ] +): + root: constr( + pattern=r"20(\d{2}(\-(0[13578]|1[02])\-(0[1-9]|[12]\d|3[01])|\-02\-(0[1-9]|1\d|2[0-8])|\-(0[469]|11)\-(0[1-9]|[12]\d|30))|([02468][048]|[13579][26])\-02\-(29))T([01]\d|2[0-3]):[0-5]\d:[0-5]\d(\.[\d]{1,4})?Z" + ) = Field(..., description="Zeitpunkt an dem der Aufruf erstellt wurde", examples=["2023-08-01T12:30:00.1704Z"]) + + +class DistrictName(RootModel[str]): + root: str = Field(..., description="Angabe des Gemarkungsnamens (des Flurstücks) der Marktlokationsadresse") + + +class East(RootModel[str]): + root: str = Field( + ..., + description="Angabe des UTM Ostwert nach WGS84. Umgerechneter Wert muss Min/Max für Länge/Breite entsprechen", + ) + + +class Easting(RootModel[str]): + root: str = Field( + ..., description="Gauß-Krüger Rechtswert. Umgerechneter Wert muss Min/Max für Länge/Breite entsprechen." + ) + + +class EnergyDirection(Enum): + consumption = "consumption" + production = "production" + + +class ExecutionTimeFrom( + RootModel[ + constr( + pattern=r"20(\d{2}(\-(0[13578]|1[02])\-(0[1-9]|[12]\d|3[01])|\-02\-(0[1-9]|1\d|2[0-8])|\-(0[469]|11)\-(0[1-9]|[12]\d|30))|([02468][048]|[13579][26])\-02\-(29))T([01]\d|2[0-3]):[0-5]\d:[0-5]\dZ" + ) + ] +): + root: constr( + pattern=r"20(\d{2}(\-(0[13578]|1[02])\-(0[1-9]|[12]\d|3[01])|\-02\-(0[1-9]|1\d|2[0-8])|\-(0[469]|11)\-(0[1-9]|[12]\d|30))|([02468][048]|[13579][26])\-02\-(29))T([01]\d|2[0-3]):[0-5]\d:[0-5]\dZ" + ) = Field( + ..., + description="Beginnzeitpunkt, Zeitpunkt zu dem die zugeordneten Marktpartner oder Lokationen zugeordnet werden. Dieser Zeitpunkt muss ein Tagesbeginn 00:00 Uhr gesetzlicher deutscher Zeit sein.", + examples=["2023-08-01T22:00:00Z"], + ) + + +class ExecutionTimeUntil( + RootModel[ + constr( + pattern=r"20(\d{2}(\-(0[13578]|1[02])\-(0[1-9]|[12]\d|3[01])|\-02\-(0[1-9]|1\d|2[0-8])|\-(0[469]|11)\-(0[1-9]|[12]\d|30))|([02468][048]|[13579][26])\-02\-(29))T([01]\d|2[0-3]):[0-5]\d:[0-5]\dZ" + ) + ] +): + root: constr( + pattern=r"20(\d{2}(\-(0[13578]|1[02])\-(0[1-9]|[12]\d|3[01])|\-02\-(0[1-9]|1\d|2[0-8])|\-(0[469]|11)\-(0[1-9]|[12]\d|30))|([02468][048]|[13579][26])\-02\-(29))T([01]\d|2[0-3]):[0-5]\d:[0-5]\dZ" + ) = Field( + ..., + description="Endezeitpunkt, Zeitpunkt bis zu dem die zugeordneten Marktpartner oder Lokationen zugeordnet werden. Dieser Zeitpunkt muss ein Tagesbeginn 00:00 Uhr gesetzlicher deutscher Zeit sein.", + examples=["2023-08-01T22:00:00Z"], + ) + + +class Reason(RootModel[str]): + root: str = Field( + ..., + description="Angabe der weiteren Erläuterung zum in responseCode genannten Antwortgrundes sofern dies gemäß dem in decisionTree genannten Entscheidungsbaums zulässig ist.", + examples=["Ich bin ein Freitext."], + ) + + +class Firstnames(RootModel[str]): + root: str = Field(..., description="Angabe des Vornamen des Kunden", examples=["Michael"]) + + +class HouseNumber(RootModel[int]): + root: int = Field(..., description="Angabe der Hausnummer der Marktlokationsadresse", examples=[32]) + + +class HouseNumberAddition(RootModel[str]): + root: str = Field(..., description="Angabe der Hausnummernergänzung der Marktlokationsadresse", examples=["F"]) + + +class IdentificationDateTime( + RootModel[ + constr( + pattern=r"20(\d{2}(\-(0[13578]|1[02])\-(0[1-9]|[12]\d|3[01])|\-02\-(0[1-9]|1\d|2[0-8])|\-(0[469]|11)\-(0[1-9]|[12]\d|30))|([02468][048]|[13579][26])\-02\-(29))T([01]\d|2[0-3]):[0-5]\d:[0-5]\dZ" + ) + ] +): + root: constr( + pattern=r"20(\d{2}(\-(0[13578]|1[02])\-(0[1-9]|[12]\d|3[01])|\-02\-(0[1-9]|1\d|2[0-8])|\-(0[469]|11)\-(0[1-9]|[12]\d|30))|([02468][048]|[13579][26])\-02\-(29))T([01]\d|2[0-3]):[0-5]\d:[0-5]\dZ" + ) = Field( + ..., + description="Zeitpunkt zu dem die Identifikation stattfinden soll. Dieser Zeitpunkt muss ein Tagesbeginn 00:00 Uhr gesetzlicher deutscher Zeit sein.", + examples=["2023-08-02T22:00:00Z"], + ) + + +class InitialTransactionId(RootModel[str]): + root: str = Field( + ..., + description="Zur Angabe des Idempodenzschlüssel im Falle eines Retry.", + examples=["f81d4fae-7dec-11d0-a765-00a0c91e6bf6"], + ) + + +class LotNumber(RootModel[str]): + root: str = Field(..., description="Angabe der Flurnummer (des Flurstücks) der Marktlokationsadresse") + + +class Latitude(RootModel[str]): + root: str = Field(..., description="Angabe der Breite (Breitengrad) nach WGS84. 46 <= X <= 59. °N") + + +class Longitude(RootModel[str]): + root: str = Field(..., description="Angabe der Länge (Längengrad) nach WGS84. 4 <= X <= 18. °O") + + +class MaloId(RootModel[constr(pattern=r"\d{11}")]): + root: constr(pattern=r"\d{11}") = Field( + ..., description="Identifiziert die Marktlokation mittels einer eindeutigen ID", examples=["57685676748"] + ) + + +class MarketLocationDateTime(BaseModel): + maloId: MaloId + executionTimeFrom: ExecutionTimeFrom + executionTimeUntil: Optional[ExecutionTimeUntil] = None + + +class MarketLocationDateTimes(RootModel[List[MarketLocationDateTime]]): + root: List[MarketLocationDateTime] + + +class MarketLocationProperty(Enum): + customerFacility = "customerFacility" + nonActive = "nonActive" + standard = "standard" + + +class MarketPartnerId(RootModel[int]): + root: int = Field(..., description="Identifiziert den Marktpartner (Marktpartner-ID)", examples=[9900987654321]) + + +class MeasurementTechnologyClassification(Enum): + intelligentMeasuringSystem = "intelligentMeasuringSystem" + conventionalMeasuringSystem = "conventionalMeasuringSystem" + noMeasurement = "noMeasurement" + + +class MeloId(RootModel[constr(pattern=r"DE\d{11}[A-Z,\d]{20}")]): + root: constr(pattern=r"DE\d{11}[A-Z,\d]{20}") = Field( + ..., + description="Identifiziert die Messlokation mittels einer eindeutigen ID", + examples=["DE00014545768S0000000000000003054"], + ) + + +class MeloIds(RootModel[List[MeloId]]): + root: List[MeloId] + + +class MeterLocationMeasuringPointOperator(BaseModel): + marketPartnerId: MarketPartnerId + executionTimeFrom: ExecutionTimeFrom + executionTimeUntil: Optional[ExecutionTimeUntil] = None + + +class MeterNumber(RootModel[str]): + root: str = Field( + ..., description="Identifiziert das Gerät der Messlokation mittels Gerätenummer", examples=["1SM-8465929523"] + ) + + +class MeterNumbers(RootModel[List[MeterNumber]]): + root: List[MeterNumber] + + +class NeloId(RootModel[constr(pattern=r"E[A-Z\d]{9}\d")]): + root: constr(pattern=r"E[A-Z\d]{9}\d") = Field( + ..., description="Identifiziert die Netzlokation mittels einer eindeutigen ID", examples=["E1234848431"] + ) + + +class NetworkLocationMeasuringPointOperator(BaseModel): + marketPartnerId: MarketPartnerId + executionTimeFrom: ExecutionTimeFrom + executionTimeUntil: Optional[ExecutionTimeUntil] = None + + +class North(RootModel[str]): + root: str = Field( + ..., + description="Angabe des UTM Nordwert nach WGS84. Umgerechneter Wert muss Min/Max für Länge/Breite entsprechen", + ) + + +class Northing(RootModel[str]): + root: str = Field( + ..., description="Gauß-Krüger Hochwert. Umgerechneter Wert muss Min/Max für Länge/Breite entsprechen." + ) + + +class OptionalChangeForecastBasis(Enum): + possible = "possible" + notPossible = "notPossible" + + +class Percent(RootModel[float]): + root: float = Field( + ..., description="Prozentualer Wert, wenn proportion mit percent vorhanden ist.", examples=["75.912"] + ) + + +class SubLotNumber(RootModel[str]): + root: str = Field(..., description="Angabe der Flurstücksnummer (des Flurstücks) der Marktlokationsadresse") + + +class Proportion(Enum): + bilateralAgreement = "bilateralAgreement" + percent = "percent" + + +class ResponseCode(RootModel[constr(pattern=r"A[A-Z\d]{2}")]): + root: constr(pattern=r"A[A-Z\d]{2}") = Field( + ..., description="Angabe des Antwortgrundes des in decisionTree genannten Entscheidungsbaums.", examples=["A10"] + ) + + +class ReferenceId(RootModel[str]): + root: str = Field( + ..., + description="Externe Vorgangsreferenz zur eindeutigen Identifikation des ursprünglichen Vorgangs", + examples=["f81d4fae-7dec-11d0-a765-00a0c91e6bf6"], + ) + + +class SrId(RootModel[constr(pattern=r"C[A-Z\d]{9}\d")]): + root: constr(pattern=r"C[A-Z\d]{9}\d") = Field( + ..., description="Identifiziert die Steuerbare Ressource mittels einer eindeutigen ID", examples=["C1234848431"] + ) + + +class SrMarketPartner(BaseModel): + marketPartnerId: MarketPartnerId + executionTimeFrom: ExecutionTimeFrom + executionTimeUntil: Optional[ExecutionTimeUntil] = None + + +class DecisionTree(RootModel[constr(pattern=r"E_\d{4}")]): + root: constr(pattern=r"E_\d{4}") = Field( + ..., + description='Angabe des Entscheidungsbaums aus dem edi@energy Dokument "Entscheidungsbaum-Diagramme und Codelisten für die Antwortnachrichten" als Grundlage für den negativen Antwortgrund', + examples=["E_0594"], + ) + + +class Street(RootModel[str]): + root: str = Field(..., description="Angabe der Straße der Marktlokationsadresse", examples=["Reinhardtstraße"]) + + +class Surnames(RootModel[str]): + root: str = Field(..., description="Angabe des Namen des Kunden", examples=["Becker"]) + + +class Title(RootModel[str]): + root: str = Field(..., description="Angabe des Titels der natürlichen Person", examples=["Prof.Dr."]) + + +class TranchenId(RootModel[constr(pattern=r"\d{11}")]): + root: constr(pattern=r"\d{11}") = Field( + ..., description="Identifiziert die Tranche mittels einer eindeutigen ID", examples=["57685676742"] + ) + + +class TranchenIds(RootModel[List[TranchenId]]): + root: List[TranchenId] + + +class TrancheSupplier(BaseModel): + marketPartnerId: MarketPartnerId + executionTimeFrom: ExecutionTimeFrom + executionTimeUntil: Optional[ExecutionTimeUntil] = None + + +class TransactionId(RootModel[str]): + root: str = Field( + ..., + description="Externe Transaktionsnummer zur eindeutigen Identifikation des Vorgangs des sendenden Marktpartners", + examples=["f81d4fae-7dec-11d0-a765-00a0c91e6bf6"], + ) + + +class TrId(RootModel[constr(pattern=r"D[A-Z\d]{9}\d")]): + root: constr(pattern=r"D[A-Z\d]{9}\d") = Field( + ..., description="Identifiziert die Technische Ressource mittels einer eindeutigen ID", examples=["D1234848431"] + ) + + +class ZipCode(RootModel[str]): + root: str = Field(..., description="Angabe der Postleitzahl der Marktlokationsadresse", examples=[12117]) + + +class Zone(Enum): + UTMZone31 = "UTMZone31" + UTMZone32 = "UTMZone32" + UTMZone33 = "UTMZone33" + + +class IdentificationParameterIdentificationParameterId(BaseModel): + maloId: Optional[MaloId] = None + tranchenIds: Optional[TranchenIds] = None + meloIds: Optional[MeloIds] = None + meterNumbers: Optional[MeterNumbers] = None + customerNumber: Optional[CustomerNumber] = None + + +class Address(BaseModel): + countryCode: Optional[CountryCode] = None + zipCode: Optional[ZipCode] = None + city: Optional[City] = None + street: Optional[Street] = None + houseNumber: Optional[HouseNumber] = None + houseNumberAddition: Optional[HouseNumberAddition] = None + + +class DataControllableResourceMeasuringPointOperators(RootModel[List[SrMarketPartner]]): + root: List[SrMarketPartner] + + +class DataMeterLocationMeasuringPointOperators(RootModel[List[MeterLocationMeasuringPointOperator]]): + root: List[MeterLocationMeasuringPointOperator] + + +class DataNetworkLocationMeasuringPointOperators(RootModel[List[NetworkLocationMeasuringPointOperator]]): + root: List[NetworkLocationMeasuringPointOperator] + + +class DataTechnicalResource(BaseModel): + trId: TrId + + +class DataTechnicalResources(RootModel[List[DataTechnicalResource]]): + root: List[DataTechnicalResource] + + +class DataTrancheSuppliers(RootModel[List[TrancheSupplier]]): + root: List[TrancheSupplier] + + +class GeographicCoordinates(BaseModel): + latitude: Optional[Latitude] = None + longitude: Optional[Longitude] = None + east: Optional[East] = None + north: Optional[North] = None + zone: Optional[Zone] = None + northing: Optional[Northing] = None + easting: Optional[Easting] = None + + +class LandParcel(BaseModel): + districtName: DistrictName + lotNumber: LotNumber + subLotNumber: SubLotNumber + + +class LandParcels(RootModel[List[LandParcel]]): + root: List[LandParcel] + + +class MarketLocationMeasuringPointOperator(BaseModel): + marketPartnerId: MarketPartnerId + executionTimeFrom: ExecutionTimeFrom + executionTimeUntil: Optional[ExecutionTimeUntil] = None + + +class MarketLocationNetworkOperator(BaseModel): + marketPartnerId: MarketPartnerId + executionTimeFrom: ExecutionTimeFrom + executionTimeUntil: Optional[ExecutionTimeUntil] = None + + +class MarketLocationProperties(BaseModel): + marketLocationProperty: MarketLocationProperty + executionTimeFrom: ExecutionTimeFrom + executionTimeUntil: Optional[ExecutionTimeUntil] = None + + +class MarketLocationSupplier(BaseModel): + marketPartnerId: MarketPartnerId + executionTimeFrom: ExecutionTimeFrom + executionTimeUntil: Optional[ExecutionTimeUntil] = None + + +class MarketLocationTransmissionSystemOperator(BaseModel): + marketPartnerId: MarketPartnerId + executionTimeFrom: ExecutionTimeFrom + executionTimeUntil: Optional[ExecutionTimeUntil] = None + + +class Name(BaseModel): + surnames: Optional[Surnames] = None + firstnames: Optional[Firstnames] = None + title: Optional[Title] = None + company: Optional[Company] = None + + +class ResultNegative(BaseModel): + decisionTree: DecisionTree + responseCode: ResponseCode + reason: Optional[Reason] = None + networkOperator: Optional[MarketPartnerId] = None + + +class IdentificationParameterIdentificationParameterAddress(BaseModel): + name: Optional[Name] = None + address: Optional[Address] = None + landParcels: Optional[LandParcels] = None + geographicCoordinates: Optional[GeographicCoordinates] = None + + +class DataControllableResource(BaseModel): + srId: SrId + dataControllableResourceMeasuringPointOperators: Optional[DataControllableResourceMeasuringPointOperators] = None + + +class DataControllableResources(RootModel[List[DataControllableResource]]): + root: List[DataControllableResource] + + +class DataMarketLocationMeasuringPointOperators(RootModel[List[MarketLocationMeasuringPointOperator]]): + root: List[MarketLocationMeasuringPointOperator] + + +class DataMarketLocationProperties(RootModel[List[MarketLocationProperties]]): + root: List[MarketLocationProperties] + + +class DataMarketLocationNetworkOperators(RootModel[List[MarketLocationNetworkOperator]]): + root: List[MarketLocationNetworkOperator] + + +class DataMarketLocationSuppliers(RootModel[List[MarketLocationSupplier]]): + root: List[MarketLocationSupplier] + + +class DataMarketLocationTransmissionSystemOperators(RootModel[List[MarketLocationTransmissionSystemOperator]]): + root: List[MarketLocationTransmissionSystemOperator] + + +class DataMeterLocation(BaseModel): + meloId: MeloId + meterNumber: MeterNumber + dataMeterLocationMeasuringPointOperators: DataMeterLocationMeasuringPointOperators + + +class DataMeterLocations(RootModel[List[DataMeterLocation]]): + root: List[DataMeterLocation] + + +class DataNetworkLocation(BaseModel): + neloId: NeloId + dataNetworkLocationMeasuringPointOperators: DataNetworkLocationMeasuringPointOperators + + +class DataNetworkLocations(RootModel[List[DataNetworkLocation]]): + root: List[DataNetworkLocation] + + +class DataTranche(BaseModel): + tranchenId: TranchenId + proportion: Proportion + percent: Optional[Percent] = None + dataTrancheSuppliers: DataTrancheSuppliers + + +class DataTranches(RootModel[List[DataTranche]]): + root: List[DataTranche] + + +class IdentificationParameter(BaseModel): + identificationDateTime: IdentificationDateTime + energyDirection: EnergyDirection + identificationParameterId: Optional[IdentificationParameterIdentificationParameterId] = None + identificationParameterAddress: Optional[IdentificationParameterIdentificationParameterAddress] = None + + +class DataMarketLocation(BaseModel): + maloId: MaloId + energyDirection: EnergyDirection + measurementTechnologyClassification: MeasurementTechnologyClassification + optionalChangeForecastBasis: OptionalChangeForecastBasis + dataMarketLocationProperties: DataMarketLocationProperties + dataMarketLocationNetworkOperators: DataMarketLocationNetworkOperators + dataMarketLocationMeasuringPointOperators: Optional[DataMarketLocationMeasuringPointOperators] = None + dataMarketLocationTransmissionSystemOperators: DataMarketLocationTransmissionSystemOperators + dataMarketLocationSuppliers: Optional[DataMarketLocationSuppliers] = None + dataMarketLocationName: Optional[Name] = None + dataMarketLocationAddress: Optional[Address] = None + dataMarketLocationLandParcels: Optional[LandParcels] = None + dataMarketLocationGeographicCoordinates: Optional[GeographicCoordinates] = None + + +class ResultPositive(BaseModel): + dataMarketLocation: DataMarketLocation + dataTranches: Optional[DataTranches] = None + dataMeterLocations: Optional[DataMeterLocations] = None + dataTechnicalResources: Optional[DataTechnicalResources] = None + dataControllableResources: Optional[DataControllableResources] = None + dataNetworkLocations: Optional[DataNetworkLocations] = None diff --git a/src/maloident/models.py b/src/maloident/models.py new file mode 100644 index 0000000..a43696c --- /dev/null +++ b/src/maloident/models.py @@ -0,0 +1,191 @@ +""" +model classes for EDI@Energy API-Webdienste zur Ermittlung der MaLo-ID der Marktlokation +""" + +# In this module we just re-export the _autogenerated classes, but we might - in the future - fix them or provide better +# suited models, e.g. by using proper datetime types instead of regex-constrained strings. +# As of now, any fixes to the broken openapi.yml happen in the yaml itself, i.e. BEFORE the code generation. + +from ._autogenerated import ( + Address, + City, + Company, + CountryCode, + CreationDateTime, + CustomerNumber, + DataControllableResource, + DataControllableResourceMeasuringPointOperators, + DataControllableResources, + DataMarketLocation, + DataMarketLocationMeasuringPointOperators, + DataMarketLocationNetworkOperators, + DataMarketLocationProperties, + DataMarketLocationSuppliers, + DataMarketLocationTransmissionSystemOperators, + DataMeterLocation, + DataMeterLocationMeasuringPointOperators, + DataMeterLocations, + DataNetworkLocation, + DataNetworkLocationMeasuringPointOperators, + DataNetworkLocations, + DataTechnicalResource, + DataTechnicalResources, + DataTranche, + DataTranches, + DataTrancheSuppliers, + DecisionTree, + DistrictName, + East, + Easting, + EnergyDirection, + ExecutionTimeFrom, + ExecutionTimeUntil, + Firstnames, + GeographicCoordinates, + HouseNumber, + HouseNumberAddition, + IdentificationDateTime, + IdentificationParameter, + IdentificationParameterIdentificationParameterAddress, + IdentificationParameterIdentificationParameterId, + InitialTransactionId, + LandParcel, + LandParcels, + Latitude, + Longitude, + LotNumber, + MaloId, + MarketLocationDateTime, + MarketLocationDateTimes, + MarketLocationMeasuringPointOperator, + MarketLocationNetworkOperator, + MarketLocationProperties, + MarketLocationProperty, + MarketLocationSupplier, + MarketLocationTransmissionSystemOperator, + MarketPartnerId, + MeasurementTechnologyClassification, + MeloId, + MeloIds, + MeterLocationMeasuringPointOperator, + MeterNumber, + MeterNumbers, + Name, + NeloId, + NetworkLocationMeasuringPointOperator, + North, + Northing, + OptionalChangeForecastBasis, + Percent, + Proportion, + Reason, + ReferenceId, + ResponseCode, + ResultNegative, + ResultPositive, + SrId, + SrMarketPartner, + Street, + SubLotNumber, + Surnames, + Title, + TranchenId, + TranchenIds, + TrancheSupplier, + TransactionId, + TrId, + ZipCode, + Zone, +) + +__all__ = [ + "Address", + "City", + "Company", + "CountryCode", + "CreationDateTime", + "CustomerNumber", + "DataControllableResource", + "DataControllableResourceMeasuringPointOperators", + "DataControllableResources", + "DataMarketLocation", + "DataMarketLocationMeasuringPointOperators", + "DataMarketLocationNetworkOperators", + "DataMarketLocationProperties", + "DataMarketLocationSuppliers", + "DataMarketLocationTransmissionSystemOperators", + "DataMeterLocation", + "DataMeterLocationMeasuringPointOperators", + "DataMeterLocations", + "DataNetworkLocation", + "DataNetworkLocationMeasuringPointOperators", + "DataNetworkLocations", + "DataTechnicalResource", + "DataTechnicalResources", + "DataTranche", + "DataTrancheSuppliers", + "DataTranches", + "DecisionTree", + "DistrictName", + "East", + "Easting", + "EnergyDirection", + "ExecutionTimeFrom", + "ExecutionTimeUntil", + "Firstnames", + "GeographicCoordinates", + "HouseNumber", + "HouseNumberAddition", + "IdentificationDateTime", + "IdentificationParameter", + "IdentificationParameterIdentificationParameterAddress", + "IdentificationParameterIdentificationParameterId", + "InitialTransactionId", + "LandParcel", + "LandParcels", + "Latitude", + "Longitude", + "LotNumber", + "MaloId", + "MarketLocationDateTime", + "MarketLocationDateTimes", + "MarketLocationMeasuringPointOperator", + "MarketLocationNetworkOperator", + "MarketLocationProperties", + "MarketLocationProperty", + "MarketLocationSupplier", + "MarketLocationTransmissionSystemOperator", + "MarketPartnerId", + "MeasurementTechnologyClassification", + "MeloId", + "MeloIds", + "MeterLocationMeasuringPointOperator", + "MeterNumber", + "MeterNumbers", + "Name", + "NeloId", + "NetworkLocationMeasuringPointOperator", + "North", + "Northing", + "OptionalChangeForecastBasis", + "Percent", + "Proportion", + "Reason", + "ReferenceId", + "ResponseCode", + "ResultNegative", + "ResultPositive", + "SrId", + "SrMarketPartner", + "Street", + "SubLotNumber", + "Surnames", + "Title", + "TrId", + "TrancheSupplier", + "TranchenId", + "TranchenIds", + "TransactionId", + "ZipCode", + "Zone", +] diff --git a/src/mypackage/py.typed b/src/maloident/py.typed similarity index 100% rename from src/mypackage/py.typed rename to src/maloident/py.typed diff --git a/src/mypackage/__init__.py b/src/mypackage/__init__.py deleted file mode 100644 index c8dd36b..0000000 --- a/src/mypackage/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -src contains all your business logic -""" diff --git a/src/mypackage/mymodule.py b/src/mypackage/mymodule.py deleted file mode 100644 index 7ab91b5..0000000 --- a/src/mypackage/mymodule.py +++ /dev/null @@ -1,23 +0,0 @@ -""" -This a docstring for the module. -""" - - -class MyClass: # pylint: disable=too-few-public-methods - """ - This is a docstring for the class. - """ - - def __init__(self) -> None: - """ - Initialize for the sake of initializing - """ - self.my_instance_var: str = "abc" - - def do_something(self) -> str: - """ - Actually does nothing. - :return: the value of an instance variable - """ - # this is a super long line with: 100 < line length <= 120 to demonstrate the purpose of pyproject.toml - return self.my_instance_var diff --git a/tox.ini b/tox.ini index 3672f01..6d84920 100644 --- a/tox.ini +++ b/tox.ini @@ -26,7 +26,7 @@ deps = # add your fixtures like e.g. pytest_datafiles here setenv = PYTHONPATH = {toxinidir}/src commands = - pylint mypackage + pylint --ignore=_autogenerated.py maloident pylint unittests --rcfile=unittests/.pylintrc # add single files (ending with .py) or packages here @@ -37,7 +37,7 @@ deps = {[testenv:tests]deps} .[type_check] commands = - mypy --show-error-codes src/mypackage --strict + mypy --show-error-codes src/maloident --strict mypy --show-error-codes unittests --strict # add single files (ending with .py) or packages here @@ -62,7 +62,7 @@ setenv = PYTHONPATH = {toxinidir}/src commands = coverage run -m pytest --basetemp={envtmpdir} {posargs} coverage html --omit .tox/*,unittests/* - coverage report --fail-under 80 --omit .tox/*,unittests/* + coverage report --fail-under 100 --omit .tox/*,unittests/* [testenv:compile_requirements] deps = @@ -70,6 +70,15 @@ deps = commands = pip-compile-multi -d dev_requirements --autoresolve +[testenv:codegen] +deps = + .[formatting] + .[codegen] +commands = + datamodel-codegen --encoding utf-8 --input openapi/openapi.yml --output src/maloident/_autogenerated.py --input-file-type openapi --output-model-type pydantic_v2.BaseModel + black src/maloident/_autogenerated.py + isort src/maloident/_autogenerated.py + [testenv:dev] # the dev environment contains everything you need to start developing on your local machine. deps = diff --git a/unittests/example_jsons.py b/unittests/example_jsons.py new file mode 100644 index 0000000..f4d44da --- /dev/null +++ b/unittests/example_jsons.py @@ -0,0 +1,166 @@ +# raw jsons copied from swagger hub +# + manual post-processing to fix the errors in the official docs + +request_body = { + "identificationDateTime": "2023-08-02T22:00:00Z", + "energyDirection": "consumption", + "identificationParameterId": { + "maloId": "57685676748", + "tranchenIds": ["57685676742"], + "meloIds": ["DE00014545768S0000000000000003054"], + "meterNumbers": ["1SM-8465929523"], + "customerNumber": "V567345345", + }, + "identificationParameterAddress": { + "name": {"surnames": "Becker", "firstnames": "Michael", "title": "Prof.Dr.", "company": "BDEW & Co. KG"}, + "address": { + "countryCode": "DE", + "zipCode": "12117", + "city": "Berlin", + "street": "Reinhardtstraße", + "houseNumber": 32, + "houseNumberAddition": "F", + }, + "landParcels": [{"districtName": "string", "lotNumber": "string", "subLotNumber": "string"}], + "geographicCoordinates": { + "latitude": "string", + "longitude": "string", + "east": "string", + "north": "string", + "zone": "UTMZone31", + "northing": "string", + "easting": "string", + }, + }, +} + +positive_response_body = { + "dataMarketLocation": { + "maloId": "57685676748", + "energyDirection": "consumption", + "measurementTechnologyClassification": "intelligentMeasuringSystem", + "optionalChangeForecastBasis": "possible", + "dataMarketLocationProperties": [ + { + "marketLocationProperty": "measured", + "executionTimeFrom": "2023-08-01T22:00:00Z", + "executionTimeUntil": "2023-08-01T22:00:00Z", + } + ], + "dataMarketLocationNetworkOperators": [ + { + "marketPartnerId": 9900987654321, + "executionTimeFrom": "2023-08-01T22:00:00Z", + "executionTimeUntil": "2023-08-01T22:00:00Z", + } + ], + "dataMarketLocationMeasuringPointOperators": [ + { + "marketPartnerId": 9900987654321, + "executionTimeFrom": "2023-08-01T22:00:00Z", + "executionTimeUntil": "2023-08-01T22:00:00Z", + } + ], + "dataMarketLocationTransmissionSystemOperators": [ + { + "marketPartnerId": 9900987654321, + "executionTimeFrom": "2023-08-01T22:00:00Z", + "executionTimeUntil": "2023-08-01T22:00:00Z", + } + ], + "dataMarketLocationSuppliers": [ + { + "marketPartnerId": 9900987654321, + "executionTimeFrom": "2023-08-01T22:00:00Z", + "executionTimeUntil": "2023-08-01T22:00:00Z", + } + ], + "dataMarketLocationName": { + "surnames": "Becker", + "firstnames": "Michael", + "title": "Prof.Dr.", + "company": "BDEW & Co. KG", + }, + "dataMarketLocationAddress": { + "countryCode": "DE", + "zipCode": "12117", + "city": "Berlin", + "street": "Reinhardtstraße", + "houseNumber": 32, + "houseNumberAddition": "F", + }, + "dataMarketLocationLandParcels": [{"districtName": "string", "lotNumber": "string", "subLotNumber": "string"}], + "dataMarketLocationGeographicCoordinates": { + "latitude": "string", + "longitude": "string", + "east": "string", + "north": "string", + "zone": "UTMZone31", + "northing": "string", + "easting": "string", + }, + }, + "dataTranches": [ + { + "tranchenId": "57685676742", + "proportion": "percent", + "percent": 75.912, + "dataTrancheSuppliers": [ + { + "marketPartnerId": 9900987654321, + "executionTimeFrom": "2023-08-01T22:00:00Z", + "executionTimeUntil": "2023-08-01T22:00:00Z", + } + ], + } + ], + "dataMeterLocations": [ + { + "meloId": "DE00014545768S0000000000000003054", + "meterNumber": "1SM-8465929523", + "dataMeterLocationMeasuringPointOperators": [ + { + "marketPartnerId": 9900987654321, + "executionTimeFrom": "2023-08-01T22:00:00Z", + "executionTimeUntil": "2023-08-01T22:00:00Z", + } + ], + } + ], + "dataTechnicalResources": [{"trId": "D1234848431"}], + "dataControllableResources": [ + { + "srId": "C1234848431", + "dataControllableResourceMeasuringPointOperators": [ + { + "marketPartnerId": 9900987654321, + "executionTimeFrom": "2023-08-01T22:00:00Z", + "executionTimeUntil": "2023-08-01T22:00:00Z", + } + ], + } + ], + "dataNetworkLocations": [ + { + "neloId": "E1234848431", + "dataNetworkLocationMeasuringPointOperators": [ + { + "marketPartnerId": 9900987654321, + "executionTimeFrom": "2023-08-01T22:00:00Z", + "executionTimeUntil": "2023-08-01T22:00:00Z", + } + ], + } + ], +} + +positive_response_body["dataMarketLocation"]["dataMarketLocationProperties"][0][ # type:ignore[index] + "marketLocationProperty" +] = "standard" # not "measured", because the official example doesn't match their own schema + +negative_response_body = { + "decisionTree": "E_0594", + "responseCode": "A10", + "reason": "Ich bin ein Freitext.", + "networkOperator": 9900987654321, +} diff --git a/unittests/test_models.py b/unittests/test_models.py new file mode 100644 index 0000000..eee5399 --- /dev/null +++ b/unittests/test_models.py @@ -0,0 +1,43 @@ +from typing import Any + +import pytest + +from maloident.models import IdentificationParameter, ResultNegative, ResultPositive + +from .example_jsons import negative_response_body, positive_response_body, request_body + + +@pytest.mark.parametrize( + "json_dict", + [ + request_body, + ], +) +def test_request_model(json_dict: dict[str, Any]) -> None: + model = IdentificationParameter.model_validate(json_dict) + re_serialized = model.model_dump(mode="json") + assert re_serialized == json_dict + + +@pytest.mark.parametrize( + "json_dict", + [ + positive_response_body, + ], +) +def test_positive_response_model(json_dict: dict[str, Any]) -> None: + model = ResultPositive.model_validate(json_dict) + re_serialized = model.model_dump(mode="json") + assert re_serialized == json_dict + + +@pytest.mark.parametrize( + "json_dict", + [ + negative_response_body, + ], +) +def test_negative_response_model(json_dict: dict[str, Any]) -> None: + model = ResultNegative.model_validate(json_dict) + re_serialized = model.model_dump(mode="json") + assert re_serialized == json_dict diff --git a/unittests/test_myclass.py b/unittests/test_myclass.py deleted file mode 100644 index 900df75..0000000 --- a/unittests/test_myclass.py +++ /dev/null @@ -1,11 +0,0 @@ -from mypackage.mymodule import MyClass - - -class TestMyClass: - """ - A class with pytest unit tests. - """ - - def test_something(self) -> None: - my_class = MyClass() - assert my_class.do_something() == "abc"