Skip to content

System parameters & sequences

Almost every app needs two boring-but-essential things: somewhere to keep instance-level configuration (a company name, an API key, a default sender address), and a reliable way to mint human-readable reference numbers (INV0001, SO0001). Victor's always-installed base module gives you both as core models, plus a small SDK you call from module code.

  • System parameters — a key/value store scoped to the instance, with optional encrypted-at-rest secrets.
  • Sequences — named, atomic counters that produce zero-padded, prefixed reference numbers.

Both are ordinary models rendered by the generic list/form framework, so you manage their records in the app UI under Settings and read or write them from code through the SDK. You never redeclare these models in your own module — you create and read records against them.

System parameters

A system parameter is a single key/value setting for the instance. The system_parameter model (from the base module) has three fields.

Field Type Notes
key String (required) The lookup key, e.g. company.name. Use dotted, namespaced keys.
value Text The stored value. Always a string.
secret Boolean (default false) When set, the value is stored encrypted at rest and masked in the UI.

Values are always strings. If you need an int, a bool, or JSON, the caller parses it — Victor stores exactly what you give it.

Reading and writing from module code

Import the config accessor and call get_param / set_param. Both are async and take the current session.

from victor import config

# Read: returns the stored string, or the default when the key is unset
value = await config.get_param(session, "company.name", "Acme")

# Write: creates the parameter, or updates it if the key already exists (upsert by key)
await config.set_param(session, "company.name", "Globex")
Function Signature Behavior
config.get_param get_param(session, key, default=None) -> str \| None Returns the stored string for key, or default when the key is unset. Secret values are decrypted transparently.
config.set_param set_param(session, key, value, secret=False) -> None Upserts by key. value must be a string. Pass secret=True to store it encrypted.

Because values are strings, parse them at the call site:

from victor import config

raw = await config.get_param(session, "billing.max_retries", "3")
max_retries = int(raw)

Real example: reading config with defaults

The built-in mail module reads its own settings this way, always supplying a sensible default so the module works before an admin has configured anything:

from victor import config

policy = await config.get_param(session, "mail.inbound.reply_policy", "known_senders")
default_from = await config.get_param(session, "mail.default_from", "notifications@victor.local")

This is the pattern to follow: namespace your keys under your module (mail.*, billing.*), and pass a default that keeps the module functional out of the box.

Secrets

Set secret=True to store a value encrypted at rest. Your module code never deals with ciphertext — get_param decrypts transparently and hands you plaintext:

from victor import config

await config.set_param(session, "api.key", "sk-123", secret=True)  # stored encrypted at rest
key = await config.get_param(session, "api.key")                   # -> "sk-123" (plaintext for module code)
# In the System Parameters UI / records list the value is shown as ••••••

In the System Parameters list and form, secret values are masked as •••••• so they never appear on screen. Secret values are encrypted at rest, so use secret=True for anything sensitive like API keys or tokens.

Managing parameters in the UI

Parameters live under Settings → System Parameters, rendered by the generic framework from this view definition. You don't write this XML — it ships with the base module — but it shows the fields you'll see on the form:

<victor>
  <view id="view_system_parameter_list" model="system_parameter" type="list">
    <field name="key"/>
    <field name="value"/>
    <field name="secret"/>
  </view>
  <view id="view_system_parameter_form" model="system_parameter" type="form">
    <group string="Parameter">
      <field name="key"/>
      <field name="value"/>
      <field name="secret"/>
    </group>
  </view>
</victor>

See Views for how list/form views and menus are declared.

Sequences

A sequence is a named, atomic counter used to produce reference numbers like SO0001. The sequence model (from the base module) holds one counter per record.

Field Type Notes
code String (required) The lookup key you pass to next_value, e.g. sale.order.
seq_name String Human-readable display name.
prefix String (default "") Prepended to the number, e.g. SO or INV.
padding Integer (default 4) Zero-pad width, so 1 renders as 0001.
next_number Integer (default 1) The number the next mint will use.

Defining a sequence

Create the sequence record first, in the app UI under Settings → Sequences. Give it a code (the key your code will look up), a prefix, and a padding. For example, a record with code = sale.order, prefix = SO, padding = 4 produces SO0001, SO0002, and so on.

The Sequences screen is rendered from this base-module view:

<victor>
  <view id="view_sequence_list" model="sequence" type="list">
    <field name="code"/>
    <field name="seq_name"/>
    <field name="prefix"/>
    <field name="next_number"/>
  </view>
  <view id="view_sequence_form" model="sequence" type="form">
    <group string="Sequence">
      <field name="code"/>
      <field name="seq_name"/>
      <field name="prefix"/>
      <field name="padding"/>
      <field name="next_number"/>
    </group>
  </view>
</victor>

Define before you mint

next_value looks the sequence up by code. The record must exist before you call it — an unknown code raises ValueError.

Minting a reference number

Import sequences and call next_value with the sequence's code:

from victor import sequences

# 'sale.order' must be defined as a sequence first (e.g. prefix='SO', padding=4).
# Atomically claims the next number, then advances the counter.
num = await sequences.next_value(session, "sale.order")   # e.g. "SO0001"
# An unknown code raises ValueError.
Function Signature Behavior
sequences.next_value next_value(session, code) -> str Atomically claims the next number for code, advances the counter, and returns prefix + zero-padded number. Raises ValueError for an unknown code.

Numbering semantics. next_value returns the value that next_number currently points at, then advances the counter by one. The returned number is zero-padded to padding digits and prefixed with prefix. So a fresh sale.order sequence (next_number = 1, prefix = SO, padding = 4) returns SO0001 on the first call and leaves next_number at 2.

The claim-and-advance is atomic and concurrency-safe: two requests minting from the same sequence at the same time each get a distinct, gap-free number.

Using a sequence in a record action

The common pattern is to stamp a reference number onto a record when it's created or confirmed — for example, in a helpdesk app assigning a ticket number:

from victor import sequences

async def assign_reference(session, ticket):
    ticket["reference"] = await sequences.next_value(session, "helpdesk.ticket")
    return ticket

Define a sequence with code = helpdesk.ticket, prefix = TKT, padding = 5 and you'll get TKT00001, TKT00002, and so on. See Actions & menus for wiring this into record or view actions.

Field reference at a glance

For quick lookup, the two core models expose these fields (all provided by the base module — do not redeclare them):

# system_parameter
#   key    (String, required)  the lookup key
#   value  (Text)              the stored value (always a string)
#   secret (Boolean, default False)  encrypt at rest + mask in the UI

# sequence
#   code        (String, required)   lookup key passed to sequences.next_value(...)
#   seq_name    (String)             display name
#   prefix      (String, default "") prepended to the number, e.g. "INV" or "SO"
#   padding     (Integer, default 4) zero-pad width -> 0001
#   next_number (Integer, default 1) the number the next mint will use
  • Models & fields — how models like system_parameter and sequence are defined.
  • Views — the list/form/menu XML the Settings screens are rendered from.
  • Tools & server actions — where you typically call sequences.next_value and config.get_param.