From cc624ff36d12ca5917188b90772a8232a7c899ef Mon Sep 17 00:00:00 2001 From: Niels Perfors Date: Mon, 30 Sep 2024 17:50:16 +0200 Subject: [PATCH] Docs (#38) Add ReadTheDocs --- .gitignore | 1 + .readthedocs.yaml | 19 ++ .vscode/tasks.json | 22 ++ README.md | 29 --- README.rst | 30 +++ docs/Makefile | 20 ++ docs/make.bat | 35 +++ docs/requirements.txt | 3 + docs/source/cli.rst | 197 +++++++++++++++ docs/source/client.rst | 14 ++ docs/source/conf.py | 54 ++++ docs/source/index.rst | 15 ++ docs/source/model.rst | 181 +++++++++++++ docs/source/usage.rst | 64 +++++ pyproject.toml | 18 +- src/sapcommissions/__main__.py | 6 +- src/sapcommissions/client.py | 163 ++++++++++-- src/sapcommissions/deploy.py | 19 +- src/sapcommissions/helpers.py | 2 +- src/sapcommissions/model/base.py | 238 ++++++++++++++---- src/sapcommissions/model/data_type.py | 10 +- src/sapcommissions/model/pipeline.py | 6 +- src/sapcommissions/model/resource.py | 20 +- src/sapcommissions/model/rule_element.py | 34 ++- .../model/rule_element_owner.py | 50 +++- 25 files changed, 1108 insertions(+), 142 deletions(-) create mode 100644 .readthedocs.yaml delete mode 100644 README.md create mode 100644 README.rst create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 100644 docs/requirements.txt create mode 100644 docs/source/cli.rst create mode 100644 docs/source/client.rst create mode 100644 docs/source/conf.py create mode 100644 docs/source/index.rst create mode 100644 docs/source/model.rst create mode 100644 docs/source/usage.rst diff --git a/.gitignore b/.gitignore index acc49fa..d91402f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist/ build/ .env *.log +docs/html/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..691cb5a --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,19 @@ +version: 2 + +build: + os: "ubuntu-22.04" + tools: + python: "3.11" + +python: + install: + - requirements: docs/requirements.txt + - method: pip + path: . + +sphinx: + configuration: docs/source/conf.py + fail_on_warning: true + +formats: + - pdf diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 316b711..a7c23c7 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -36,6 +36,28 @@ "panel": "shared" }, "problemMatcher": [] + }, + { + "label": "sphinx", + "type": "shell", + "command": "python -m sphinx -T -W --keep-going -b html -d build/doctrees -D language=en docs/source docs/html", + "group": "test", + "presentation": { + "reveal": "always", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "http server", + "type": "shell", + "command": "python -m http.server -d docs/html", + "group": "test", + "presentation": { + "reveal": "always", + "panel": "shared" + }, + "problemMatcher": [] } ] } diff --git a/README.md b/README.md deleted file mode 100644 index d6b908b..0000000 --- a/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# Python SAP Commissions® - -An Asynchronous Python client to communicate with SAP Commissions®. - -If you like this project, please consider to [BuyMeACoffee](https://www.buymeacoffee.com/niro1987) or -[contact me](mailto:niels.perfors1987@gmail.com) directly. - -[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/niro1987) - -## Installation - -To install the project, run the following command: - -```text -pip install python-sapcommissions -``` - -### REST API - -This project mimics the usage of the SAP Commissions REST API. Visit -`https://{TENANT}.callidusondemand.com/APIDocument` to read the full specification, replacing `TENANT` with your -tenant-id. - -## Legal Disclamer - -This software is designed for use with SAP Commissions®. It is not affiliated with SAP® and the developers -take no legal responsibility for the functionality or security of your Commissions environment. - -SAP Commissions is a registered trademark of SAP SE or its affiliates in Germany and in other countries. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..e453350 --- /dev/null +++ b/README.rst @@ -0,0 +1,30 @@ +Python SAP Commissions® +======================= + +An Asynchronous Python client to communicate with SAP Commissions®. + +If you like this project, please consider to `BuyMeACoffee `_. + +.. image:: https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png + :alt: BuyMeACoffee + :target: https://www.buymeacoffee.com/niro1987 + +Documentation +------------- + +Read the full documentation on [Read the Docs](https://python-sapcommissions.readthedocs.io/) + +REST API +-------- + +This project mimics the usage of the SAP Commissions REST API. Visit +:code:`https://{TENANT}.callidusondemand.com/APIDocument` to read the full specification, replacing :code:`TENANT` with your +tenant-id. + +Legal Disclamer +--------------- + +This software is designed for use with SAP Commissions®. It is not affiliated with SAP® and the developers +take no legal responsibility for the functionality or security of your Commissions environment. + +SAP Commissions is a registered trademark of SAP SE or its affiliates in Germany and in other countries. diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..9534b01 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..f3b1537 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +sphinx==7.4.7 +sphinx-rtd-theme==2.0.0 +myst-parser==4.0.0 diff --git a/docs/source/cli.rst b/docs/source/cli.rst new file mode 100644 index 0000000..a8ece46 --- /dev/null +++ b/docs/source/cli.rst @@ -0,0 +1,197 @@ +Command-Line Interface +====================== + +Usage +----- + +This project includes some command-line-interface commands, +to install the required dependencies, install them with pip: + +.. code-block:: console + + (.venv) $ pip install python-sapcommissions[cli] + +Options: + - ``--tenant {TENANT}``: **Required** + Tenant to connect to, for example 'CALD-DEV'. + - ``--username {USERNAME}``: **Required** + Username for authentication. + - ``--password {PASSWORD}``: **Required** + Password for authentication. + - ``--no-ssl``: *Optional* + Disable SSL validation. + - ``--logfile {PATH}``: *Optional* + Enable logging to a file. + - ``-v``: *Optional* + Increase log verbosity. + +To list all available options and commands: + +.. code-block:: console + + (.venv) $ python -m sapcommissions --help + +.. code-block:: console + + Usage: python -m sapcommissions [OPTIONS] COMMAND [ARGS]... + + Command-line interface for Python SAP Commissions. + + You may provide parameters by setting environment variables + prefixed with 'SAP_' or by passing them as options. + For example: `export SAP_TENANT=CALD-DEV` is equivalent + to passing `--tenant CALD-DEV` + + Options: + -t, --tenant TEXT Tenant to connect to, for example 'CALD-DEV'. + -u, --username TEXT Username for authentication. + -p, --password TEXT Password for authentication. + --no-ssl Disable SSL validation. + -l, --logfile FILE Enable logging to a file. + -v Increase logging verbosity. + --help Show this message and exit. + + Commands: + calendars List all calendars. + deploy Deploy rule elements from a directory to the tenant. + export Export Resource to a file. + periods List all periods for a calendar. + +Each command has it's own options, to list all options, append ``--help`` to the command. + +Calendars +--------- + +List all calendars: + +.. code-block:: console + + (.venv) $ python -m sapcommissions \ + --tenant {TENANT} \ + --username {USERNAME} \ + --password {PASSWORD} \ + calendars + +Periods +------- + +Options: + - ``--calendar {CALENDAR}`` **Required** + The calendar name to list periods for. + - ``--period {PERIOD}`` *Optional* + Name of the Period to search. Allows wildcard like ``*2024*``. + +List all periods for a calendar: + +.. code-block:: console + + (.venv) $ python -m sapcommissions \ + --tenant {TENANT} \ + --username {USERNAME} \ + --password {PASSWORD} \ + periods --calendar {CALENDAR} + +Deploy +------ + +Deploy exported Plan Data and Global Values from a +directory to the tenant. + +Plan data ``*.xml`` is imported with an import pipeline job. +Global Values ``*.txt`` are imported with their respective :ref:`model:data type`. + +To correctly identify the :ref:`model:data type`, files have to follow a specific +naming convention: + + - Event Type.txt + - Credit Type.txt + - Earning Group.txt + - Earning Code.txt + - Fixed Value Type.txt + - Reason Code.txt + +Additionally you can control the order of processing by prefixing the filenames. +For example: + + - 01 Event Type.txt + - 02 Credit Type.txt + - 03 Plan.XML + +.. tip:: + Enable logging to catch import errors. + See ``--logfile log.txt`` in the below example. + +Arguments: + - ``PATH``: **Required** + The path to the directory to be processed. + +.. code-block:: console + + (.venv) $ python -m sapcommissions \ + --tenant {TENANT} \ + --username {USERNAME} \ + --password {PASSWORD} \ + --logfile log.txt \ + deploy ./deploy + + +Export +------ + +Export Credits, Measurements, Incentives, Commissions, Deposits and Payments to a file +in the same way you would using the respective UI workspaces. + +Options: + - ``--calendar {CALENDAR}`` *Optional* + Apply :py:class:`Calendar ` filter on the exported data. + - ``--period {PERIOD}`` *Optional* + Apply :py:class:`Period ` filter on the exported data. + Requires ``--calendar`` to be provided. + - ``--filters {FILTER_TEXT}`` *Optional* + Apply :py:class:`Period ` filter on the exported data. + Can be applied more then once. + +.. tip:: + + Refer to the :ref:`API Documentation ` to understand the filter mechanism. + +Arguments: + - ``RESOURCE`` **Required** + The resource to load into a file. + One of ``{CREDITS|MEASUREMENTS|INCENTIVES|COMMISSIONS|DEPOSITS|PAYMENTS}`` + - ``PATH`` **Required** + Path of the file to write to. + +.. note:: + + Exporting a large number of records will lead to poor performance. It is strngly + recommended to stay below 100.000 records. + +.. tip:: + Enable logging to catch import errors. + See ``--logfile log.txt`` in the below examples. + +Export credits to a file: + +.. code-block:: console + + (.venv) $ python -m sapcommissions \ + --tenant {TENANT} \ + --username {USERNAME} \ + --password {PASSWORD} \ + --logfile log.txt \ + export CREDITS credits.txt \ + --calendar {CALENDAR} \ + --period {PERIOD} + +Export payments above € 100.000,-: + +.. code-block:: console + + (.venv) $ python -m sapcommissions \ + --tenant {TENANT} \ + --username {USERNAME} \ + --password {PASSWORD} \ + --logfile log.txt \ + export PAYMENTS payments.txt \ + --filter "payment ge '100000 EUR'" diff --git a/docs/source/client.rst b/docs/source/client.rst new file mode 100644 index 0000000..7c9935a --- /dev/null +++ b/docs/source/client.rst @@ -0,0 +1,14 @@ +Client +====== + +.. autosummary:: sapcommissions.client + +.. hint:: + See :ref:`usage:examples` + +.. automodule:: sapcommissions.client + + .. autoclass:: CommissionsClient + :members: + :private-members: _request + :member-order: bysource diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..3580c15 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,54 @@ +"""Configuration file for the Sphinx documentation builder.""" +# pylint: disable=all +from importlib.metadata import version as _version + + +# Project information +project = "python-sapcommissions" +copyright = "2024, Niels Perfors" +author = "Niels Perfors" +release = _version(project) +version = '.'.join(release.split('.')[:2]) + +# General configuration +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.autosectionlabel", + "sphinx.ext.autosummary", + "sphinx.ext.doctest", + "sphinx.ext.duration", + "sphinx.ext.intersphinx", + "sphinx.ext.napoleon", # https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html + "sphinx.ext.todo", + "sphinx.ext.viewcode", +] +templates_path = ["_templates"] +intersphinx_mapping = { + "python": ("https://docs.python.org/3/", None), + "sphinx": ("https://www.sphinx-doc.org/en/master/", None), +} +intersphinx_disabled_domains = ["std"] + +# Extensions configuration +autosectionlabel_prefix_document = True +autodoc_class_signature = "separated" +autodoc_default_options = { + "exclude-members": "__init__, __new__", + "show-inheritance": True, +} +html_theme = "sphinx_rtd_theme" +epub_show_urls = "footnote" +napoleon_google_docstring = True +napoleon_numpy_docstring = True +napoleon_include_init_with_doc = False +napoleon_include_private_with_doc = False +napoleon_include_special_with_doc = True +napoleon_use_admonition_for_examples = False +napoleon_use_admonition_for_notes = False +napoleon_use_admonition_for_references = False +napoleon_use_ivar = False +napoleon_use_param = True +napoleon_use_rtype = True +napoleon_preprocess_types = False +napoleon_type_aliases = None +napoleon_attr_annotations = True diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..15e4df4 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,15 @@ +.. include:: ../../README.rst + +.. note:: + + This project is under active development. + +.. toctree:: + :name: toc + :caption: Contents + :maxdepth: 2 + + usage + client + model + cli diff --git a/docs/source/model.rst b/docs/source/model.rst new file mode 100644 index 0000000..934820e --- /dev/null +++ b/docs/source/model.rst @@ -0,0 +1,181 @@ +Model +===== + +.. autosummary:: sapcommissions.model + +.. admonition:: Read the API Documentation + + Visit :code:`https://{TENANT}.callidusondemand.com/APIDocument` + to read the full specification, replacing :code:`TENANT` with your + tenant-id. + + Most of these models are undocumented. For a complete list of attributes, + refer to the API documentation. Attribute names are converted to snake_case + to follow standard python conventions. + + All models support the `read` methods of :py:class:`CommissionsClient `, + refer to the API documentation for a full list of supported methods. + +Base +---- + +.. autosummary:: sapcommissions.model.base +.. automodule:: sapcommissions.model.base + + .. autoclass:: _BaseModel + :members: typed_fields + .. autoclass:: Endpoint + :members: expands + .. autoclass:: Resource + :members: seq + + .. autoclass:: AdjustmentContext + .. autoclass:: Value + .. autoclass:: ValueClass + .. autoclass:: ValueUnitType + .. autoclass:: Reference + .. autoclass:: Assignment + .. autoclass:: BusinessUnitAssignment + .. autoclass:: RuleUsage + .. autoclass:: RuleUsageList + .. autoclass:: SalesTransactionAssignment + +Data Type +--------- + +.. autosummary:: sapcommissions.model.data_type +.. automodule:: sapcommissions.model.data_type + + .. autoclass:: _DataType + .. autoclass:: CreditType + .. autoclass:: EarningCode + .. autoclass:: EarningGroup + .. autoclass:: EventType + .. autoclass:: FixedValueType + .. autoclass:: PositionRelationType + .. autoclass:: Reason + .. autoclass:: StatusCode + .. autoclass:: UnitType + +Rule Elements +------------- + +.. autosummary:: sapcommissions.model.rule_element +.. automodule:: sapcommissions.model.rule_element + + .. autoclass:: _RuleElement + .. autoclass:: Category + .. autoclass:: FixedValue + .. autoclass:: CFixedValue + .. autoclass:: FixedValueVariable + .. autoclass:: Formula + .. autoclass:: RelationalMDLT + .. autoclass:: LookUpTableVariable + .. autoclass:: RateTable + .. autoclass:: RateTableVariable + .. autoclass:: Territory + .. autoclass:: TerritoryVariable + .. autoclass:: Variable + +Rule Element Owners +------------------- + +.. autosummary:: sapcommissions.model.rule_element_owner +.. automodule:: sapcommissions.model.rule_element_owner + + .. autoclass:: _RuleElementOwner + .. autoclass:: Plan + .. autoclass:: Position + .. autoclass:: Title + +Resource +-------- + +.. autosummary:: sapcommissions.model.resource + +.. hint:: + See :ref:`usage:examples` + +.. automodule:: sapcommissions.model.resource + + .. autoclass:: AppliedDeposit + .. autoclass:: AuditLog + .. autoclass:: Balance + .. autoclass:: BusinessUnit + .. autoclass:: Calendar + .. autoclass:: CategoryClassifier + .. autoclass:: CategoryTree + .. autoclass:: Commission + .. autoclass:: Credit + .. autoclass:: Deposit + .. autoclass:: EarningGroupCode + .. autoclass:: GenericClassifier + .. autoclass:: GenericClassifierType + .. autoclass:: GlobalFieldName + .. autoclass:: Incentive + .. autoclass:: Measurement + .. autoclass:: PrimaryMeasurement + .. autoclass:: SecondaryMeasurement + .. autoclass:: Message + .. autoclass:: MessageLog + .. autoclass:: Participant + .. autoclass:: PaymentMapping + .. autoclass:: PaymentSummary + .. autoclass:: Period + .. autoclass:: PeriodType + .. autoclass:: Pipeline + .. autoclass:: PositionGroup + .. autoclass:: PositionRelation + .. autoclass:: PostalCode + .. autoclass:: ProcessingUnit + .. autoclass:: Product + .. autoclass:: Quota + .. autoclass:: SalesOrder + .. autoclass:: SalesTransaction + .. autoclass:: User + .. autoclass:: PlanComponent + .. autoclass:: Rule + .. autoclass:: CreditRule + .. autoclass:: CommissionRule + .. autoclass:: DepositRule + .. autoclass:: MeasurementRule + +Pipeline +-------- + +.. autosummary:: sapcommissions.model.pipeline + +.. hint:: + See example :ref:`example_comp-and-pay` + +.. automodule:: sapcommissions.model.pipeline + + .. autoclass:: _PipelineJob + .. autoclass:: ResetFromValidate + .. autoclass:: Purge + .. autoclass:: XMLImport + .. autoclass:: _PipelineRunJob + .. autoclass:: Classify + .. autoclass:: Allocate + .. autoclass:: Reward + .. autoclass:: Pay + .. autoclass:: Summarize + .. autoclass:: Compensate + .. autoclass:: CompensateAndPay + .. autoclass:: ResetFromClassify + .. autoclass:: ResetFromAllocate + .. autoclass:: ResetFromReward + .. autoclass:: ResetFromPay + .. autoclass:: Post + .. autoclass:: Finalize + .. autoclass:: ReportsGeneration + .. autoclass:: UndoPost + .. autoclass:: UndoFinalize + .. autoclass:: CleanupDefferedResults + .. autoclass:: UpdateAnalytics + .. autoclass:: _ImportJob + .. autoclass:: Validate + .. autoclass:: Transfer + .. autoclass:: ValidateAndTransfer + .. autoclass:: ValidateAndTransferIfAllValid + .. autoclass:: TransferIfAllValid diff --git a/docs/source/usage.rst b/docs/source/usage.rst new file mode 100644 index 0000000..9dee87d --- /dev/null +++ b/docs/source/usage.rst @@ -0,0 +1,64 @@ +Usage +===== + +Installation +------------ + +To use this project, install it with pip: + +.. code-block:: console + + (.venv) $ pip install python-sapcommissions + +This project includes some command-line-interface commands, +to install the required dependencies, install them with pip: + +.. code-block:: console + + (.venv) $ pip install python-sapcommissions[cli] + +Examples +-------- + +.. _example_comp-and-pay: + +Run Comp & Pay +`````````````` + +This example shows how to run Compensate And Pay for +period ``January 2024`` on the ``Main Monthly Calendar``. + +.. code-block:: python + + from aoihttp + from sapcommissions import CommissionsClient, model + from sapcommissions.const import PipelineState, PipelineStatus + from sapcommissions.model.base import Reference + + async def run_comp_and_pay( + client: CommissionsClient, + calendar_name: str, + period_name: str, + ) -> None: + """Run Comp and Pay for the specified period in the specified calendar.""" + + period: model.Period = await client.read_first( + resource_cls=model.Period, + filters=f"calendar/name eq '{calendar_name}' and name eq '{period_name}'", + ) + assert isinstance(period, model.Period) + assert isinstance(period.calendar, Reference) + + job: model.CompensateAndPay = model.CompensateAndPay( + period_seq=period.period_seq, + calendar_seq=period.calendar.key, + ) + + result: model.Pipeline = await client.run_pipeline(job) + + # Optionally you can wait for the pipeline to complete. + while result.state != PipelineState.Done: + await asyncio.sleep(60) + result = await client.read(result) + + assert result.status == PipelineStatus.Successful diff --git a/pyproject.toml b/pyproject.toml index dc7c548..a1973c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,8 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "python-sapcommissions" authors = [{ name = "Niels Perfors", email = "niels.perfors1987@gmail.com" }] -description = "Client for SAP Commissions" -readme = "README.md" +readme = "README.rst" requires-python = ">=3.11" keywords = ["SAP Commissions", "Incentive Management"] license = { text = "MIT" } @@ -38,6 +37,9 @@ dev = [ "click>=8.1.7", "pandas==2.2.2", "pandas-stubs>=2.2.2", + "sphinx==7.4.7", + "sphinx-rtd-theme==2.0.0", + "myst-parser==4.0.0", ] cli = ["click>=8.1.7", "pandas==2.2.2"] @@ -57,14 +59,15 @@ include-package-data = true [tool.setuptools.packages.find] where = ["src"] -exclude = ["tests"] +exclude = ["docs", "tests"] [tool.setuptools.package-data] "sapcommissions" = ["py.typed"] [tool.pylint.MAIN] py-version = "3.11" -ignore = ["tests"] +ignore = ["docs", "tests"] +ignore-paths = ["^docs/.*$"] load-plugins = ["pylint.extensions.code_style", "pylint.extensions.typing"] [tool.pylint."MESSAGES CONTROL"] @@ -93,6 +96,9 @@ log_file_level = "DEBUG" log_file_format = "%(asctime)s.%(msecs)03d %(levelname)-8s %(name)25s:%(lineno)-4s %(message)s" log_file_date_format = "%Y-%m-%d %H:%M:%S" +[tool.ruff] +exclude = ["docs"] + [tool.ruff.lint] select = [ "B002", # Python does not support the unary prefix increment @@ -180,6 +186,7 @@ ignore = [ "ISC001", # Use a single space after the "#" in a comment "ISC002", # Use a single space after the "#" in a comment ] +pydocstyle.convention = "google" [tool.ruff.lint.per-file-ignores] "tests/**/*.py" = [ @@ -190,3 +197,6 @@ ignore = [ known-first-party = ["sapcommissions"] combine-as-imports = true split-on-trailing-comma = true + +[tool.mypy] +exclude = ["^docs/.*$"] diff --git a/src/sapcommissions/__main__.py b/src/sapcommissions/__main__.py index 6fe825c..be98797 100644 --- a/src/sapcommissions/__main__.py +++ b/src/sapcommissions/__main__.py @@ -294,9 +294,9 @@ def deploy( \b Additionally you can control the order of processing by prefixing the filenames numerically. For example: - - 01_Event Type.txt - - 02_Credit Type.txt - - 03_Plan.XML + - 01 Event Type.txt + - 02 Credit Type.txt + - 03 Plan.XML """ # noqa: D301 LOGGER.info("Deploy '%s'", path) diff --git a/src/sapcommissions/client.py b/src/sapcommissions/client.py index d125756..0261296 100644 --- a/src/sapcommissions/client.py +++ b/src/sapcommissions/client.py @@ -1,4 +1,4 @@ -"""Python SAP Commissions Client.""" +"""Asynchronous Client to interact with SAP Commissions REST API.""" import asyncio import logging @@ -16,7 +16,7 @@ LOGGER: logging.Logger = logging.getLogger(__name__) T = TypeVar("T", bound="model.base.Resource") -REQUEST_TIMEOUT: int = 30 +REQUEST_TIMEOUT: int = 60 STATUS_NOT_MODIFIED: int = 304 STATUS_BAD_REQUEST: int = 400 STATUS_SERVER_ERROR: int = 500 @@ -47,7 +47,18 @@ @dataclass class CommissionsClient: - """Client interface for interacting with SAP Commissions.""" + """Asynchronous interface to interacting with SAP Commissions REST API. + + Parameters: + tenant (str): Your tenant ID. For example, if the login url is + `https://cald-prd.callidusondemand.com/SalesPortal/#!/`, + the tenant ID is `cald-prd`. + session (ClientSession): An aiohttp ClientSession. + verify_ssl (bool, optional): Enable SSL verification. + Defaults to True. + request_timeout (int, optional): Request timeout in seconds. + Defaults to 60. + """ tenant: str session: ClientSession @@ -66,7 +77,23 @@ async def _request( params: dict | None = None, json: list | None = None, ) -> dict[str, Any]: - """Send a request.""" + """Send a request. + + Parameters: + method (str): HTTP method (GET, POST, PUT, DELETE, UDPATE). + uri (str): API endpoint URI. + params (dict, optional): Query parameters. + json (list, optional): JSON payload. + + Returns: + dict: The JSON response. + + Raises: + SAPConnectionError: If the connection fails. + SAPNotModified: If the resource has not been modified. + SAPResponseError: If the response status is not as expected. + SAPBadRequest: If the request status indicates an error. + """ LOGGER.debug("Request: %s, %s, %s", method, uri, params) try: @@ -91,18 +118,17 @@ async def _request( msg = "Resource not modified" raise exceptions.SAPNotModified(msg) + response_text: str = await response.text() if ( response.status not in REQUIRED_STATUS[method] and response.status != STATUS_BAD_REQUEST ): - text = await response.text() - msg = f"Unexpected status. {response.status}: {text}" + msg = f"Unexpected status. {response.status}: {response_text}" LOGGER.error(msg) raise exceptions.SAPResponseError(msg) if (content_type := response.headers.get("Content-Type")) != "application/json": - text = await response.text() - msg = f"Unexpected Content-Type. {content_type}: {text}" + msg = f"Unexpected Content-Type. {content_type}: {response_text}" LOGGER.error(msg) raise exceptions.SAPResponseError(msg) @@ -112,7 +138,19 @@ async def _request( return json_data async def create(self, resource: T) -> T: - """Create a new resource.""" + """Create a new resource. + + Parameters: + resource (T): The resource to create. + + Returns: + T: The created resource. + + Raises: + SAPAlreadyExists: If the resource already exists. + SAPMissingField: If one or more required fields are missing. + SAPResponseError: If the creation encountered an error. + """ cls = type(resource) LOGGER.debug("Create %s(%s)", cls.__name__, resource) @@ -158,7 +196,17 @@ async def create(self, resource: T) -> T: raise async def update(self, resource: T) -> T: - """Update an existing resource.""" + """Update an existing resource. + + Parameters: + resource (T): The resource to update. + + Returns: + T: The updated resource. + + Raises: + SAPResponseError: If the update encountered an error. + """ cls = type(resource) LOGGER.debug("Update %s(%s)", cls.__name__, resource) @@ -203,7 +251,17 @@ async def update(self, resource: T) -> T: raise async def delete(self, resource: T) -> bool: - """Delete a resource.""" + """Delete a resource. + + Parameters: + resource (T): The resource to delete. + + Returns: + bool: True if the resource was deleted. Raises an exception othwise. + + Raises: + SAPResponseError: If the deletion encountered an error. + """ cls = type(resource) LOGGER.debug("Delete %s(%s)", cls.__name__, resource) @@ -254,7 +312,20 @@ async def read_all( # pylint: disable=too-many-arguments,too-many-locals # noq order_by: list[str] | None = None, page_size: int = 10, ) -> AsyncGenerator[T, None]: - """Read all matching resources.""" + """Read all matching resources. + + Parameters: + resource_cls (type[T]): The type of the resource to list. + filters (BooleanOperator | LogicalOperator | str, optional): The filters to apply. + order_by (list[str], optional): The fields to order by. + page_size (int, optional): The number of resources per page. Defaults to 10. + + Returns: + AsyncGenerator[T, None]: An asynchronous generator yielding the matching resources. + + Yields: + T: Matching resource. + """ LOGGER.debug( "List %s filters=%s order_by=%s page_size=%s", resource_cls.__name__, @@ -325,7 +396,15 @@ async def read_first( ) -> T | None: """Read the first matching resource. - TODO: Fix type error + A convenience method for `await anext(read_all(...))` with `page_size=1`. + + Parameters: + resource_cls (type[T]): The type of the resource to read. + filters (BooleanOperator | LogicalOperator | str, optional): The filters to apply. + order_by (list[str], optional): The fields to order by. + + Returns: + T | None: The first matching resource. None if there are no matching resources. """ LOGGER.debug("Read %s %s", resource_cls.__name__, f"filters={filters}") list_resources = self.read_all( @@ -340,7 +419,18 @@ async def read_first( return None async def read_seq(self, resource_cls: type[T], seq: str) -> T: - """Read the specified resource.""" + """Read the specified resource. + + Parameters: + resource_cls (type[T]): The type of the resource to read. + seq (str): The unique identifier of the resource. + + Returns: + T: The specified resource. Raises an exception if the resource is not found. + + Raises: + SAPBadRequest: If the resource was not found. + """ LOGGER.debug("Read Seq %s(%s)", resource_cls.__name__, seq) uri: str = f"{resource_cls.attr_endpoint}({seq})" @@ -360,7 +450,26 @@ async def read_seq(self, resource_cls: type[T], seq: str) -> T: raise async def read(self, resource: T) -> T: - """Reload a fully initiated resource.""" + """Reload a fully initiated resource. + + A convenience method for `await read_seq(resource.__class__, resource.seq)`. + + Parameters: + resource (T): The fully initiated resource. + + Returns: + T: The fully initiated resource. + + Example: + When running a pipeline job, you can wait for the job to complete: + + .. code-block:: python + + pipeline = await run_pipeline(job) + while pipeline.state != PipelineState.Done: + await asyncio.sleep(30) + pipeline = client.read(pipeline) + """ cls = type(resource) LOGGER.debug("Read %s(%s)", cls.__name__, resource.seq) if not (seq := resource.seq): @@ -368,7 +477,17 @@ async def read(self, resource: T) -> T: return await self.read_seq(cls, seq) async def run_pipeline(self, job: model.pipeline._PipelineJob) -> model.Pipeline: - """Run a pipeline and retrieves the created Pipeline.""" + """Run a pipeline and retrieves the created Pipeline. + + Parameters: + job (model.pipeline._PipelineJob): The pipeline job to run. + + Returns: + model.Pipeline: The created Pipeline. + + Raises: + SAPResponseError: If the pipeline failed to run. + """ LOGGER.debug("Run pipeline %s", type(job).__name__) json: dict[str, Any] = job.model_dump(by_alias=True, exclude_none=True) @@ -409,7 +528,17 @@ async def run_pipeline(self, job: model.pipeline._PipelineJob) -> model.Pipeline return await self.read_seq(model.Pipeline, seq) async def cancel_pipeline(self, job: model.Pipeline) -> bool: - """Cancel a running pipeline.""" + """Cancel a running pipeline. + + Parameters: + job (model.Pipeline): The running pipeline job to cancel. + + Returns: + bool: True if the pipeline was successfully canceled. Raises an exception othwise. + + Raises: + SAPResponseError: If the deletion encountered an error. + """ LOGGER.debug("Cancel %s(%s)", job.command, job.pipeline_run_seq) uri: str = f"{job.attr_endpoint}({job.pipeline_run_seq})" diff --git a/src/sapcommissions/deploy.py b/src/sapcommissions/deploy.py index 7e6982c..d1c7afb 100644 --- a/src/sapcommissions/deploy.py +++ b/src/sapcommissions/deploy.py @@ -15,25 +15,25 @@ LOGGER: logging.Logger = logging.getLogger(__name__) RE_CREDIT_TYPE: Final[re.Pattern] = re.compile( - r"^([a-z0-9_.-]+)?(Credit Type)\.txt$", re.IGNORECASE + r"^[a-zA-Z0-9_.\- ]*(Credit Type)\.txt$", ) RE_EARNING_CODE: Final[re.Pattern] = re.compile( - r"^([a-z0-9_.-]+)?(Earning Code)\.txt$", re.IGNORECASE + r"^[a-zA-Z0-9_.\- ]*(Earning Code)\.txt$", ) RE_EARNING_GROUP: Final[re.Pattern] = re.compile( - r"^([a-z0-9_.-]+)?(Earning Group)\.txt$", re.IGNORECASE + r"^[a-zA-Z0-9_.\- ]*(Earning Group)\.txt$", ) RE_EVENT_TYPE: Final[re.Pattern] = re.compile( - r"^([a-z0-9_.-]+)?(Event Type)\.txt$", re.IGNORECASE + r"^[a-zA-Z0-9_.\- ]*(Event Type)\.txt$", ) RE_FIXED_VALUE_TYPE: Final[re.Pattern] = re.compile( - r"^([a-z0-9_.-]+)?(Fixed Value Type)\.txt$", re.IGNORECASE + r"^[a-zA-Z0-9_.\- ]*(Fixed Value Type)\.txt$", ) RE_REASON_CODE: Final[re.Pattern] = re.compile( - r"^([a-z0-9_.-]+)?(Reason Code)\.txt$", re.IGNORECASE + r"^[a-zA-Z0-9_.\- ]*(Reason Code)\.txt$", ) RE_XML: Final[re.Pattern] = re.compile( - r"^([a-z0-9_.-]+)?([a-z0-9_.-]+)\.xml$", re.IGNORECASE + r"^[a-zA-Z0-9_.\- ]*[a-zA-Z0-9_.\- ]+\.xml$", ) @@ -92,10 +92,7 @@ async def deploy_resources_from_file( for row in reader: resources.append( resource_cls( - **{ - "id": row[0], - "description": row[1] if row[1] else None - } + **{"id": row[0], "description": row[1] if row[1] else None} ) ) tasks = [deploy_resource(client, resource) for resource in resources] diff --git a/src/sapcommissions/helpers.py b/src/sapcommissions/helpers.py index e91ff9a..c916432 100644 --- a/src/sapcommissions/helpers.py +++ b/src/sapcommissions/helpers.py @@ -99,7 +99,7 @@ class BooleanOperator: def __init__(self, *conditions: Union[LogicalOperator, "BooleanOperator"]): """Initialize the BooleanExpression with conditions. - Args: + Attributes: ---- *conditions: Instances of LogicalOperator or BooleanOperator. diff --git a/src/sapcommissions/model/base.py b/src/sapcommissions/model/base.py index 36bca94..a9d8cbe 100644 --- a/src/sapcommissions/model/base.py +++ b/src/sapcommissions/model/base.py @@ -1,4 +1,9 @@ -"""Base models for Python SAP Commissions Client.""" +"""Pydantic models for Python SAP Commissions Client. + +These classes are generally not used directly but can be usefull +for type checking and type hints. Used to inherrit function on +all other models. +""" from datetime import datetime from importlib import import_module @@ -18,7 +23,13 @@ class _BaseModel(BaseModel): - """BaseModel for SAP Commissions.""" + """BaseModel inherited from ``pydantic.BaseModel``. + + Contains the primary model_config which is required + for Pydantic to convert field names between + snake_case and camelCase when sending and recieving + json data from/to the SAP Commissions tenant. + """ model_config: ClassVar[ConfigDict] = ConfigDict( str_strip_whitespace=True, @@ -53,7 +64,16 @@ def typed_fields( cls, typed: type | tuple[type, ...], ) -> dict[str, FieldInfo]: - """Extract all fields from a resource of the specified type.""" + """Return model fields of the specified type. + + This method can be usefull when converting data types for + example. + + Returns: + A dictionary of attributes annotated with the specified type + where the keys are attribute names and the values are + `FieldInfo` objects. + """ model_fields: dict[str, FieldInfo] = cls.model_fields fields: dict[str, FieldInfo] = {} @@ -83,76 +103,106 @@ def _process_type( class Endpoint(_BaseModel): - """BaseModel for an Endpoint.""" + """Base class for resources that can connect with the client. + + Parameters: + attr_endpoint (str): URI endpoint to connect with + tenant. Must follow format ``api/v2/nameOfResource``. + Used by the client to construct the full request url. + """ attr_endpoint: ClassVar[str] @classmethod def expands(cls) -> dict[str, FieldInfo]: - """Return model fields that refer to onther resource.""" - return cls.typed_fields(Expandable) + """Return model fields that refer to another model class. + This function is primarily used by the client to add the + ``expand`` parameter to the request. -class Resource(Endpoint): - """Base class for a Resource.""" - - attr_seq: ClassVar[str] - - @property - def seq(self) -> str | None: - """Return the `seq` attribute value for the resource.""" - return getattr(self, self.attr_seq) - - -class Assignment(_BaseModel): - """BaseModel for Assignment.""" - - key: str | None = None - owned_key: str | None = None + Returns: + A dictionary of attributes that can be expanded + where the keys are attribute names and the values are + ``FieldInfo`` objects. + """ + return cls.typed_fields(Expandable) -class BusinessUnitAssignment(_BaseModel): - """BaseModel for BusinessUnitAssignment.""" - - mask: int - smask: int +class Resource(Endpoint): + """Base class for a resource. + Every resource has it's own attribute that uniquely + identifies the object on the tenant. Inheritance of + this class allows us to refer to the system unique + identifier without having to address it directly. -class RuleUsage(_BaseModel): - """BaseModel for RuleUsage.""" + Example: + Attributes ``seq`` and ``credit_seq`` are + equal:: - id: str - name: str + assert Credit.seq == Credit.credit_seq + Parameters: + attr_seq (str): Name of attribute that contains the + system unique identifier (seq). + """ -class RuleUsageList(_BaseModel): - """BaseModel for RuleUsage lists.""" + attr_seq: ClassVar[str] - children: list[RuleUsage] + @property + def seq(self) -> str | None: + """System unique identifier (seq) of the resource instance.""" + return getattr(self, self.attr_seq) class ValueUnitType(_BaseModel): - """BaseModel for UnitType.""" + """Unit Type of for ``Value``. + + Parameters: + name (str): Name of the unit type. + unit_type_seq (str): System unique identifier. + """ name: str unit_type_seq: str class Value(_BaseModel): - """BaseModel for Value.""" + """Value object used by all numeric fields. + + Parameters: + value (int | float | None): The amount. + unit_type (ValueUnitType): Type of amount. + """ value: int | float | None unit_type: ValueUnitType class ValueClass(_BaseModel): - """BaseModel for ValueClass.""" + """Value Class, used only by ``UnitType``. + + Parameters: + display_name (str): Name of the value class. + """ display_name: str class AdjustmentContext(_BaseModel): - """Adjustment Context for a Sales Transaction.""" + """Adjustment Context for ``SalesTransaction``. + + Used only when updating the value of a ``SalesTransaction``. + + Parameters: + adjust_type_flag (Literal["adjustTo", "adjustBy", "reset"]): + - ``adjustTo`` + - ``adjustBy`` + - ``reset`` + adjust_to_value (Value | None): Adjust value to this amount. + adjust_by_value (Value | None): Adjust value by this amount. + comment (str | None): Adjustment comment. + """ adjust_type_flag: Literal["adjustTo", "adjustBy", "reset"] adjust_to_value: Value | None = None @@ -161,7 +211,7 @@ class AdjustmentContext(_BaseModel): class Generic16Mixin(_BaseModel): - """Mixin to add Generic Attributes to a model.""" + """Mixin to add generic fields to a model.""" ga1: str | None = Field(None, alias="genericAttribute1") ga2: str | None = Field(None, alias="genericAttribute2") @@ -200,7 +250,7 @@ class Generic16Mixin(_BaseModel): class Generic32Mixin(Generic16Mixin): - """Mixin to add extended Generic Attributes to a model.""" + """Mixin to add generic fields to a model.""" ga17: str | None = Field(None, alias="genericAttribute17") ga18: str | None = Field(None, alias="genericAttribute18") @@ -221,11 +271,25 @@ class Generic32Mixin(Generic16Mixin): class Expandable(_BaseModel): - """Pydantic BaseModel to indicate expandable field.""" + """Indicates expandable field. + + Any model field that is annotated with a subclass of + ``Expandable`` will be added to the ``expand`` parameter + when sending requests to the tenant. + """ class Reference(Expandable): - """Expanded reference to another resource.""" + """Expanded reference to a resource. + + Parameters: + key (str): System unique identifier for the referred resource. + display_name (str): Name of the referred resource. + object_type (type[model.Resource]): Class of the referred resource. + key_string (str): Seems to always be the same as ``key``. + logical_keys (dict[str, str | int | Value | Any]): Some key + attributes of the referred resource. + """ key: str display_name: str @@ -250,13 +314,95 @@ def __str__(self) -> str: class SalesTransactionAssignment(Expandable, Generic16Mixin): - """SalesTransaction Assignment.""" + """Expanded reference to a transaction assignment. + + Parameters: + payee_id (str | None): Participant ID assigned to the transaction. + position_name (str | None): Position Name assigned to the transaction. + title_name (str | None): Title Name assigned to the transaction. + sales_order (str | None): Order ID of the transaction. + sales_transaction_seq (str): System unique identifier of the + transaction. + set_number (int | None): Index of the Assignment. + compensation_date (datetime | None): Compensation Date of the + transaction. + processing_unit (str | None): System unique identifier of the + Processing Unit. + ga{1-16} (str | None): Generic Attributes. + gn{1-6} (Value | None): Generic Numbers. + gd{1-6} (datetime | None): Generic Dates. + gb{1-6} (bool | None): Generic Booleans. + """ + payee_id: str | None = None + position_name: str | None = None + title_name: str | None = None sales_order: str sales_transaction_seq: str set_number: int | None = None compensation_date: datetime | None = None - title_name: str | None = None - position_name: str | None = None - payee_id: str | None = None processing_unit: str | None = None + + +class Assignment(_BaseModel): + """Assignment. + + Used by ``Pipeline`` to refer to stage tables and by + ``Plan``, ``Title`` and ``Position`` to refer to + Variable Assignments. + + Parameters: + key (str | None): Not sure really. + owned_key (str | None): Also not sure really. + + TODO: Is this an expandable reference? + """ + + key: str | None = None + owned_key: str | None = None + + +class BusinessUnitAssignment(_BaseModel): + """Business Unit Assignment. + + Used by ``AuditLog`` and ``Rule`` to refer to + Business Units. + + Parameters: + mask (int): Not sure really. + smask (int): Seems to be the same as mask. + + TODO: Is this an expandable reference? + """ + + mask: int + smask: int + + +class RuleUsage(_BaseModel): + """Rule Usage. + + Used by ``Rule`` and ``Rule Elements`` for some reason. + + Parameters: + id (str): ID + name (str): Name + + TODO: Is this an expandable reference? + """ + + id: str + name: str + + +class RuleUsageList(_BaseModel): + """List of RuleUsage. + + Parameters: + children (list[RuleUsage]): List of RuleUsage elements. + + TODO: Is this an expandable reference? + TODO: Make this class accessible as iterator of ``RuleUsage``. + """ + + children: list[RuleUsage] diff --git a/src/sapcommissions/model/data_type.py b/src/sapcommissions/model/data_type.py index 090c7f2..7824fe9 100644 --- a/src/sapcommissions/model/data_type.py +++ b/src/sapcommissions/model/data_type.py @@ -1,4 +1,4 @@ -"""DataType models for Python SAP Commissions Client.""" +"""Pydantic models for Data Type Resources.""" from datetime import datetime from typing import ClassVar @@ -9,7 +9,7 @@ class _DataType(Resource): - """Base class for Data Type resources.""" + """Base class for DataType resources.""" attr_seq: ClassVar[str] = "data_type_seq" data_type_seq: str | None = None @@ -42,7 +42,7 @@ class EarningGroup(_DataType): class EventType(_DataType): - """Class representation of an Event Type.""" + """Event Type.""" attr_endpoint: ClassVar[str] = "api/v2/eventTypes" event_type_id: str = Field(validation_alias=AliasChoices("eventTypeId", "id")) @@ -75,14 +75,14 @@ class StatusCode(_DataType): """Status Code.""" attr_endpoint: ClassVar[str] = "api/v2/statusCodes" + status: str name: str | None = None type: str | None = None - status: str is_active: bool = True class UnitType(_DataType): - """Class representation of a Unit Type.""" + """Unit Type.""" attr_endpoint: ClassVar[str] = "api/v2/unitTypes" unit_type_seq: str diff --git a/src/sapcommissions/model/pipeline.py b/src/sapcommissions/model/pipeline.py index 54c3d81..4ed0591 100644 --- a/src/sapcommissions/model/pipeline.py +++ b/src/sapcommissions/model/pipeline.py @@ -1,4 +1,4 @@ -"""Pipeline models for Python SAP Commissions Client.""" +"""Pydantic models for Pipeline jobs.""" from typing import ClassVar, Literal @@ -306,8 +306,8 @@ def validate_conditional_fields(self) -> "_ImportJob": """Validate conditional required fields. Validations: - ----------- - - run_mode can only be 'new' when importing TransactionalData + ------------ + run_mode can only be 'new' when importing TransactionalData """ if ( self.module != const.StageTables.TransactionalData diff --git a/src/sapcommissions/model/resource.py b/src/sapcommissions/model/resource.py index ee11313..e440aed 100644 --- a/src/sapcommissions/model/resource.py +++ b/src/sapcommissions/model/resource.py @@ -1,4 +1,4 @@ -"""Resource models for Python SAP Commissions Client.""" +"""Pydantic models for Resources.""" from datetime import datetime from typing import ClassVar, Literal @@ -22,7 +22,11 @@ class AppliedDeposit(Resource): - """AppliedDeposit.""" + """AppliedDeposit. + + Note: + Supports only ``read`` operations. + """ attr_endpoint: ClassVar[str] = "api/v2/appliedDeposits" attr_seq: ClassVar[str] = "applied_deposit_seq" @@ -42,7 +46,11 @@ class AppliedDeposit(Resource): class AuditLog(Resource): - """Audit Log.""" + """Audit Log. + + Note: + Supports only ``read`` operations. + """ attr_endpoint: ClassVar[str] = "api/v2/auditLogs" attr_seq: ClassVar[str] = "audit_log_seq" @@ -59,7 +67,11 @@ class AuditLog(Resource): class Balance(Resource): - """Balance.""" + """Balance. + + Note: + Supports only ``read`` operations. + """ attr_endpoint: ClassVar[str] = "api/v2/balances" attr_seq: ClassVar[str] = "balance_seq" diff --git a/src/sapcommissions/model/rule_element.py b/src/sapcommissions/model/rule_element.py index 854ad43..ff47940 100644 --- a/src/sapcommissions/model/rule_element.py +++ b/src/sapcommissions/model/rule_element.py @@ -1,4 +1,4 @@ -"""RuleElement models for Python SAP Commissions Client.""" +"""Pydantic models for Rule Element Resources.""" from datetime import datetime from typing import ClassVar @@ -17,7 +17,10 @@ class _RuleElement(Resource): - """Base class for Rule Element resources.""" + """Base class for Rule Element resources. + + TODO: What does ``owning_element`` represent? + """ attr_seq: ClassVar[str] = "rule_element_seq" rule_element_seq: str | None = None @@ -28,7 +31,6 @@ class _RuleElement(Resource): effective_end_date: datetime business_units: list[str] | None = None not_allow_update: bool = False - model_seq: str | None = None reference_class_type: str | None = None return_type: str | None = None owning_element: str | None = None @@ -37,6 +39,7 @@ class _RuleElement(Resource): created_by: str | None = Field(None, exclude=True, repr=False) create_date: datetime | None = Field(None, exclude=True, repr=False) modified_by: str | None = Field(None, exclude=True, repr=False) + model_seq: str | None = None class Category(_RuleElement, Generic16Mixin): @@ -57,7 +60,7 @@ class FixedValue(_RuleElement): class CFixedValue(FixedValue): - """Alias for FixedValue.""" + """Alias for ``FixedValue``.""" class Formula(_RuleElement): @@ -83,7 +86,10 @@ class LookUpTableVariable(_RuleElement): class RateTable(_RuleElement): - """Rate Table.""" + """Rate Table. + + TODO: Does this endpoint return ``default_element``? + """ attr_endpoint: ClassVar[str] = "api/v2/rateTables" default_element: str | Reference | None = None @@ -100,16 +106,23 @@ class RateTableVariable(_RuleElement): class RelationalMDLT(_RuleElement): - """Relational MDLT.""" + """Relational MDLT (Lookup Table). + + Multi Dimensional Lookup Table. + + TODO: Does this endpoint return ``default_element``? + TODO: Are ``dimensions`` and ``indices`` expandable? + TODO: What does ``expression_type_counts`` represent? + """ attr_endpoint: ClassVar[str] = "api/v2/relationalMDLTs" default_element: str | Reference | None = None required_period_type: str | Reference | None = None return_unit_type: str | Reference | None = None - indices: list[Assignment] | Assignment | None = None treat_null_as_zero: bool | None = None - expression_type_counts: str | None = None dimensions: list[Assignment] | Assignment | None = None + indices: list[Assignment] | Assignment | None = None + expression_type_counts: str | None = None class Territory(_RuleElement): @@ -127,7 +140,10 @@ class TerritoryVariable(_RuleElement): class Variable(_RuleElement): - """Variable.""" + """Variable. + + TODO: What does ``default_element`` refer to? + """ attr_endpoint: ClassVar[str] = "api/v2/variables" default_element: str | Reference | None = None diff --git a/src/sapcommissions/model/rule_element_owner.py b/src/sapcommissions/model/rule_element_owner.py index ce7e6d6..70f47ee 100644 --- a/src/sapcommissions/model/rule_element_owner.py +++ b/src/sapcommissions/model/rule_element_owner.py @@ -1,4 +1,4 @@ -"""RuleElementOwner models for Python SAP Commissions Client.""" +"""Pydantic models for Rule Element Owner Resources.""" from datetime import datetime from typing import ClassVar @@ -14,7 +14,11 @@ class _RuleElementOwner(Resource): - """Base class for Rule Element Owner resources.""" + """Base class for Rule Element Owner resources. + + TODO: ``variable_assignments`` should be ``Reference``? + TODO: ``business_units`` should be ``Reference``? + """ attr_seq: ClassVar[str] = "rule_element_owner_seq" rule_element_owner_seq: str | None = None @@ -31,27 +35,53 @@ class _RuleElementOwner(Resource): class Plan(_RuleElementOwner): - """Plan.""" + """Plan. + + Parameters: + rule_element_owner_seq (str | None): System Unique Identifier. + name (str): Name of the plan. + description (str | None): Description of the plan. + calendar (str | Reference): Reference to ``Calendar`` associated + with the plan. + effective_start_date (datetime): Effective start date of the plan + version. + effective_end_date (datetime): Effective end date of the plan version. + create_date (datetime | None): Date when plan was created. + created_by (str | None): User ID that created the plan. + modified_by (str | None): User ID that last modified the plan. + business_units (list[str] | None): Business units associated with the + plan. + variable_assignments (list[Assignment] | Assignment | None): Variable + Assignments on the plan level. + model_seq (str | None): System Unique Identifier for the model. + + TODO: Add GenericMixin? + TODO: is ``variable_assignments`` expandable? + """ attr_endpoint: ClassVar[str] = "api/v2/plans" calendar: str | Reference class Position(_RuleElementOwner, Generic16Mixin): - """Position.""" + """Position. + + TODO: ``target_compensation`` is ``Value``? + TODO: ``processing_unit`` should be ``Reference``? + """ attr_endpoint: ClassVar[str] = "api/v2/positions" + payee: str | Reference | None = None + plan: str | Reference | None = None + title: str | Reference | None = None + manager: str | Reference | None = None + position_group: str | Reference | None = None + target_compensation: dict | None = None credit_start_date: datetime | None = None credit_end_date: datetime | None = None processing_start_date: datetime | None = None processing_end_date: datetime | None = None - target_compensation: dict | None = None processing_unit: str | None = None - manager: str | Reference | None = None - title: str | Reference | None = None - plan: str | Reference | None = None - position_group: str | Reference | None = None - payee: str | Reference | None = None class Title(_RuleElementOwner, Generic16Mixin):