Course / Reading data / Lesson 17

Lesson 17 · Reading data

The environment

Set context values for queries and writes, and know when they are read.

about 5 minutes · screenplay estimate8 scenesPrerequisites: lessons 15, 16

Development course · Reading edition · API compatibility

Scene 01

What the environment is

The environment is a dictionary of context values held by the database object. It holds values such as the current user, the working date or the current state of a partition. Queries read it through env underscore parameters. Row policies read it, and so does the hash PERIOD macro. Each thread has its own dictionary. The environment is context only. It does not open a transaction, does not select a database and does not grant permissions.

Example & screen notes

Slide title "db.currentEnv". Diagram: a box "thread 1" holding a dictionary {'user': 'anna', 'customer_id': '...'}, a box "thread 2" holding its own dictionary. Both boxes point to one box "db". An arrow from each dictionary goes to a box ":env_* parameters, row policies, #PERIOD".

Scene 02

Reading and changing it

db dot currentEnv is the live dictionary of the current thread. You change it directly. db dot current_env, with an underscore, returns a copy with a read-only outer mapping. Assigning to it raises TypeError. db dot currentUser returns the value of the key user. updateEnv sets several keys at once. None is a value, so region equal None stores None. With underscore excludeNoneValues equal True, updateEnv skips the keys whose value is None. clearCurrentEnv replaces the dictionary with an empty one. None of these calls commits, rolls back or closes a connection.

Example & screen notes

Slide title "Live mapping and snapshot". Code:

db.currentEnv['user'] = 'anna'
db.currentEnv['user']          # 'anna'   live dict of this thread
db.current_env['user']         # 'anna'   read-only copy
db.current_env['user'] = 'x'   # TypeError
db.currentUser                 # 'anna'

db.updateEnv(customer_id='X', region=None)
db.updateEnv(_excludeNoneValues=True, region='NE', state=None)
dict(db.currentEnv)            # {'user': 'anna', 'customer_id': 'X', 'region': 'NE'}

db.clearCurrentEnv()
dict(db.currentEnv)            # {}

Scene 03

Temporary values

temp_env sets values for the duration of a with block. tempEnv is another spelling of the same method. On exit, a key that existed before is restored to its old value, also when the block raises. A key that the block introduced is removed, but only if its value is still the one given to temp_env. If code inside the block changed it, the new value stays. temp_env is not a transaction. In the second example an invoice is inserted inside the block. After the block, the insert is still pending in the open transaction. Only rollback removes it, and only commit makes it permanent.

Example & screen notes

Slide title "db.temp_env(**values)". Code:

db.currentEnv['user'] = 'anna'
with db.temp_env(user='bruno', extra=1):
    db.currentEnv['user'], db.currentEnv['extra']     # ('bruno', 1)
dict(db.currentEnv)                                   # {'user': 'anna'}

with db.temp_env(user='temp'):
    invoice.insert({'inv_number': 'T1', 'customer_id': ada_id, 'total': 1})
invoice.query('$inv_number', "$inv_number = 'T1'").fetch()   # still there: T1
db.rollback()                                                 # now it is gone

Bullets: - on exit: a key that existed is restored; a new key is removed if its value is unchanged - tempEnv is the same method - no transaction: work done inside stays pending after the block

Scene 04

Working date and locale

Two keys have properties on the database. db dot workdate returns the key workdate, or today's date when the key is absent or false. db dot locale returns the key locale. When it is absent, it uses the environment variable GNR_LOCALE, then the locale of the Python process, then en_GB. Setting either property writes the key into currentEnv. The values are not validated. They do not change the process locale or date formatting. A colon env_workdate parameter reads the workdate with the same default, today.

Example & screen notes

Slide title "db.workdate, db.locale". Code:

db.workdate                   # today, when not set
db.locale                     # GNR_LOCALE, else the process locale, else 'en_GB'

db.workdate = date(2026, 1, 31)
db.locale = 'it'
dict(db.currentEnv)           # {'workdate': date(2026, 1, 31), 'locale': 'it'}

Scene 05

When :env_ values are read

The query compiles once, but env underscore values are read at every execution. The same query object returns Ada's invoices in the first block and Bob's invoices in the second. Outside both blocks the key is absent, the parameter is NULL, and the result is empty. No error is raised for an absent key. An explicit parameter takes precedence. When you pass env_customer_id to query, the value is a normal parameter, and the environment is not read for it. Some environment values change the SQL text itself, not only a parameter. These are the dates of hash PERIOD and the shape of a partition condition. Those are fixed at the first compilation. To change them, build a new query inside the new scope.

Example & screen notes

Slide title "Read at every execution". Code:

query = invoice.query('$inv_number', '$customer_id = :env_customer_id',
                      order_by='$inv_number')
with db.temp_env(customer_id=ada_id):
    query.fetch()          # A1, A2
with db.temp_env(customer_id=bob_id):
    query.fetch()          # B1, B2   same compiled SQL
query.fetch()              # []       key absent: NULL

explicit = invoice.query('$inv_number', '$customer_id = :env_customer_id',
                         env_customer_id=bob_id, order_by='$inv_number')
with db.temp_env(customer_id=ada_id):
    explicit.fetch()       # B1, B2   the explicit value wins

Scene 06

Threads

Each thread has its own environment dictionary. A new thread starts from the initial values of the database, not from the values of the thread that created it. In the example the worker thread does not see the user main, and its change does not reach the main thread. Do not pass the currentEnv dictionary to another thread or to an async task. Several async tasks in one thread share one dictionary, so asqueel does not support concurrent async tasks within one thread.

Example & screen notes

Slide title "One environment per thread". Code:

db.currentEnv['user'] = 'main'
seen = {}

def worker():
    seen['user'] = db.currentEnv.get('user')
    db.currentEnv['user'] = 'worker'

thread = threading.Thread(target=worker)
thread.start(); thread.join()
seen, db.currentEnv['user']        # ({'user': None}, 'main')

Scene 07

Initial values

To give every thread the same starting values, pass an SqlEnvironment to AsqueelDb with the environment argument. The database copies its values when it is built. Each thread receives a copy of them on its first access to currentEnv. clearCurrentEnv empties only the dictionary of the current thread. A thread created later still starts with the initial values. The values must support deepcopy.

Example & screen notes

Slide title "AsqueelDb(..., environment=SqlEnvironment(...))". Code:

from asqueel import AsqueelDb, SqlEnvironment

db = AsqueelDb(Invoicing, environment=SqlEnvironment({'user': 'system'}))
dict(db.currentEnv)          # {'user': 'system'}
db.clearCurrentEnv()
dict(db.currentEnv)          # {}
# a new thread still starts with {'user': 'system'}

Scene 08

Recap

To recap. currentEnv is the live dictionary of the thread, and current_env is a read-only copy. temp_env restores values on exit and does not touch the transaction. env underscore values are read at every execution, and an explicit parameter takes precedence. The next lesson uses the environment to filter rows with row policies.

Example & screen notes

Slide title "Recap". Bullets: - currentEnv is live and per thread; current_env is a read-only copy - temp_env restores values on exit and is not a transaction - :env_* values are read at every execution; an explicit parameter wins

Footer: "Next: lesson 18 — Row policies".