Skip to content

Instantly share code, notes, and snippets.

@phorward
Last active January 28, 2026 21:56
Show Gist options
  • Select an option

  • Save phorward/fd6cfc379867d26437167b83f15967e6 to your computer and use it in GitHub Desktop.

Select an option

Save phorward/fd6cfc379867d26437167b83f15967e6 to your computer and use it in GitHub Desktop.
ViUR-core Whats new?

Was ist neu in viur-core 3.8?

Dieses Dokument beschreibt die größten Änderungen im Bezug auf viur-core 3.8.

Allgemeines

viur-core 3.8 ist nur noch mit Python 3.12 und Python 3.13 kompatibel. Alle Versionen darunter werden nicht mehr unterstützt. Die Dokumentation wird jetzt ebenfalls mit dem aktuellstem Sphinx unter Python 3.13 erzeugt.

In viur-core 3.8 sind die Kompatiblitätsflags für alte Admin und Vi-Versionen standardmäßig deaktiviert, so dass ausschließlich die neuen Formate verwendet werden.

Die folgenden Zeilen können in Projekten die auf viur-core 3.8 portiert werden nun komplett entfernt werden:

# Diese Zeilen können weg:
conf.compatibility.remove("json.bone.structure.keytuples")
conf.compatibility.remove("json.bone.structure.camelcasenames")
conf.compatibility.remove("bone.select.structure.values.keytuple")
conf.compatibility.remove("json.bone.structure.inlists")

Außerdem läuft die conf jetzt im strict-mode, d.h. die Kompatiblität zu alten conf-Zugriffen wie z.B. conf["mainApp"] ist nicht mehr erlaubt.

In den Renderern wurden die Module render.json.user und render.vi.user komplett und ersatzlos entfernt. Referenzen auf diese sollten in alten Projekten entfernt werden. Es wird jetzt nur noch die jeweilige default-Klasse für alle Renderer benutzt.

Internationalisierung (i18n)

Sämtliche Bone validierungen liefern jetzt übersetzte Fehlermeldungen (war vorher hart auf English).

Über das Flag conf.i18n.auto_translate_bones kann nun global eingestellt werden, ob descr und params.category bei Bones automatisch übersetzt werden sollen oder nicht (default: True, wie vorher).

Datenbank

db-Modul ist wieder in viur-core

Das db-Modul, die ViUR-Abstraktion für die low-level Cloud Datastore Datenbankzuriffe, ist jetzt wieder Bestandteil von viur-core selbst.

Das Modul wurde in viur-core 3.1 in ein eigenes Paket viur-datastore ausgegliedert. Dies geschah zum einen wegen einer damals besseren Performance, zum anderen auch um andere Datenbanken anzubinden - was aber nie passierte. Schlussendlich führte diese Splittung zu größeren Problemen und Inkonsistenzen in der Weiterentwicklung, und die Google-eigene Implementation der Datastore-API ist inzwischen auch wieder schneller.

Die re-integrierte db-API enthält die bekannten Funktionen wie db.put, db.get, db.delete usw. Diese werden jetzt aber PEP-8 konform in Snake-Case geschrieben, so dass die Benutzung der alten Funktionen eine Deprecation-Warning erzeugt.

Die Konfiguration von db-spezifischen Einstellungen erfolgt über conf.db.

Query-Limits

Important

Ab viur-core 3.8 muss nicht mehr bei externen Aufrufen von Listen-Modulen das explizite /example/list?limit=99 übergeben werden um mehr als 30 Datensätze zu erhalten. Das geht jetzt besser, bitte nutzt die neuen Möglichkeiten!

Hier wurde von ViUR immer erwartet das weitere Datensätze per Cursor nachgeladen werden, was bei Jinja-Templates nur mit JavaScript und ziemlich bekloppt lösbar wäre. Diese Limits (30 und 100) waren bisher hard-codiert.

Dies kann jetzt über folgende conf-Variablen global und individuell im Projekt modifiziert werden:

  • conf.db.query_default_limit (default: 30) ist das Standard-Limit für Queries.
  • conf.db.query_external_limit (default: 100) ist das Limit für Queries, welches über externe Aufrue maximal gesetzt werden darf. Dies verhindert, dass man als Benutzer eines ViUR-Systems von außen ein Limit von 30000 setzen kann, obwohl nur conf.db.query_default_limit = 100 gesetzt wurde.

Ein Projekt, das in der main.py z.B. mit

