From ba54939a3bc6053d44b77df540748dff29648eaa Mon Sep 17 00:00:00 2001 From: raj_mathe Date: Fri, 14 Oct 2022 12:16:08 +0200 Subject: [PATCH] main > main: init --- .coveragerc | 22 +++ .gitignore | 86 +++++++++++ LICENSE | 0 README.md | 77 ++++++++++ dist/VERSION | 1 + justfile | 259 +++++++++++++++++++++++++++++++++ main.py | 28 ++++ pyproject.toml | 57 ++++++++ requirements.txt | 49 +++++++ src/__init__.py | 0 src/core/__init__.py | 0 src/core/calls.py | 215 +++++++++++++++++++++++++++ src/core/env.py | 61 ++++++++ src/core/log.py | 163 +++++++++++++++++++++ src/core/utils.py | 36 +++++ src/thirdparty/__init__.py | 0 src/thirdparty/code.py | 51 +++++++ src/thirdparty/config.py | 32 ++++ src/thirdparty/io.py | 16 ++ src/thirdparty/log.py | 18 +++ src/thirdparty/maths.py | 22 +++ src/thirdparty/misc.py | 24 +++ src/thirdparty/render.py | 27 ++++ src/thirdparty/run.py | 16 ++ src/thirdparty/system.py | 20 +++ src/thirdparty/types.py | 84 +++++++++++ templates/template-config.yaml | 0 templates/template.env | 0 tests/README.md | 1 + 29 files changed, 1365 insertions(+) create mode 100644 .coveragerc create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 dist/VERSION create mode 100644 justfile create mode 100644 main.py create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100644 src/__init__.py create mode 100644 src/core/__init__.py create mode 100644 src/core/calls.py create mode 100644 src/core/env.py create mode 100644 src/core/log.py create mode 100644 src/core/utils.py create mode 100644 src/thirdparty/__init__.py create mode 100644 src/thirdparty/code.py create mode 100644 src/thirdparty/config.py create mode 100644 src/thirdparty/io.py create mode 100644 src/thirdparty/log.py create mode 100644 src/thirdparty/maths.py create mode 100644 src/thirdparty/misc.py create mode 100644 src/thirdparty/render.py create mode 100644 src/thirdparty/run.py create mode 100644 src/thirdparty/system.py create mode 100644 src/thirdparty/types.py create mode 100644 templates/template-config.yaml create mode 100644 templates/template.env create mode 100644 tests/README.md diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..2068dde --- /dev/null +++ b/.coveragerc @@ -0,0 +1,22 @@ +[run] +source="." + +[report] +show_missing = true +omit = + # ignore tests folder + tests/* + # ignore thirdparty imports + src/thirdparty/* + # ignore models folder (auto generated) + models/** + # ignore __init__ files (only used for exports) + **/__init__.py + # ignore main.py, app.py (-> too macroscopic and covered by integration tests) + main.py + src/app.py +# TODO: increase code-coverage: +fail_under = 100 +precision = 1 +exclude_lines = + pragma: no cover diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd2b917 --- /dev/null +++ b/.gitignore @@ -0,0 +1,86 @@ +* +!/.gitignore + +################################################################ +# DOCKER +################################################################ + +!/.dockerignore +!/Dockerfile +!/docker-compose.yaml + +################################################################ +# MAIN FOLDER +################################################################ + +!/justfile +!/Makefile +!/README.md +!/LICENSE +!/.coveragerc +!/pyproject.toml +!/requirements* + +################################################################ +# PROJECT FILES +################################################################ + +!/templates +!/templates/template* + +!/src +!/src/**/ +!/src/**/*.py +!/src/**/*.yaml +!/main.py + +!/notebooks +!/notebooks/*.ipynb +!/notebooks/*.md + +# NOTE: models are created as part of the build process: +!/models +!/models/*.yaml +!/models/README.md + +!/docs +!/docs/*/ +!/docs/*/Models/ +!/docs/**/*.md + +!/tests +!/tests/**/ +!/tests/**/*.py +!/tests/resources/*.yaml +!/tests/README.md + +!/dist +!/dist/VERSION + +################################################################ +# AUXLIARY +################################################################ + +/logs +/**/.env + +################################################################ +# TEMPLATES +################################################################ + +!/templates/.env + +################################################################ +# ARTEFACTS +################################################################ + +/**/__pycache__ +/**/.pytest_cache +/**/.DS_Store +/**/__archive__* + +################################################################ +# Git Keep +################################################################ + +!/**/.gitkeep diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e6f02ed --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# Algorithmen und Datenstrukturen I, WiSe 2022-23 # + +Der Inhalt dieses Repository ist keineswegs verpflichtend, sondern dient dem Zweck, „Handnotizen“ anzubieten. +Es besteht hier kein Anspruch auf Vollständigkeit. +Es werden hier keine offiziellen Lösungen für die Übungsblätter gespeichert. + +Die offiziellen Notizen zum Kurs sowie alle Übungsblätter, (Abgabe)termine, _etc._ +findet man ausschließlich auf der **Moodle**-Seite. + +## (Wesentliche) Struktur des Repositorys ## + +```text +. +├── ./notebooks # Python/Jupyter Notebooks +│ ├── ... +│ └── ... *.ipynb Dateien +├── ./src # Quellcode +│ ├── ... +│ └── ... *.py Dateien +├── ./templates # Templates für Konfiguration +│ ├── ... +│ └── ... *.yaml Dateien +├── ./setup # User Konfiguration +│ ├── ... +│ └── ... *.yaml Dateien +├── ./tests # enthält ggf. automatisiertes Testing +├── ./dist # enthält VERSION infos +├── ./models # erzeugte Modelle +├── ./docs # Dokumentation für erzeugte Modelle +│ +├── justfile +├── README.md +├── LICENSE +├── requirements.txt +└── main.py +``` + +## Gebrauchshinweise ## + +### Systemvoraussetzungen ### + +- [**python 3.10.x**](https://www.python.org/downloads) +- optional: **bash** (für Windowsuser siehe [git for windows](https://gitforwindows.org), was bash mit installiert) +- optional: das [**justfile** Tool](https://github.com/casey/just#installation) (für Windowsuser wird die das [Chocolatey](https://chocolatey.org/install) Tool empfohlen, um dieses Tool installieren zu können) + +### Setup ### + +Für den Gebrauch der Python-Skripte + Notebooks braucht man einige Packages. +Um diese zu installiere führe man +```bash +just build +# oder +just build-requirements +``` +aus. Alternativ kann man +```bash +python3 -m pip install -r --disable-pip-version-check -r requirements.txt +# für Windowsuser: +py -3 -m pip install -r --disable-pip-version-check -r requirements.txt +``` +verwenden. + +### Ausführung ### + +Die Notebooks kann man direkt in dem [**jupyter**](https://jupyter.org) GUI öffnen, +oder in einem bash Terminal führe man +```bash +just notebook xyz +``` +aus, um den Notebook **notebooks/xyz.ipynb** zu starten. +Alternativ, kann man +``` bash +python3 -m jupyter notebook notebooks/xyz.ipynb +# für Windowsuser: +py -3 -m jupyter notebook notebooks/xyz.ipynb +``` +verwenden. diff --git a/dist/VERSION b/dist/VERSION new file mode 100644 index 0000000..77d6f4c --- /dev/null +++ b/dist/VERSION @@ -0,0 +1 @@ +0.0.0 diff --git a/justfile b/justfile new file mode 100644 index 0000000..ba44a40 --- /dev/null +++ b/justfile @@ -0,0 +1,259 @@ +# set shell := [ "bash", "-uc" ] +_default: + @- just --unsorted --list +menu: + @- just --unsorted --choose +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Justfile +# NOTE: Do not change the contents of this file! +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# VARIABLES +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +OS := if os_family() == "windows" { "windows" } else { "linux" } +PYTHON := if os_family() == "windows" { "py -3" } else { "python3" } +GEN_MODELS := "datamodel-codegen" +GEN_MODELS_DOCUMENTATION := "openapi-generator" +PORT := "8000" +URL_API := "http://127.0.0.1:8000/docs" + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Macros +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +_create-file-if-not-exists fname: + @touch "{{fname}}"; + +_create-folder-if-not-exists path: + @if ! [[ -d "{{path}}" ]]; then mkdir -p "{{path}}"; fi + +_delete-if-file-exists fname: + @if [[ -f "{{fname}}" ]]; then rm "{{fname}}"; fi + +_delete-if-folder-exists path: + @if [[ -d "{{path}}" ]]; then rm -rf "{{path}}"; fi + +_clean-all-files path pattern: + @find {{path}} -type f -name "{{pattern}}" -exec basename {} \; 2> /dev/null + @- find {{path}} -type f -name "{{pattern}}" -exec rm {} \; 2> /dev/null + +_clean-all-folders path pattern: + @find {{path}} -type d -name "{{pattern}}" -exec basename {} \; 2> /dev/null + @- find {{path}} -type d -name "{{pattern}}" -exec rm -rf {} \; 2> /dev/null + +_copy-file-if-not-exists path_from path_to: + @- cp -n "{{path_from}}" "{{path_to}}" + +_check-python-tool tool name: + #!/usr/bin/env bash + success=false + {{tool}} --help >> /dev/null 2> /dev/null && success=true; + # NOTE: if exitcode is 251 (= help or print version), then render success. + [[ "$?" == "251" ]] && success=true; + # FAIL tool not installed + if ( $success ); then + echo -e "Tool \x1b[2;3m{{tool}}\x1b[0m installed correctly."; + exit 0; + else + echo -e "Tool \x1b[2;3m{{tool}}\x1b[0m did not work." >> /dev/stderr; + echo -e "Ensure that \x1b[2;3m{{name}}\x1b[0m (-> \x1b[1mjust build\x1b[0m) installed correctly and system paths are set." >> /dev/stderr; + exit 1; + fi + +_generate-models path name: + @{{GEN_MODELS}} \ + --input-file-type openapi \ + --encoding "UTF-8" \ + --disable-timestamp \ + --use-schema-description \ + --allow-population-by-field-name \ + --snake-case-field \ + --strict-nullable \ + --target-python-version 3.10 \ + --input {{path}}/{{name}}-schema.yaml \ + --output {{path}}/generated/{{name}}.py + +_generate-models-documentation path_schema path_docs name: + @{{GEN_MODELS_DOCUMENTATION}} generate \ + --input-spec {{path_schema}}/{{name}}-schema.yaml \ + --generator-name markdown \ + --output "{{path_docs}}/{{name}}" + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# TARGETS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# TARGETS: build +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +build: + @just build-misc + @just build-requirements + @just _check-system-requirements + @just build-models +build-misc: + @# create database if not exists: + @just _create-folder-if-not-exists "data" + @# copy template of configs to setup if not exists: + @just _create-folder-if-not-exists "setup" + @just _copy-file-if-not-exists "templates/template-config.yaml" "setup/config.yaml" + @just _copy-file-if-not-exists "templates/template.env" ".env" +build-requirements: + @{{PYTHON}} -m pip install --disable-pip-version-check -r requirements.txt +build-models: + @echo "Generate data models from schemata." + @just _delete-if-folder-exists "models/generated" + @just _create-folder-if-not-exists "models/generated" + @- #just _generate-models "models" "config" +build-documentation: + @echo "Generate documentations data models from schemata." + @just _delete-if-folder-exists "docs" + @just _create-folder-if-not-exists "docs" + @- #just _generate-models-documentation "models" "docs" "config" + @- just _clean-all-files "." .openapi-generator* + @- just _clean-all-folders "." .openapi-generator* +dist: + @just build + @just build-documentation + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# TARGETS: run +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# runs using commandline interface +run: + @# create config/data folders if missing: + @just build-misc + @# run source code als cli + @{{PYTHON}} main.py + +# runs python notebook (in browser) +notebook name="main": + @# create config/data folders if missing: + @just build-misc + @# run notebook + @{{PYTHON}} -m jupyter notebook notebooks/{{name}}.ipynb + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# TARGETS: tests +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +tests: tests-unit tests-integration +tests-logs: + @just _create-logs + @- just tests + @just _display-logs +tests-unit-logs: + @just _create-logs + @- just tests-unit + @just _display-logs +tests-integration-logs: + @just _create-logs + @- just tests-integration + @just _display-logs +tests-unit: + @{{PYTHON}} -m pytest tests \ + --ignore=tests/integration \ + --cov-reset \ + --cov=. \ + 2> /dev/null +tests-integration: + @{{PYTHON}} -m pytest tests/integration 2> /dev/null + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# TARGETS: qa +# NOTE: use for development only. +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +qa: + @{{PYTHON}} -m coverage report -m +coverage source_path tests_path: + @just _create-logs + @-just _coverage-no-logs "{{source_path}}" "{{tests_path}}" + @just _display-logs +_coverage-no-logs source_path tests_path: + @{{PYTHON}} -m pytest {{tests_path}} \ + --ignore=tests/integration \ + --cov-reset \ + --cov={{source_path}} \ + --capture=tee-sys \ + 2> /dev/null + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# TARGETS: clean +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +clean: + @just clean-basic + @just pre-commit +pre-commit: + @echo "Clean python notebooks." + @{{PYTHON}} -m jupyter nbconvert --clear-output --inplace **/*.ipynb + @- {{PYTHON}} -m jupytext --update-metadata '{"vscode":""}' **/*.ipynb 2> /dev/null + @- {{PYTHON}} -m jupytext --update-metadata '{"vscode":null}' **/*.ipynb 2> /dev/null +clean-basic: + @echo "All system artefacts will be force removed." + @- just _clean-all-files "." ".DS_Store" 2> /dev/null + @echo "All build artefacts will be force removed." + @- just _clean-all-folders "." "__pycache__" 2> /dev/null + @- just _delete-if-folder-exists "models/generated" 2> /dev/null + @echo "All test artefacts will be force removed." + @- just _clean-all-folders "." ".pytest_cache" 2> /dev/null + @- just _delete-if-file-exists ".coverage" 2> /dev/null + @- just _delete-if-folder-exists "logs" 2> /dev/null + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# TARGETS: logging +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +_create-logs: + @# For logging purposes (since stdout is rechanneled): + @just _delete-if-file-exists "logs/debug.log" + @just _create-folder-if-not-exists "logs" + @just _create-file-if-not-exists "logs/debug.log" +_display-logs: + @echo "" + @echo "Content of logs/debug.log:" + @echo "----------------" + @echo "" + @- cat logs/debug.log + @echo "" + @echo "----------------" + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# TARGETS: processes +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +api-process: + #!/usr/bin/env bash + if [[ "{{OS}}" == "linux" ]]; then + ps aux | grep "uvicorn src.api:app" + else + netstat -ano | findstr "uvicorn src.api:app" + fi +kill-api-process: + #!/usr/bin/env bash + # NOTE: only need to do this for linux. + if [[ "{{OS}}" == "linux" ]]; then + echo "Terminating all processes associated to app and port {{PORT}}." + while read pid; do + if [[ "$pid" == "" ]]; then continue; fi + echo "- killing process $pid:" + kill -9 ${pid}; + done <<< $( pgrep -f "uvicorn src.api:app --port {{PORT}}" ) + fi + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# TARGETS: requirements +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +_check-system: + @echo "Operating System detected: {{os_family()}}." + @echo "Python command used: {{PYTHON}}." + +_check-system-requirements: + @just _check-python-tool "{{GEN_MODELS}}" "datamodel-code-generator" + @just _check-python-tool "{{GEN_MODELS_DOCUMENTATION}}" "openapi-code-generator" diff --git a/main.py b/main.py new file mode 100644 index 0000000..c106cd8 --- /dev/null +++ b/main.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +import os; +import sys; + +os.chdir(os.path.dirname(__file__)); +sys.path.insert(0, os.getcwd()); + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# MAIN +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def enter(): + print('Not yet implemented.'); + return; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXECUTION +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +if __name__ == '__main__': + # sys.tracebacklimit = 0; + enter(); diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ebe9152 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,57 @@ +[project] +name = "course-ads-1" +version = "X.Y.Z" +description = "" +authors = [ "gitea.math.uni-leipzig.de/raj_mathe" ] +maintainers = [ "gitea.math.uni-leipzig.de/raj_mathe" ] +license = "MIT" +readme = "README.md" +python = "^3.10" +homepage = "https://gitea.math.uni-leipzig.de/raj_mathe/mfp1-2022" +repository = "https://gitea.math.uni-leipzig.de/raj_mathe/mfp1-2022" +documentation = "https://gitea.math.uni-leipzig.de/raj_mathe/mfp1-2022/src/README.md" +keywords = [ + "python", +] +# cf. https://pypi.org/classifiers +classifiers = [ + "Development Status :: 3 - Alpha", + "Environment :: Console", + "Intended Audience :: Developers", + "Operating System :: Unix", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", +] + +[tool.pytest.ini_options] +minversion = "7.1.1" +testpaths = [ + "tests", +] +python_files = [ + "**/tests_*.py", +] +asyncio_mode = "auto" +filterwarnings = [ + "error", + "ignore::UserWarning", + "ignore::DeprecationWarning", +] +# NOTE: appends (not prepends) flags: +addopts = [ + "--order-dependencies", + "--order-group-scope=module", + "--cache-clear", + "--verbose", + "--maxfail=1", + "-k test_", + # NOTE: will be ignored, if --cov not used (e.g. integration tests): + "--no-cov-on-fail", + "--cov-report=term", + "--cov-config=.coveragerc", + # NOTE: for development purposes only: + # "-s", # verbose print/err capturing disabled + # "--capture=tee-sys", # verbose print/err capturing enabled +] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1f448d0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,49 @@ +pip>=22.2.2 +wheel>=0.37.1 + +# jupyter +ipython>=8.3.0 +jupyter>=1.0.0 + +# running +codetiming>=1.3.0 + +# testing + dev +coverage[toml]>=6.4 +pytest>=7.1.1 +pytest-asyncio>=0.18.3 +pytest-cov>=3.0.0 +pytest-lazy-fixture>=0.6.3 +pytest-order>=1.0.1 +testfixtures>=6.18.5 + +# misc +importlib>=1.0.4 +authlib>=1.0.1 +passlib>=1.7.4 +psutil>=5.9.0 +pathlib>=1.0.1 +lorem>=0.1.1 +safetywrap>=1.5.0 +typing>=3.7.4.3 +nptyping>=2.1.1 +typing-extensions>=3.10.0.2 + +# config +python-dotenv>=0.20.0 +jsonschema>=4.4.0 +lazy-load>=0.8.2 +pyyaml>=5.4.1 +pydantic>=1.9.1 +datamodel-code-generator>=0.12.0 + +# maths +fraction>=2.2.0 +numpy>=1.22.4 +matplotlib>=3.5.1 + +# tables, data frames +pandas>=1.4.2 +tabulate>=0.8.10 +# array-to-latex>=0.82 # <- has issues +qiskit[visualization]>=0.38.0 diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/core/__init__.py b/src/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/core/calls.py b/src/core/calls.py new file mode 100644 index 0000000..9b8dc3f --- /dev/null +++ b/src/core/calls.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from __future__ import annotations; + +from src.thirdparty.code import *; +from src.thirdparty.config import *; +from src.thirdparty.types import *; + +from src.core.utils import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'CallState', + 'GetState', + 'CallValue', + 'CallError', + 'keep_calm_and_carry_on', + 'run_safely', + 'to_async', +]; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# CONSTANTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# local usage only +T = TypeVar('T'); +V = TypeVar('V'); +ARGS = ParamSpec('ARGS'); + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Class State for keeping track of results in the course of a computation +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +@dataclass +class CallState(Generic[V]): + ''' + An auxiliary class which keeps track of the latest state of during calls. + ''' + tag: Option[str] = field(default=None); + result: Optional[V] = field(default=None, repr=False); + timestamp: str = field(default_factory=get_timestamp_string); + has_action: bool = field(default=False); + no_action: bool = field(default=False); + has_error: bool = field(default=False); + values: list[tuple[bool, dict]] = field(default_factory=list, repr=False); + errors: list[str] = field(default_factory=list, repr=False); + + def __copy__(self) -> CallState: + return CallState(**asdict(self)); + + def __add__(self, other) -> CallState: + ''' + Combines states sequentially: + - takes on latest `timestamp`. + - takes on lates `value`. + - takes on `tag` of latest non-null value. + - takes on latest value of `no_action` + - `has_action` = `true` <==> at least one action taken, + unless `no_action` = `true`. + - `has_error` = `true` <==> at least one error occurred. + ''' + if isinstance(other, CallState): + no_action = other.no_action; + has_action = False if no_action else (self.has_action or other.has_action); + return CallState( + tag = other.tag or self.tag, + value = other.value, + timestamp = other.timestamp, + has_action = has_action, + no_action = no_action, + has_error = self.has_error or other.has_error, + values = self.values + other.values, + errors = self.errors + other.errors, + ); + raise Exception('Cannot add states!'); + + ## NOTE: only need __radd__ for additions of form + + def __radd__(self, other) -> CallState: + if other == 0: + return self.__copy__(); + raise Exception('Cannot add a CallState to the right of a non-zero object!'); + + def get_result(self) -> V: + if self.result is not None: + return self.result; + raise Exception('No result set!'); + + @property + def first_data(self) -> dict: + ''' + Returns data in first value collected or else defaults to empty dictionary. + ''' + return self.values[0][1] if len(self.values) > 0 else dict(); + + @property + def data(self) -> list[dict]: + ''' + Returns the data collected. + ''' + return [ data for _, data in self.values ]; + + @property + def data_log(self) -> list[dict]: + ''' + Returns the data to be logged. + ''' + return [ data for log, data in self.values if log == True ]; + + @property + def data_log_json(self) -> list[str]: + ''' + Returns the data to be logged as json. + ''' + return list(map(json.dumps, self.data_log)); + +def GetState(result: Result[CallState, CallState]) -> CallState: + if isinstance(result, Ok): + return result.unwrap(); + return result.unwrap_err(); + +def CallValue( + tag: str = None, + result: Optional[V] = None, + has_action: bool = True, + no_action: bool = False, + value: Option[tuple[bool, dict] | list[tuple[bool, dict]]] = Nothing(), +) -> CallState[V]: + x = []; + if isinstance(value, Some): + x = value.unwrap() or []; + x = x if isinstance(x, list) else [ x ]; + X = CallState(tag=tag, result=result, values=x, has_action=has_action, no_action=no_action, has_error=False); + return X; + +def CallError( + tag: str = None, + has_action: bool = True, + error: Option[str | BaseException | list[str | BaseException]] = Nothing(), +) -> CallState[V]: + x = []; + if isinstance(error, Some): + x = error.unwrap() or []; + x = x if isinstance(x, list) else [ x ]; + x = list(map(str, x)); + return CallState(tag=tag, errors=x, has_action=has_action, has_error=True); + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# DECORATOR - forces methods to run safely +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def run_safely(tag: str | None = None, error_message: str | None = None): + ''' + Creates a decorator for an action to perform it safely. + + @inputs (parameters) + - `tag` - optional string to aid error tracking. + - `error_message` - optional string for an error message. + + ### Example usage ### + ```py + @run_safely(tag='recognise int', error_message='unrecognise string') + def action1(x: str) -> Result[int, CallState]: + return Ok(int(x)); + + assert action1('5') == Ok(5); + result = action1('not a number'); + assert isinstance(result, Err); + err = result.unwrap_err(); + assert isinstance(err, CallState); + assert err.tag == 'recognise int'; + assert err.errors == ['unrecognise string']; + + @run_safely('recognise int') + def action2(x: str) -> Result[int, CallState]: + return Ok(int(x)); + + assert action2('5') == Ok(5); + result = action2('not a number'); + assert isinstance(result, Err); + err = result.unwrap_err(); + assert isinstance(err, CallState); + assert err.tag == 'recognise int'; + assert len(err.errors) == 1; + ``` + NOTE: in the second example, err.errors is a list containing + the stringified Exception generated when calling `int('not a number')`. + ''' + def dec(action: Callable[ARGS, Result[V, CallState]]) -> Callable[ARGS, Result[V, CallState]]: + ''' + Wraps action with return type Result[..., CallState], + so that it is performed safely a promise, + catching any internal exceptions as an Err(...)-component of the Result. + ''' + @wraps(action) + def wrapped_action(*_, **__) -> Result[V, CallState]: + # NOTE: intercept Exceptions first, then flatten: + return Result.of(lambda: action(*_, **__)) \ + .or_else( + lambda err: Err(CallError( + tag = tag or action.__name__, + error = Some(error_message or err), + )) + ) \ + .flatmap(lambda value: value); # necessary to flatten result. + return wrapped_action; + return dec; diff --git a/src/core/env.py b/src/core/env.py new file mode 100644 index 0000000..c3dfcaa --- /dev/null +++ b/src/core/env.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from src.thirdparty.code import *; +from src.thirdparty.config import *; +from src.thirdparty.system import *; +from src.thirdparty.types import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'get_env_string', + 'get_env_optional_string', + 'get_env_int', + 'get_env_optional_int', + 'get_env_float', + 'get_env_optional_float', +]; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# AUXILIARY METHODS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def get_env_value(env: dict, key: str, default: Any = None) -> Any: # pragma: no cover + return env[key] if key in env else default; + +def get_env_string(env: dict, key: str, default: Optional[str] = None) -> str: + result = Result.of(lambda: str(env[key] or default)); + if default is None: + return result.unwrap(); + return result.unwrap_or(default); + +def get_env_optional_string(env: dict, key: str) -> Optional[str]: + result = Result.of(lambda: str(env[key])); + return result.unwrap_or(None); + +def get_env_int(env: dict, key: str, default: Optional[int] = None) -> int: + result = Result.of(lambda: int(env[key] or default)); + if default is None: + return result.unwrap(); + return result.unwrap_or(default); + +def get_env_optional_int(env: dict, key: str) -> Optional[int]: + result = Result.of(lambda: int(env[key])); + return result.unwrap_or(None); + +def get_env_float(env: dict, key: str, default: Optional[float] = None) -> float: + result = Result.of(lambda: float(env[key] or default)); + if default is None: + return result.unwrap(); + return result.unwrap_or(default); + +def get_env_optional_float(env: dict, key: str) -> Optional[float]: + result = Result.of(lambda: float(env[key])); + return result.unwrap_or(None); diff --git a/src/core/log.py b/src/core/log.py new file mode 100644 index 0000000..4484a3d --- /dev/null +++ b/src/core/log.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from src.thirdparty.code import *; +from src.thirdparty.io import *; +from src.thirdparty.log import *; +from src.thirdparty.misc import *; +from src.thirdparty.types import *; + +from src.core.calls import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'LOG_LEVELS', + 'configure_logging', + 'log_info', + 'log_debug', + 'log_warn', + 'log_error', + 'log_fatal', + 'log_result', + 'log_dev', + 'catch_fatal', + 'prompt_user_input', + 'prompt_secure_user_input', + 'prompt_confirmation', +]; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# CONSTANTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LOG_LEVELS(Enum): # pragma: no cover + INFO = logging.INFO; + DEBUG = logging.DEBUG; + +# local usage only +_LOGGING_DEBUG_FILE: str = 'logs/debug.log'; +T = TypeVar('T'); + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# METHODS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def configure_logging(level: LOG_LEVELS): # pragma: no cover + logging.basicConfig( + format = '%(asctime)s [\x1b[1m%(levelname)s\x1b[0m] %(message)s', + level = level.value, + datefmt = r'%Y-%m-%d %H:%M:%S', + ); + return; + +def log_debug(*messages: Any): + logging.debug(*messages); + +def log_info(*messages: Any): + logging.info(*messages); + +def log_warn(*messages: Any): + logging.warning(*messages); + +def log_error(*messages: Any): + logging.error(*messages); + +def log_fatal(*messages: Any): + logging.fatal(*messages); + exit(1); + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Special Methods +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Intercept fatal errors +def catch_fatal(method: Callable[[], T]) -> T: + try: + return method(); + except Exception as e: + log_fatal(e); + +def log_result(result: Result[CallState, CallState], debug: bool = False): + ''' + Logs safely encapsulated result of call as either debug/info or error. + + @inputs + - `result` - the result of the call. + - `debug = False` (default) - if the result is okay, will be logged as an INFO message. + - `debug = True` - if the result is okay, will be logged as a DEBUG message. + ''' + if isinstance(result, Ok): + value = result.unwrap(); + log_debug(value); + for x in value.data_log: + log_info(x); + else: + err = result.unwrap_err(); + log_debug(err); + for e in err.errors: + log_error(e); + return; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# DEBUG LOGGING FOR DEVELOPMENT +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def log_dev(*messages: Any): # pragma: no cover + with open(_LOGGING_DEBUG_FILE, 'a') as fp: + print(*messages, file=fp); + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# User Input +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def prompt_user_input(message: str, expectedformat: Callable) -> Optional[str]: + answer = None; + while True: + try: + answer = input(f'{message}: '); + ## Capture Meta+C: + except KeyboardInterrupt: + print(''); + return None; + ## Capture Meta+D: + except EOFError: + print(''); + return None; + except: + continue; + if expectedformat(answer): + break; + return answer; + +def prompt_secure_user_input(message: str, expectedformat: Callable) -> Optional[str]: + answer = None; + while True: + try: + ## TODO: zeige **** ohne Zeilenumbruch an. + answer = getpass(f'{message}: ', stream=None); + ## Capture Meta+C: + except KeyboardInterrupt: + print(''); + return None; + ## Capture Meta+D: + except EOFError: + print(''); + return None; + except: + continue; + if expectedformat(answer): + break; + return answer; + +def prompt_confirmation(message: str, default: bool = False) -> bool: + answer = prompt_user_input(message, lambda x: not not re.match(r'^(y|yes|j|ja|n|no|nein)$', x)); + if isinstance(answer, str): + return True if re.match(r'^(y|yes|j|ja)$', answer) else False; + return default; diff --git a/src/core/utils.py b/src/core/utils.py new file mode 100644 index 0000000..35061e4 --- /dev/null +++ b/src/core/utils.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from src.thirdparty.code import *; +from src.thirdparty.misc import *; +from src.thirdparty.types import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'flatten', + 'get_timestamp_string', +]; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# METHODS - date, time +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def get_timestamp() -> datetime: # pragma: no cover + return datetime.now(); + +def get_timestamp_string() -> str: # pragma: no cover + return str(get_timestamp()); + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# METHODS arrays +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def flatten(X: list[list[Any]]) -> list[Any]: + return list(itertools_chain.from_iterable(X)); diff --git a/src/thirdparty/__init__.py b/src/thirdparty/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/thirdparty/code.py b/src/thirdparty/code.py new file mode 100644 index 0000000..1650362 --- /dev/null +++ b/src/thirdparty/code.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from functools import partial; +from functools import reduce; +from functools import wraps; +from dataclasses import Field; +from dataclasses import MISSING; +from dataclasses import asdict; +from dataclasses import dataclass; +from dataclasses import field; +from itertools import chain as itertools_chain; +from itertools import product as itertools_product; +from operator import itemgetter; +from pydantic import BaseModel; +# cf. https://github.com/mplanchard/safetywrap +from safetywrap import Ok; +from safetywrap import Err; +from safetywrap import Nothing; +from safetywrap import Result; +from safetywrap import Option; +from safetywrap import Some; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'partial', + 'reduce', + 'wraps', + 'asdict', + 'dataclass', + 'field', + 'Field', + 'MISSING', + 'itertools_chain', + 'itertools_product', + 'itemgetter', + 'BaseModel', + 'Err', + 'Nothing', + 'Ok', + 'Option', + 'Result', + 'Some', +]; diff --git a/src/thirdparty/config.py b/src/thirdparty/config.py new file mode 100644 index 0000000..214b216 --- /dev/null +++ b/src/thirdparty/config.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from dotenv import load_dotenv; +from dotenv import dotenv_values; +import json; +import jsonschema; +from lazy_load import lazy; +from yaml import add_constructor; +from yaml import load as yaml_load; +from yaml import FullLoader as yaml_FullLoader; +from yaml import add_path_resolver as yaml_add_path_resolver; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'load_dotenv', + 'dotenv_values', + 'json', + 'jsonschema', + 'lazy', + 'add_constructor', + 'yaml_load', + 'yaml_FullLoader', + 'yaml_add_path_resolver', +]; diff --git a/src/thirdparty/io.py b/src/thirdparty/io.py new file mode 100644 index 0000000..34d0292 --- /dev/null +++ b/src/thirdparty/io.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from getpass import getpass; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'getpass', +]; diff --git a/src/thirdparty/log.py b/src/thirdparty/log.py new file mode 100644 index 0000000..03d159d --- /dev/null +++ b/src/thirdparty/log.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +import logging; +from logging import LogRecord; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'logging', + 'LogRecord', +]; diff --git a/src/thirdparty/maths.py b/src/thirdparty/maths.py new file mode 100644 index 0000000..b7fbf06 --- /dev/null +++ b/src/thirdparty/maths.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from fractions import Fraction; +import math; +import numpy as np; +import random; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'Fraction', + 'math', + 'np', + 'random', +]; diff --git a/src/thirdparty/misc.py b/src/thirdparty/misc.py new file mode 100644 index 0000000..af351b7 --- /dev/null +++ b/src/thirdparty/misc.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from datetime import datetime; +from datetime import timedelta; +import lorem; +import re; +from textwrap import dedent; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'lorem', + 're', + 'datetime', + 'timedelta', + 'dedent', +]; diff --git a/src/thirdparty/render.py b/src/thirdparty/render.py new file mode 100644 index 0000000..e41c841 --- /dev/null +++ b/src/thirdparty/render.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from IPython.display import Latex; +from IPython.display import display_latex; +from IPython.display import display_png; +from IPython.display import display_markdown; +from IPython.display import display; +# from array_to_latex import to_ltx as array_to_latex; # <- has issues +from qiskit.visualization import array_to_latex; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'Latex', + 'display', + 'display_latex', + 'display_png', + 'display_markdown', + 'array_to_latex', +]; diff --git a/src/thirdparty/run.py b/src/thirdparty/run.py new file mode 100644 index 0000000..f6a7081 --- /dev/null +++ b/src/thirdparty/run.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from codetiming import Timer; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'Timer', +]; diff --git a/src/thirdparty/system.py b/src/thirdparty/system.py new file mode 100644 index 0000000..75bc198 --- /dev/null +++ b/src/thirdparty/system.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +import os; +from pathlib import Path; +import sys; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'os', + 'Path', + 'sys', +]; diff --git a/src/thirdparty/types.py b/src/thirdparty/types.py new file mode 100644 index 0000000..cfaf601 --- /dev/null +++ b/src/thirdparty/types.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from enum import Enum; +from io import BytesIO; +from nptyping import NDArray; +from nptyping import Shape; +from nptyping import Bool; +from nptyping import UInt; +from nptyping import UInt8; +from nptyping import UInt32; +from nptyping import UInt64; +from nptyping import Int; +from nptyping import Int32; +from nptyping import Int64; +from nptyping import Float; +from nptyping import Float32; +from nptyping import Float64; +from numpy import uint8; +from numpy import int32; +from numpy import int64; +from numpy import float32; +from numpy import float64; +from numpy import complex64; +from numpy import complex128; +from pydantic import conint; +from typing import Any; +from typing import Awaitable; +from typing import Callable; +from typing import ClassVar; +from typing import Coroutine; +from typing import Generator; +from typing import Generic; +from typing import Optional; +from typing import Type; +from typing import TypeVar; +from typing_extensions import Concatenate; +from typing_extensions import ParamSpec; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'Enum', + 'BytesIO', + 'NDArray', + 'Shape', + 'Bool', + 'UInt', + 'UInt8', + 'UInt32', + 'UInt64', + 'Int', + 'Int32', + 'Int64', + 'Float', + 'Float32', + 'Float64', + 'uint8', + 'int32', + 'int64', + 'float32', + 'float64', + 'complex64', + 'complex128', + 'conint', + 'Any', + 'Awaitable', + 'Callable', + 'ClassVar', + 'Coroutine', + 'Generator', + 'Generic', + 'Optional', + 'Type', + 'TypeVar', + 'Concatenate', + 'ParamSpec', +]; diff --git a/templates/template-config.yaml b/templates/template-config.yaml new file mode 100644 index 0000000..e69de29 diff --git a/templates/template.env b/templates/template.env new file mode 100644 index 0000000..e69de29 diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..e01a800 --- /dev/null +++ b/tests/README.md @@ -0,0 +1 @@ +# Tests #