Variables and typed configuration
Values in .env are stored as text, but Env.get() and env() can return Python types. Define the appropriate format in the file and read it exclusively from config/. During runtime, use resolved configuration as described in Environment.
A complete example
Section titled “A complete example”This .env uses all ten available prefixes:
REPORTS_NAME="str:Weekly report"REPORTS_LIMIT=int:100REPORTS_TIMEOUT=float:2.5REPORTS_ENABLED=bool:onREPORTS_RECIPIENTS="list:['team@example.test', 'ops@example.test']"REPORTS_OPTIONS="dict:{'retries': 3, 'notify': True}"REPORTS_WINDOW="tuple:(8, 18)"REPORTS_TAGS="set:{'daily', 'internal'}"REPORTS_DIRECTORY=path:storage/reportsREPORTS_BANNER=base64:SGVsbG8=Create config/reports.py with the corresponding reads:
from dataclasses import dataclass, fieldfrom orionis.environment import env
@dataclass(frozen=True, kw_only=True)class Reports: name: str = field( default_factory=lambda: env("REPORTS_NAME", "Weekly report"), ) limit: int = field( default_factory=lambda: env("REPORTS_LIMIT", 100), ) timeout: float = field( default_factory=lambda: env("REPORTS_TIMEOUT", 2.5), ) enabled: bool = field( default_factory=lambda: env("REPORTS_ENABLED", False), ) recipients: list[str] = field( default_factory=lambda: env("REPORTS_RECIPIENTS", []), ) options: dict[str, object] = field( default_factory=lambda: env("REPORTS_OPTIONS", {}), ) window: tuple[int, int] = field( default_factory=lambda: env("REPORTS_WINDOW", (8, 18)), ) tags: set[str] = field( default_factory=lambda: env("REPORTS_TAGS", set()), ) directory: str = field( default_factory=lambda: env("REPORTS_DIRECTORY", "storage/reports"), ) banner: str | bytes = field( default_factory=lambda: env("REPORTS_BANNER", ""), )The factories build values for each configuration instance. Once the application has been created, read keys such as app.config("reports.limit") and app.config("reports.options"); do not repeat environment reads from application components.
The results described on this page are those of environment reads. Review compiled configuration serialization before relying on preservation of types such as tuples, sets or bytes through JSON storage.
Supported prefixes
Section titled “Supported prefixes”Write the prefix in lowercase with no spaces before :. int:100 is recognized; INT:100 and int :100 remain text when read with Env.get().
| Prefix | Result type | Example result |
|---|---|---|
str: | str | "Weekly report" |
int: | int | 100 |
float: | float | 2.5 |
bool: | bool | True |
list: | list | ["team@example.test", "ops@example.test"] |
dict: | dict | {"retries": 3, "notify": True} |
tuple: | tuple | (8, 18) |
set: | set | {"daily", "internal"} |
path: | str | "storage/reports" |
base64: | str or bytes | "Hello" |
str:: preserve text
Section titled “str:: preserve text”Use it for codes, identifiers or text that might be interpreted as another type:
ACCOUNT_CODE=str:00125DISPLAY_FLAG=str:falseEMPTY_LABEL=str:Reading these from config/ produces "00125", "false" and "". Dotenv quotes only delimit the value: ACCOUNT_CODE="123" still becomes the integer 123. The str: prefix prevents this conversion.
Conversion removes leading whitespace from the content after the prefix. Do not use it to preserve that leading whitespace.
int:: integers
Section titled “int:: integers”Use it for ports, quantities or limits:
REPORTS_LIMIT=int:100REPORTS_OFFSET=int:-5These produce 100 and -5. Surrounding whitespace and formats supported by int(text) with its default base are accepted. int:many and int:2.5 raise ValueError; the read default does not replace them.
float:: floating-point numbers
Section titled “float:: floating-point numbers”Use it for timeouts or factors:
REPORTS_TIMEOUT=float:2.5REPORTS_FACTOR=float:1e-3These produce 2.5 and 0.001 through float(text). The decimal separator is .; float:2,5 raises ValueError.
bool:: boolean switches
Section titled “bool:: boolean switches”Use it to enable or disable settings:
REPORTS_ENABLED=bool:onREPORTS_NOTIFY=bool:disabledtrue, 1, yes, on and enabled produce True, ignoring case and surrounding whitespace. false, 0, no, off and disabled produce False.
Env.get() and env() treat any other content after bool: as False. For example, bool:treu also returns False; it does not validate that the value is one of the negative spellings above. Use the documented representations to avoid disabling a setting through a typo. Explicit conversion with EnvironmentCaster(...).get() validates both groups, as explained below.
list:: ordered lists
Section titled “list:: ordered lists”Use it for recipients, origins or values whose order matters:
REPORTS_RECIPIENTS="list:['team@example.test', 'ops@example.test']"REPORTS_EMPTY_LIST=list:[]These produce a list containing two strings and an empty list. The content must be a Python list literal; list:team,ops does not split comma-separated text. Elements are not automatically converted either: list:['1', 2] preserves a string and an integer.
dict:: named settings
Section titled “dict:: named settings”Use it to group related settings:
REPORTS_OPTIONS="dict:{'retries': 3, 'notify': True}"This produces {"retries": 3, "notify": True}. The inner syntax is a safe Python literal: use True, False and None for those values. This is not a JSON parser; dict:{"notify": true} fails because true is not a Python literal.
tuple:: positional groups
Section titled “tuple:: positional groups”Use it when values represent specific positions, such as a time window:
REPORTS_WINDOW="tuple:(8, 18)"REPORTS_SINGLE_VALUE="tuple:('daily',)"These produce (8, 18) and ("daily",). A single-item tuple requires the comma. tuple:[8, 18] is a list and is rejected; the prefix does not convert another collection type.
set:: unique values
Section titled “set:: unique values”Use it for collections where membership matters rather than order:
REPORTS_TAGS="set:{'daily', 'internal', 'daily'}"REPORTS_EMPTY_SET=set:set()These produce {"daily", "internal"} and set(). Duplicates disappear and order is not guaranteed. set:{} does not create an empty set: {} is a dictionary and is rejected. Elements must be valid Python set members.
path:: normalized paths
Section titled “path:: normalized paths”Use it for settings that contain file or directory paths:
REPORTS_DIRECTORY=path:storage/reportsREPORTS_WINDOWS_DIRECTORY='path:storage\reports'Both reads return "storage/reports". The result is a string with POSIX separators, not a Path object. Reading path: does not create the directory, check its existence or turn a relative path into an absolute one.
Explicit serialization with EnvironmentCaster(value).to("path") does prepend the working directory for relative values. This difference between reading and serialization matters when preparing .env values.
base64:: encoded content
Section titled “base64:: encoded content”Use it when a setting’s format requires Base64-encoded content:
REPORTS_BANNER=base64:SGVsbG8=REPORTS_BINARY=base64:/w==The first read returns the string "Hello"; the second returns b"\xff" because the decoded content is not valid UTF-8 text. An invalid Base64 payload raises ValueError.
The component decodes this prefix on read. Base64 does not encrypt content or protect a secret.
Unprefixed values
Section titled “Unprefixed values”An unprefixed value is parsed in this order:
- An empty string returns
None. none,null,nanandnil, ignoring case and surrounding whitespace, returnNone.trueandfalse, also ignoring case, return booleans.- A Python literal is attempted through
ast.literal_eval: numbers, quoted strings and literal collections. - If it is not a valid literal, the original string is preserved.
AUTO_LIMIT=100AUTO_ENABLED=trueAUTO_OPTIONS="{'notify': True, 'retries': 3}"AUTO_URL=https://example.test/reportsAUTO_WORD=yesThe results are 100, True, a dictionary, the URL as a string and "yes". Without bool:, words such as yes and off are not boolean switches. An unknown prefix such as json:{} does not add a new type and normally remains text.
Literals are evaluated safely; arbitrary expressions and calls are not executed. The component does not validate collection element types against a dataclass annotation.
When the default applies
Section titled “When the default applies”The read signatures are:
env(key: str, default: object | None = None) -> objectEnv.get(key: str, default: object | None = None) -> objectGiven these entries:
REPORTS_EMPTY=REPORTS_NULL=nullREPORTS_EMPTY_TEXT=str:REPORTS_ZERO=int:0REPORTS_DISABLED=bool:falseRead inside config/ | Result |
|---|---|
env("REPORTS_MISSING", 100) | 100, if the key is absent from the process. |
env("REPORTS_EMPTY", 100) | None. |
env("REPORTS_NULL", 100) | None. |
env("REPORTS_EMPTY_TEXT", "fallback") | "". |
env("REPORTS_ZERO", 100) | 0. |
env("REPORTS_DISABLED", True) | False. |
env("REPORTS_MISSING", "int:100") | "int:100", with no conversion of the default. |
A line containing only a name without = appears as None in the Env.all() snapshot but publishes no process value. If that variable was not already defined by the system, Env.get() returns the default.
The default is neither validation nor a correction for an invalid prefix. Supply the Python value the configuration requires, such as 100, False or [], rather than a prefixed string.
Explicit conversion in configuration
Section titled “Explicit conversion in configuration”EnvironmentCaster is the utility for interpreting or serializing typed values. It implements IEnvironmentCaster from orionis.environment.contracts.caster, whose contract defines get() and to(). Its explicit use must also remain in configuration files. It does not need to be imported for ordinary Env.get() reads.
Construction
Section titled “Construction”EnvironmentCaster(raw: str | object) -> NoneIt accepts an object or a string. If a prefix is recognized, it retains the content for interpretation by get(); otherwise, it keeps the value without conversion. Unlike prefix recognition in Env.get(), the constructor normalizes the prefix by removing surrounding whitespace and lowercasing it.
OPTIONS and supportedTypes()
Section titled “OPTIONS and supportedTypes()”OPTIONS and supportedTypes() -> frozenset[str] expose the same ten type names listed in the table above. EnvironmentValueType contains members STR, INT, FLOAT, BOOL, LIST, DICT, TUPLE, SET, PATH and BASE64, whose values are those lowercase names.
# A configuration check inside config/reports.py.from orionis.environment.dynamic.caster import EnvironmentCaster
SUPPORTED_REPORT_TYPES = EnvironmentCaster.supportedTypes()assert "dict" in SUPPORTED_REPORT_TYPESassert SUPPORTED_REPORT_TYPES == EnvironmentCaster.OPTIONSget() -> object
Section titled “get() -> object”Interprets the instance value. To validate a boolean explicitly, keep its input as text and convert it while constructing configuration. This alternative config/reports.py focuses on that case:
REPORTS_SWITCH=str:offfrom dataclasses import dataclass, fieldfrom orionis.environment import envfrom orionis.environment.dynamic.caster import EnvironmentCaster
def report_switch() -> bool: value = env("REPORTS_SWITCH", "off") return EnvironmentCaster(f"bool:{value}").get()
@dataclass(frozen=True, kw_only=True)class Reports: enabled: bool = field(default_factory=report_switch)off produces False; str:unexpected in .env makes this configuration raise ValueError. Keep str: on the entry so that its original value reaches validation: with bool:unexpected, the initial env() read would already have converted it to False.
get() preserves conversion errors as ValueError or TypeError, depending on the case, and wraps other failures as ValueError. The empty case EnvironmentCaster("str:").get() fails; ordinary env() reading takes a different path and does return "".
parseTyped(value_str: str) -> object
Section titled “parseTyped(value_str: str) -> object”Directly interprets a string in type:content format. It normalizes the prefix and uses a fast conversion for str, int, float and bool; other types pass through a converter instance.
# Conversion expressions while loading config/reports.py.from orionis.environment.dynamic.caster import EnvironmentCaster
DEFAULT_LIMIT = EnvironmentCaster.parseTyped("int:100")DEFAULT_OPTIONS = EnvironmentCaster.parseTyped("dict:{'retries': 3}")DEFAULT_LIMIT is 100 and DEFAULT_OPTIONS is {"retries": 3}. A string without : raises ValueError. This path has the same permissive bool: behavior as Env.get(); to validate input spellings, use get() as shown above.
to(type_hint: str | EnvironmentValueType) -> str
Section titled “to(type_hint: str | EnvironmentValueType) -> str”Serializes the object to a type:content string. It does not save the result to .env; that operation belongs to Env.set(). Use it when preparing typed values in configuration or a configuration integration:
# Explicit serialization in a config/ file.from orionis.environment.dynamic.caster import EnvironmentCasterfrom orionis.environment.enums import EnvironmentValueType
LIMIT_LITERAL = EnvironmentCaster("12").to("int")OPTIONS_LITERAL = EnvironmentCaster({"retries": 3}).to("dict")ENABLED_LITERAL = EnvironmentCaster("yes").to(EnvironmentValueType.BOOL)The results are "int:12", "dict:{'retries': 3}" and "bool:true".
| Requested type | Python value accepted for serialization | Behavior |
|---|---|---|
str | String. | Adds the prefix; does not automatically convert other objects to text. |
int | Integer or value convertible with int(). | Can convert numeric text; converting a Python float to integer can discard its fractional part. |
float | Float or value convertible with float(). | Returns its floating-point representation with a prefix. |
bool | Boolean, recognized boolean word or another object. | Validates words; for other objects, uses Python truthiness. |
list, dict, tuple, set | A collection of the corresponding type. | Uses its literal representation; a string containing a collection is not a replacement for the actual collection. |
path | String or Path. | Normalizes separators and makes relative paths absolute using the working directory. |
base64 | String or bytes decodable as UTF-8. | Preserves content that is already valid Base64; otherwise encodes it. |
to() accepts lowercase names or an enum member. An unknown name, incompatible collection or conversion failure results in ValueError. When serializing Base64, a string that is already valid Base64 is considered encoded and is not encoded again. Bytes that cannot be decoded as UTF-8 are not accepted by that operation, although they can be returned when decoding a .env value.
Continue with the guide
Section titled “Continue with the guide”See the operations reference for writing, reloading and name validation. The compilation and persistence guide explains what happens to these values when the application uses configuration stored in JSON.