conf.db.query_default_limit = 200

konfiguriert wird, holt bei allen Abfragen also immer bis zu 200 Einträge, also kann man sich das ?limit=99 sparen, wenn man z.B. 120 Datensätze erwartet, und "die maximal mögliche Anzahl" haben möchte, weil das interne Limit schon auf 200 gesetzt wurde.

Skeleton-API

Die Skeleton-API (ehem. skeleton.py) wurde jetzt in ein Modul mit mehreren Dateien aufgesplittet.

viur-relations und RelationalConsistency

Ferner wurde das gesamte viur-relations-Handling um die Funktion update_relations() komplett refactored. So werden auch RelationalBones in RecordBones mit aktualisiert. Auch RelationalConsistency-Konsistenzprüfungen werden jetzt über update_relations() behandelt - dies wurde vorher durch die Funktion processRemovedRelations gelöst, die durch das Refactoring ersetzt und obsolet wurde.

RelationalBone.fromClient() refactored

RelationalBone.fromClient() wurde auch dahingehend refactored, dass RelationalConsistency-Einstellungen akzeptiert werden und allgemein grottiger Code entfernt. Dies verhindert, dass im Fall einer gesetzten RelationalConsistency das Speichern von Relationen auf nicht mehr vorhandene Datensätze unterbunden wird. Ist keine RelationalConsistency gesetzt, verhält sich das Bone genau wie zuvor.

RefSkel.read()

Man kann jetzt direkt das Target-Skeleton eines RelationalBones mittels RefSkel.read() auslesen.

# User lesen
user_skel = UserSkel()
assert user_skel.read(key)

# Firma zum User auslesen
company_skel = user_skel["company"]["dest"].read()  # gesamtes CompanySkel (ging schon in v3.7)

# ... man kann auch (dynamische) SubSkels erzeugen (nur ab v3.8):
company_skel = user_skel["company"]["dest"].read(subskel=("select",))  # in subSkels definiertes SubSkel
company_skel = user_skel["company"]["dest"].read(bones=("name", "city",))  # dynamisches SubSkel

Skeleton.readonly()

Alle Bones eines Skeletons auf readOnly=True setzen, z.B. für ActionSkels:

channel_skel.readonly()

Skeleton.dump()

Eine JSON-serialisierbare Repräsentation direkt aus einem Skeleton erzeugen:

channel_skel.dump()

Früher musste man dafür sowas beklopptes machen:

from viur.core.render.json.default import DefaultRender as JsonRender
#...
JsonRender().renderSkelValues(channel_skel)

Bones

searchable=True für RawBone

RawBones können jetzt searchable=True sein. Außerdem wurde die Extrahierung der Suchbegriffe auf reguläre Ausdrücke umgestellt und zwischen String-, Text- und RawBone generalisiert.

ImageBone

Das ImageBone sollte allgemein für Bilder benutzt werden, und wurde für die Barrierefreiheit sowie allgemein zur klaren Definition eines Bildes in den Daten eingeführt.

Es ist konfiguriert als

class ImageBone(FileBone):
    type = FileBone.type + ".image"

    def __init__(
        self,
        *,
        public: bool = True,
        using: t.Optional[RelSkel] = ImageBoneRelSkel,
        validMimeTypes: None | t.Iterable[str] = ["image/*"],
        **kwargs,
    ):
        super().__init__(
            public=public,
            using=using,
            validMimeTypes=validMimeTypes,
            **kwargs,
        )

Das ImageBoneRelSkel enthält übersetzte Felder für Alt-Texte.

image

Module.skel()

Generell gibt es jetzt für Module die Funktion skel(). Diese ist äquivalent zu baseSkel(), nur kürzer. Sie wird in viur-core>=4 noch eine größere Rolle spielen.

assert (skel := self.skel().read(key))  # alles da! :-D

File-Modul

Die Funktion File.write() erlaubt nun die optionale Angabe eines Repository und eines Pfades. Dadurch können Dateien, die vom ViUR System geschrieben werden, direkt auch in ein Verzeichnis im File-Modul geschrieben werden. Das Verzeichnis wird automatisch angelegt, wenn es nicht existiert.

conf.main_app.file.write(
    f"test-{utils.utcNow().strftime("%d-%m-%Y_%H-%M-%S")}.txt",
    b"Hello World",
    folder=("Meine Textdateien", str(utils.utcNow().year))
)
image

History-Modul

