diff docs/schema.txt @ 5:84dfd1a94926

Add the existing implementation. All tests work. The documentation as text file is included also.
author Franz Glasner <fzglas.hg@dom66.de>
date Thu, 06 Jul 2023 23:41:41 +0200
parents
children c3a0fe8d4587
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/schema.txt	Thu Jul 06 23:41:41 2023 +0200
@@ -0,0 +1,542 @@
+.. -*- coding: utf-8; mode: rst; indent-tabs-mode: nil -*-
+
+========
+ Schema
+========
+
+Grundideen
+==========
+
+- Angelehnt an JSON Schema
+- Deklarativ in YAML
+- Verwendung von erweiterten YAML-Features:
+
+  + laden von (beliebigen) Python-Objekten
+  + Benutzung von YAML-Referenzen
+
+- Möglichkeit der direkten Verwendung von Python-Callables
+  Diese müssen Iteratoren sein und jedes Problem `yield`en.
+- Rückgabe einer Liste von Problemen: Versuch möglichst viele Probleme auf
+  einen Schlag zu melden (soweit möglich und sinnvoll)
+
+.. seealso:: - https://json-schema.org/understanding-json-schema/index.html
+             - http://rx.codesimply.com/coretypes.html
+
+
+Extra Keywords für :py:func:`validate`
+======================================
+
+- ``skip_keys``
+
+  Eine Liste Strings oder von compilierten REs
+
+  Ein String-Item wird auf Gleichheit getestet, die RE per :py:meth:`search`
+  -- und zwar auf den Dict-key
+
+  Bei Treffer wird dieser Key komplett ignoriert. Das ist also eine globale
+  Ignore-Liste für Dict-Keys.
+
+
+Typen
+=====
+
+Durch ``type`` (required) gekennzeichnet
+
+
+Alle Schemata außer den `Schema-Kombinatoren`_ haben auch ein optionales
+Attribut ``index-constraint``. 
+
+  Dessen Wert ist eine Liste von Indizes, an denen das Element in
+  seinem Parent-Container (Liste, sorted dict) vorkommen darf.
+
+
+dict / map / object
+-------------------
+
+- ``nullable``
+
+  bool (Default: False): instead of an empty dict allow also a None/null/nil
+
+- ``keys``
+
+  `dict` mit Keys und den Values als zugeordnete Schemata für die Values
+  des Dicts
+
+- ``keyNames``
+
+  Wenn vorhanden: ein Schema, dem die *Keys* -- auch die `additionalKeys` --
+  folgen müssen.
+
+  Default: entspricht ``{"type": "string"}``
+
+- ``additionalKeys``
+
+  * bool
+
+      `False`
+         nicht erlaubt (default)
+
+         Globales ``skip_keys`` wird aber zusätzlich noch in Betracht
+         gezogen.
+
+      `True`
+         erlaubt -- keine weitergehende Schema-Prüfung der Inhalte
+
+         Globales ``skip_keys`` ist offensichtlich irrelevant.
+
+  * Schema
+
+    Prüfung erfolgt nach gegebenem Schema
+
+    Globales ``skip_keys`` wird aber zusätzlich noch in Betracht gezogen.
+
+- ``required``
+
+  Liste von Strings mit Key-Namen, die vorkommen müssen
+
+- ``maxLength``
+- ``minLength``
+
+
+list / array
+------------
+
+- ``nullable``
+
+  bool (Default: False): instead of an empty list allow also a None/null/nil
+
+- ``items``
+
+  Ein Schema für *alle* Items.
+
+- ``maxLength``
+- ``minLength``
+
+
+set / frozenset
+---------------
+
+- ``nullable``
+
+  bool (Default: False): instead of an empty set allow also a None/null/nil
+
+- ``items``
+
+  Ein Schema für *alle* Items
+
+- ``maxLength``
+- ``minLength``
+
+
+tuple / record
+--------------
+
+- ``nullable``
+
+  bool (Default: False): instead of an empty list or tuple allow also
+  a None/null/nil
+
+- ``items``
+
+  Eine Liste: je ein spezielles Schema *pro Item*
+
+- ``additionalItems``
+
+  * bool
+
+      `False`
+         nicht erlaubt (default)
+
+      `True`
+         erlaubt -- keine weitergehende Schema-Prüfung der Inhalte
+
+  * Schema
+
+    Prüfung der "zusätzlichen" Items erfolgt nach gegebenem Schema
+
+- ``maxLength``
+- ``minLength``
+
+
+string / str
+------------
+
+- ``nullable``
+
+  bool (Default: False): instead of an empty string allow also a None/null/nil
+
+- ``enum``
+
+  Eine Liste von Strings, von denen genau einer dem String entspricht
+
+  Achtung: Alle anderen Prüfungen (siehe unten) werden trotzdem auch
+           durchgeführt.
+
+- ``is-contained-in-ref``
+
+  The string's value must be contained in (Python ``in``) in the referenced
+  object (see `Referenzen`_).
+
+- ``maxLength``
+- ``minLength``
+- ``pattern``
+
+  * string
+
+    RE of the accepted pattern
+
+  * compiled RE
+
+    compiled RE of the accepted pattern
+
+  * Callable
+
+
+binary
+------
+
+- ``maxLength``
+- ``minLength``
+- ``pattern``
+
+  * string
+
+    RE of the accepted pattern. The YAML unicode string value will be
+    converted to a byte-string with :func:`ast.literal_eval` as if it
+    is surrounded by ``b'''<re>'''`` or ``b"""<re>"""``. If the pattern
+    contains both a ``'''`` or ``"""`` substring the conversion will fail.
+
+  * bytes, bytearray
+
+    RE of the accepted pattern
+
+  * compiled RE
+
+    compiled RE of the accepted pattern
+
+  * Callable
+
+
+bool / boolean
+--------------
+
+Only **real** boolean values: ``true`` and ``false``
+
+- ``value``
+
+  The accepted value or a validating callable
+
+- ``nullable``
+
+  bool (Default: False): instead of a boolean allow also a None/null/nil
+
+
+timestamp / datetime
+--------------------
+
+Only :py:class:`datetime.datetime` allowed
+
+- ``value``
+
+  Callable that validates the value of a timestamp
+
+
+Callable
+--------
+
+Iterator (e.g. ``yield``) mit Signatur: :py:func:`callable(object, schema, context)`
+
+
+accept
+------
+
+Validates successfully always: accept everything
+
+
+deny
+----
+
+Does not validate successfully: always yield the error code 10010
+
+
+:py:obj:`None` / none / null / nil
+----------------------------------
+
+Only the `None` object validates
+
+
+empty
+-----
+
+Erlaubt sind: None, leeres Dict, leere Liste, leeres Set/Frozenset
+
+.. note:: Leere Strings sind **nicht** erlaubt.
+
+
+integer / int
+-------------
+
+- ``nullable``
+
+  bool (Default: False): allow also a None/null/nil
+
+- ``minValue``
+- ``maxValue``
+- ``value``
+
+  A callable to validate the integer value
+
+- ``enum``
+
+  Eine Liste von ganzen Zahlen, von denen genau einer dem vorhandenen
+  Wert entsprechen muß.
+
+  Achtung: Alle anderen Prüfungen (`minValue`, `maxValue`, `value`)
+           werden trotzdem auch durchgeführt.
+
+
+real / double / float
+---------------------
+
+- ``nullable``
+
+  bool (Default: False): allow also a None/null/nil
+
+- ``minValue``
+- ``maxValue``
+- ``value``
+
+  A callable to validate the float value
+
+
+number / num
+------------
+
+- ``nullable``
+
+  bool (Default: False): allow also a None/null/nil
+
+Any numeric value (int or float)
+
+- ``minValue``
+- ``maxValue``
+- ``value``
+
+  A callable to validate the number
+
+- ``enum``
+
+  Eine Liste von Zahlen, von denen genau einer dem vorhandenen
+  Wert entsprechen muß.
+
+  Achtung: Alle anderen Prüfungen (`minValue`, `maxValue`, `value`)
+           werden trotzdem auch durchgeführt.
+
+
+scalar
+------
+
+Any scalar value: no `None`, no `dict`, no `tuple`, no `list`, no `set`,
+no `frozenset`.
+
+But if
+
+- ``nullable``
+
+  bool (Default: False): None/null/nil is allowed also
+
+
+Schema-Kombinatoren
+-------------------
+
+- ``all-of``
+
+  alle in der gegebenen Liste müssen validieren
+
+- ``any-of``
+
+  mindestens einer muß validieren
+
+    Nach den ersten erfolgreichen Test werden alle weiteren Sub-Tests
+    abgebrochen (aka. short-circuit Verhalten).
+
+- ``one-of``
+
+  **genau einer** aus der Liste muß validieren (aka. xor)
+
+- ``not``
+
+  das folgende Schema darf nicht successful validieren
+
+
+Bedingungen
+===========
+
+``cond``-Key im Schema:
+
+  Lisp-like `cond`:
+
+  - eine Liste von Wenn-Dann-Paaren
+
+    Bedingung: ``when``, ``when-ref-true``, ``when-ref-exists``
+
+    Dann: ``then``, ``then-merge``
+
+    Für ``when``:
+
+      Logische Operatoren:
+
+        ``not``
+
+        ``all-of`` (aka `and`)
+
+        ``any-of`` (aka `or`)
+
+        ``one-of`` (aka `xor`)
+
+      Prädikate:
+
+        ``ref-true``, ``ref-exists``, ein Objekt im boolschen Kontext
+
+      Vergleichs-Operator:
+
+        ``equals`` gefolgt von einer Liste der Länge zwei als Gleichheits-
+        Operator:
+
+            Mögliche Keys:
+
+              ``ref``: eine Referenz
+
+              ``value`` oder ``val`` ein Wert
+
+        z.B. in YAML::
+
+            equals:
+              - ref: object:#my.key
+              - value: "a string value"
+
+    ``when-ref-true`` und ``when-ref-exists`` sind einfache Abkürzungen für::
+
+         when:
+           ref-true:   ...
+
+    bzw::
+
+         when:
+           ref-exists:  ...
+
+  - die *erste* zutreffende Bedingung bestimmt via seinem "Dann" ein Schema
+
+      ``then``
+
+         Keys im Then-Schema *ersetzen* korrespondierende Keys im Parent-Schema
+
+      ``then-merge``
+
+         Then-Merge-Schema wird in das Parent-Schema *eingemischt*
+
+  - das ganze erfolgt rekursiv
+
+  - falls keine der Bedingungen zutrifft wird nichts ausgeführt/geändert
+
+  - ``when`` -- direkt gefolgt von einer Liste -- ist eine Abkürzung für
+    ``all-of` und eben dieser Liste::
+
+        cond:
+          when:
+            - test1
+            - test2
+            - test3
+
+    ist äquivalent zu::
+
+        cond:
+          when:
+            all-of:
+              - test1
+              - test2
+              - test3
+
+.. important:: Schema-Referenzen werden **vor** dem Replace/Merge jeweils
+               aufgelöst!
+
+``match`` entspricht ``cond`` -- mit dem Unterschied, daß statt der *ersten*
+wahren Bedingung **alle** wahren Bedingungen ausgeführt werden;
+
+   erst werden alle Schemata, die aus wahren Bedingungen kommen gesammelt,
+   danach werden die Schemata ersetzt bzw. gemerged.
+
+Beispiel::
+
+  required:
+    - a
+    - b
+  cond:
+    - when:
+        all-of:
+          - not:
+              ref-true: 'object:#p1.p2.p3'
+          - ref-exists: '#p4.p5'
+      then:
+        required: ["foo", "bar"]    # replace existing `required'
+    - when:
+        ref-true: 'object:#p6.p7'
+      then:
+        new-key: "new-val"          # a new key to the containing dict
+      then-merge:
+        required: ["c", "d"]     # add `c' and `d' to `a' and `b'
+    - when: true            # als letzer Fall: "else"
+      then-replace:
+        required: ["something", "else"]    # replace existing `required'
+
+
+Referenzen
+==========
+
+URI-Syntax
+
+  Angepaßte und simplifizierte JSON-Pointer-Syntax (:rfc:`6901`)
+
+Beispiele:
+
+  - ``object:#wsgi.china_detector.enabled``
+
+    ist (weil `object` das Default-URI-Schema ist) äquivalent zu:
+
+    ``#wsgi.china_detector.enabled``
+
+    Das ist eine **absolute** Referenz.
+
+    ``.`` ist also -- wie in :py:mod:`configmix` -- der Hierarchie-Separator
+    für URI-Fragmente in Objekt-Referenzen
+
+  - ``object:#`` ist das Root-Objekt
+
+  - ``object:#.`` ist das current Kontext-Object (aka "Base")
+
+  - ``object:`` ist *ungültig*
+
+    Ein Fragment **muß** also formal vorhanden sein -- auch wenn es leer ist.
+
+  - Relative Referenzen *starten* mit einen Punkt (analog Python-Imports)
+
+    Mehrere führende Punkte sind -- wie bei Python-Imports -- relative 
+    Referenzen zu Parent-Objekten. Der Versuch, den Parent des Root-Objektes
+    anzusprechen, liefert einen :py:exc:`TypeError`.
+
+Wo ein Schema erlaubt ist, ist auch ein dict mit dem einzigen Key ``$ref``
+erlaubt. Dies ist eine Referenz auf ein anderes Schema mit dem URI-Schema
+``schema:``. Dieses andere Schema kann auch aus einer anderen Datei kommen:
+
+  - ``schema:$root#/``
+
+    Das Root-Element des Root-Schemas
+
+  - ``schema:$self#/``
+
+    Das Root-Element des gerade aktiven Schemas.
+
+  - ``schema:data:schemalib:file.schema.yml#/foo``
+
+    Das ``foo``-Element des via Packagedata von `schemalib` geladenen Schemas
+    `file.schema.yml`. Das ist dann auch das neue aktive Schema.