Es gibt jetzt erstmals ein ViUR-Standard-History-Modul. Dieses besteht aus den Komponenten History (ein List-Modul) und dem HistoryAdapter.

Das History-Modul schreibt per Default in das ViUR System, kann aber auch optional nach BigQuery schreiben. Dies wird über die History-Config konfiguriert.

Um das Modul zu verwenden muss es einmal im System (so wie File) gesubclassed werden:

from viur.core.modules.history import History

class History(History):
    adminInfo = History.adminInfo | {
        "moduleGroup": "system",  # hier wird z.B. das Modul in eine andere Module-Gruppe gelegt
    }

Ein Skeleton, welches History-Einträge erzeugen soll, muss so konfiguriert werden:

from viur.core.skeleton import Skeleton, ViurTagsSearchAdapter
from viur.core.bones import *
from viur.core.modules.history import HistoryAdapter


class ExampleSkel(Skeleton):
    database_adapters = (
        ViurTagsSearchAdapter(),
        HistoryAdapter(),
    )

Über den HistoryAdapter können weitere Einstellungen erfolgen, z.B. welche Bones ingoriert werden sollen.

User-Modul

is_admin()-Funktion

Die is_admin()-Funktion des User-Moduls definiert, welche User als Administrator gelten. Dies kann projektspezifisch definiert werden. Bisherige Prüfungen der Art if "root" in user["access"]: sollten durch if conf.main_app.user.is_admin(user): ersetzt werden.

Prüfung auf "root" in user["access"] sollten wirklich nur für Fälle benutzt werden, wo wirklich Super-User-Rechte notwendig sind (z.B. Maintenance-Funktionen).

Login-Prozess auf ActionSkels umgestellt

Der Login-Prozess im User-Modul wurde dahingehend verändert, dass jetzt ActionSkels ausgegeben werden, um den User mit einer bestimmten Login-Methode durch den Login-Prozess zu führen. Dies wird im Admin umgesetzt und demnächst für mehrere Projekte relevant sein.

Important

Dies ist ein kleiner Breaking-Change, der aber nur in speziellen Projekten zu einem Problem führen könnte, da die Funktion /vi/user/login jetzt entweder ein ActionSkel anzeigt, mit den möglichen Login-Methoden (wenn es mehrere gibt), oder auf die einzige existieren Login-Methode weiterleitet. Die Funktion mit dem krankhaft wunderbescheuerten Namen /vi/user/getAuthMethods, die vorher genau das gleiche ausgab wie /vi/user/login, ist damit deprecated und fliegt bald raus.

User.adminInfo ist jetzt eine Funktion

Man kann nicht mehr - wie bisher - sowas machen:

from viur.core.modules.user import User

class User(User):
    adminInfo = User.adminInfo | {
        # ...weitere Settings...
    }

sondern man muss jetzt explizit

from viur.core.modules.user import User

class User(User):
    def adminInfo(self):
        return super().adminInfo() | {
        # ...weitere Settings...
    }

machen.

Important

Dies ist ein kleiner Breaking-Change, der in einigen Projekten dazu führen könnte das ein Fehler beim Projektstart auftritt. Bitte entsprechend wie oben korrigieren.

SkeletonMaintenanceTask

Der Server-Task rebuildSearchIndex wurde entfernt, und durch SkeletonMaintenanceTask ersetzt.

Mit diesem Task können ausschließlich 'root'-Benutzer ein oder mehrere Kinds

  • Refreshen (ehemals rebuildSearchIndex)
  • Löschen
  • Zählen

Es kann eine Filterung vorgenommen werden, die die Datenselektion vorfiltert (normales Filtering).

Außerdem kann mit Hilfe von Logics ein programmierter Bedingungsausdruck definiert werden, der für jeden Datensatz angewendet wird und diesen refreshed, löscht oder zählt.

image

Important

Die Condition False # fused: by default, doesn't affect anything. die immer voreingestellt ist, ist wie eine Sicherheitsabfrage. Wird diese so gelassen, dann wird KEIN Datensatz refreshed, gelöscht oder gezählt. Daher muss man explizt True oder etwas reglementierendes dort hineinschreiben.

What's new in viur-core 3.8?

This document describes the most significant changes in viur-core 3.8.

General

viur-core 3.8 is now only compatible with Python 3.12 and Python 3.13. All versions below these are no longer supported. The documentation is now also generated with the latest Sphinx under Python 3.13.

In viur-core 3.8, the compatibility flags for old Admin and Vi versions are disabled by default, so that only the new formats are used.

The following lines can now be completely removed from projects ported to viur-core 3.8:

# These lines can be removed:
conf.compatibility.remove("json.bone.structure.keytuples")
conf.compatibility.remove("json.bone.structure.camelcasenames")
conf.compatibility.remove("bone.select.structure.values.keytuple")
conf.compatibility.remove("json.bone.structure.inlists")

In addition, conf now runs in strict mode, i.e., compatibility with old conf accesses such as conf["mainApp"] is no longer allowed.

In the renderers, the modules render.json.user and render.vi.user have been completely removed without replacement. References to these should be removed in old projects. Only the respective default class is now used for all renderers.

Internationalization (i18n)

All Bone validations now provide translated error messages (previously only available in English).

The flag conf.i18n.auto_translate_bones can now be used to set globally whether descr and params.category should be automatically translated for Bones or not (default: True, as before).

Database

db module is back in viur-core

The db module, the ViUR abstraction for low-level Cloud Datastore database access, is now part of viur-core itself again.

The module was spun off into its own package viur-datastore in viur-core 3.1. This was done partly to improve performance at the time, and partly to connect to other databases—which never happened. Ultimately, this split led to major problems and inconsistencies in further development, and Google's own implementation of the Datastore API is now faster again.

The re-integrated db API contains the familiar functions such as db.put, db.get, db.delete, etc. However, these are now written in snake case in accordance with PEP-8, so that using the old functions generates a deprecation warning.

The configuration of db-specific settings is done via conf.db.

Query limits

Important

Starting with viur-core 3.8, it is no longer necessary to explicitly pass /example/list?limit=99 when calling list modules externally in order to obtain more than 30 records. This has been improved, so please take advantage of the new options!

ViUR always expected additional records to be reloaded via cursor, which in Jinja templates could only be solved with JavaScript and would be quite complicated. These limits (30 and 100) were previously hard-coded.

This can now be modified globally and individually in the project using the following conf variables:

  • conf.db.query_default_limit (default: 30) is the standard limit for queries.
  • conf.db.query_external_limit (default: 100) is the maximum limit for queries that can be set via external calls. This prevents users of a ViUR system from setting a limit of 30,000 from outside, even though only conf.db.query_default_limit = 100 has been set.

A project that is configured in main.py, for example, with

conf.db.query_default_limit = 200

will always retrieve up to 200 entries for all queries, so you can save yourself the ?limit=99 if, for example, you expect 120 records and want "the maximum possible number" because the internal limit has already been set to 200.

Skeleton API

The Skeleton API (formerly skeleton.py) has now been split into a module with several files.

viur-relations and RelationalConsistency

Furthermore, the entire viur-relations handling has been completely refactored around the function update_relations(). This means that RelationalBones in RecordBones are also updated. RelationalConsistency consistency checks are now also handled by update_relations()—this was previously done by the processRemovedRelations function, which has been replaced by the refactoring and is now obsolete.

RelationalBone.fromClient() refactored

RelationalBone.fromClient() has also been refactored to accept RelationalConsistency settings and remove generally terrible code. This prevents the saving of relations to records that no longer exist in the case of a set RelationalConsistency. If no RelationalConsistency is set, the bone behaves exactly as before.

RefSkel.read()

You can now read the target skeleton of a RelationalBone directly using RefSkel.read().

# Read user
user_skel = UserSkel()
assert user_skel.read(key)

# Read company for user
company_skel = user_skel["company"]["dest"].read()  # entire CompanySkel (already possible in v3.7)

# ... you can also create (dynamic) SubSkels (only from v3.8):
company_skel = user_skel["company"][‘dest’].read(subskel=("select",))  # SubSkel defined in subSkels
company_skel = user_skel["company"]["dest"].read(bones=(‘name’, "city",))  # dynamic SubSkel

Skeleton.readonly()

Set all bones of a skeleton to readOnly=True, e.g. for ActionSkels:

channel_skel.readonly()

Skeleton.dump()

Generate a JSON-serializable representation directly from a skeleton:

channel_skel.dump()

Previously, you had to do something crazy like this:

from viur.core.render.json.default import DefaultRender as JsonRender
#...
JsonRender().renderSkelValues(channel_skel)

Bones

searchable=True for RawBone

RawBones can now be searchable=True. In addition, the extraction of search terms has been changed to regular expressions and generalized between string, text, and RawBone.

ImageBone

The ImageBone should be used generally for images and was introduced for accessibility and to clearly define an image in the data.

It is configured as

class ImageBone(FileBone):
    type = FileBone.type + ".image"

    def __init__(
        self,
        *,
        public: bool = True,
        using: t.Optional[RelSkel] = ImageBoneRelSkel,
        validMimeTypes: None | t.Iterable[str] = ["image/*"],
        **kwargs,
    ):
        super().__init__(
            public=public,
            using=using,
            validMimeTypes=validMimeTypes,
            **kwargs,
        )

The ImageBoneRelSkel contains translated fields for alt texts.

image

Module.skel()

In general, there is now a skel() function for modules. This is equivalent to baseSkel(), only shorter. It will play an even greater role in viur-core>=4.

assert (skel := self.skel().read(key))  # everything is there! :-D

File module

The function File.write() now allows the optional specification of a repository and a path. This allows files written by the ViUR system to be written directly to a directory in the File module. The directory is created automatically if it does not exist.

conf.main_app.file.write(
    f"test-{utils.utcNow().strftime("%d-%m-%Y_%H-%M-%S")}.txt",
    b"Hello World",
    folder=("My text files", str(utils.utcNow().year))
)
image

History module

For the first time, there is now a ViUR standard history module. This consists of the components History (a list module) and the HistoryAdapter.

The History module writes to the ViUR system by default, but can also write to BigQuery optionally. This is configured via the History Config.

To use the module, it must be subclassed once in the system (just like File):

from viur.core.modules.history import History

class History(History):
    adminInfo = History.adminInfo | {
        "moduleGroup": "system",  # here, for example, the module is placed in another module group
    }

A skeleton that is to generate history entries must be configured as follows:

from viur.core.skeleton import Skeleton, ViurTagsSearchAdapter
from viur.core.bones import *
from viur.core.modules.history import HistoryAdapter


class ExampleSkel(Skeleton):
    database_adapters = (
        ViurTagsSearchAdapter(),
        HistoryAdapter(),
    )

Additional settings can be made via the HistoryAdapter, e.g., which bones should be ignored.

User module

is_admin() function

The is_admin() function of the User module defines which users are considered administrators. This can be defined on a project-specific basis. Previous checks of the type if "root" in user["access"]: should be replaced by if conf.main_app.user.is_admin(user):.

Checks for "root" in user["access"] should really only be used in cases where superuser rights are actually necessary (e.g., maintenance functions).

Login process changed to ActionSkels

The login process in the user module has been changed so that ActionSkels are now output to guide the user through the login process using a specific login method. This has been implemented in the admin area and will soon be relevant for several projects.

Important

This is a minor breaking change, but it could only cause problems in specific projects, as the /vi/user/login function now either displays an ActionSkel with the possible login methods (if there are several) or redirects to the only existing login method. The function with the ridiculously stupid name /vi/user/getAuthMethods, which previously did exactly the same thing as /vi/user/login, is now deprecated and will soon be removed.

User.adminInfo is now a function

You can no longer do something like this, as you could before:

from viur.core.modules.user import User

class User(User):
    adminInfo = User.adminInfo | {
        # ...more settings...
    }

Instead, you must now explicitly do the following:

from viur.core.modules.user import User

class User(User):
    def adminInfo(self):
        return super().adminInfo() | {
        # ...further settings...
    }

Important

This is a minor breaking change that could cause an error when starting the project in some projects. Please correct as above.

SkeletonMaintenanceTask

The server task rebuildSearchIndex has been removed and replaced by SkeletonMaintenanceTask.

With this task, only ‘root’ users can perform one or more of the following actions on their children

  • Refresh (formerly rebuildSearchIndex)
  • Delete
  • Count

Filtering can be performed to pre-filter the data selection (normal filtering).

In addition, logics can be used to define a programmed conditional expression that is applied to each data record and refreshes, deletes, or counts it.

image

Important

The condition False # fused: by default, doesn't affect anything., which is always preset, is like a security prompt. If left as is, NO data records will be refreshed, deleted, or counted. Therefore, you must explicitly enter True or something regulatory there.

@sveneberth

Copy link
Copy Markdown
  • core.db.config["memcache_client"] = core.db.cache.LocalMemcache() --> conf.db.memcache_client = Client()
  • html UserRender removed
  • skeleton._UNDEFINED --> skeleton._UNDEFINED_KINDNAME

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment