Data Model API
Tier: Stable Core
Structured tags, IEC data types, and memory block primitives.
pyrung.Field
Field(
type: TagType | None = None,
default: Any = ...,
retentive: bool | None = None,
choices: type[IntEnum] | ChoiceMap | None = None,
readonly: bool | None = None,
external: bool | None = None,
final: bool | None = None,
public: bool | None = None,
physical: Physical | None = None,
link: str | None = None,
min: int | float | None = None,
max: int | float | None = None,
uom: str | None = None,
) -> Any
pyrung.auto
Create a per-instance numeric default sequence descriptor.
pyrung.udt
udt(
*,
count: int = 1,
always_number: bool = False,
readonly: bool = False,
external: bool = False,
final: bool = False,
public: bool = False,
) -> Callable[[type[Any]], _StructRuntime]
Decorator that builds a mixed-type structured runtime from annotations.
pyrung.named_array
named_array(
base_type: object,
*,
count: int = 1,
stride: int = 1,
always_number: bool = False,
readonly: bool = False,
external: bool = False,
final: bool = False,
public: bool = False,
) -> Callable[[type[Any]], _NamedArrayRuntime]
Decorator that builds a single-type, instance-interleaved structured runtime.
pyrung.Timer
module-attribute
Timer: _DoneAccRuntime = _DoneAccRuntime(
name="Timer",
count=1,
field_specs=(
_FieldSpec("Done", BOOL, UNSET, retentive=False),
_FieldSpec("Acc", INT, UNSET, retentive=True),
),
)
pyrung.Counter
module-attribute
Counter: _DoneAccRuntime = _DoneAccRuntime(
name="Counter",
count=1,
field_specs=(
_FieldSpec("Done", BOOL, UNSET, retentive=False),
_FieldSpec("Acc", DINT, UNSET, retentive=True),
),
)
pyrung.TagType
Bases: Enum
Data types for tags (IEC 61131-3 naming).
pyrung.Bool
dataclass
pyrung.Int
dataclass
pyrung.Dint
dataclass
pyrung.Real
dataclass
pyrung.Char
dataclass
pyrung.Word
dataclass
pyrung.Block
dataclass
Factory for creating Tags from a typed memory region.
Block defines a named, 1-indexed memory region where every address shares
the same TagType. Indexing a Block returns a cached LiveTag. The block
holds no runtime values — all values live in SystemState.tags.
Address bounds are inclusive on both ends: Block("DS", INT, 1, 100)
defines addresses 1–100 (100 tags). Indexing outside this range raises
IndexError. Slice syntax (block[1:10]) is rejected — use
.select(start, end) instead.
For sparse blocks (e.g. Click X/Y banks with non-contiguous valid addresses),
pass valid_ranges to restrict which addresses within [start, end] are
legal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Block prefix used to generate tag names (e.g. |
required |
type
|
TagType
|
|
required |
start
|
int
|
Inclusive lower bound address (must be ≥ 0). |
required |
end
|
int
|
Inclusive upper bound address (must be ≥ start). |
required |
retentive
|
bool
|
Whether tags in this block survive power cycles.
Default |
False
|
valid_ranges
|
tuple[tuple[int, int], ...] | None
|
Optional tuple of |
None
|
address_formatter
|
Callable[[str, int], str] | None
|
Optional callable |
None
|
Example
DS = Block("DS", TagType.INT, 1, 100)
DS[1] # → LiveTag("DS1", TagType.INT)
DS[101] # → IndexError
# Range for block operations:
DS.select(1, 10) # → BlockRange, tags DS1..DS10
# Indirect (pointer) addressing:
idx = Int("Idx")
DS[idx] # → IndirectRef, resolved at scan time
DS[idx + 1] # → IndirectExprRef
slot
slot(
addr: int,
*,
name: str = ...,
retentive: bool = ...,
default: Any = ...,
comment: str = ...,
choices: ChoiceMap | None = ...,
readonly: bool = ...,
external: bool = ...,
final: bool = ...,
public: bool = ...,
physical: Physical | None = ...,
link: str | None = ...,
min: int | float | None = ...,
max: int | float | None = ...,
uom: str | None = ...,
) -> SlotView
slot(
addr: int,
end: int | None = None,
*,
name: object = UNSET,
retentive: bool | None = None,
default: object = UNSET,
comment: object = UNSET,
choices: object = UNSET,
readonly: object = UNSET,
external: object = UNSET,
final: object = UNSET,
public: object = UNSET,
physical: object = UNSET,
link: object = UNSET,
min: object = UNSET,
max: object = UNSET,
uom: object = UNSET,
) -> SlotView | RangeSlotView
Inspect, configure, or reset one or more block slots.
Single slot::
ds.slot(10) # inspect
ds.slot(10, name="Speed", retentive=True) # configure
ds.slot(10).reset() # clear overrides
Range::
ds.slot(20, 30, retentive=True) # configure range
ds.slot(20, 30).reset() # clear range
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
addr
|
int
|
Slot address (always required). |
required |
end
|
int | None
|
If given, defines an inclusive range |
None
|
name
|
object
|
Custom tag name (single-slot only). |
UNSET
|
retentive
|
bool | None
|
Retentive policy override. |
None
|
default
|
object
|
Default value override. |
UNSET
|
comment
|
object
|
Comment override (empty string clears). |
UNSET
|
Returns:
| Type | Description |
|---|---|
SlotView | RangeSlotView
|
|
select
Select an inclusive range of addresses for block operations.
Both start and end are inclusive: DS.select(1, 10) yields
10 tags (1, 2, … 10). This mirrors the block constructor convention and
avoids the off-by-one confusion of Python's half-open slices.
For sparse blocks (valid_ranges set), returns only the valid addresses
within the window — gaps are silently skipped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start
|
int | Tag | Any
|
Start address. |
required |
end
|
int | Tag | Any
|
End address. |
required |
Returns:
| Type | Description |
|---|---|
BlockRange | IndirectBlockRange
|
|
BlockRange | IndirectBlockRange
|
definition time). |
BlockRange | IndirectBlockRange
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
IndexError
|
If either bound is outside the block's |
Example
# Static range
DS.select(1, 100) # BlockRange, DS1..DS100
# Sparse window (Click X bank)
x.select(1, 21) # valid tags only: X001..X016, X021
# Dynamic range (resolved each scan)
DS.select(start_tag, end_tag) # IndirectBlockRange
# Use with bulk instructions:
fill(0, DS.select(1, 10))
blockcopy(DS.select(1, 10), DD.select(1, 10))
search(">=", 100, DS.select(1, 100), result=Found, found=FoundFlag)
pyrung.InputBlock
dataclass
Bases: Block
Factory for creating InputTag instances from a physical input memory region.
InputBlock is identical to Block except:
- Indexing returns
LiveInputTag(notLiveTag), so elements have.immediate. - Always non-retentive — physical inputs do not survive power cycles.
Use InputBlock when the tags represent real hardware inputs (sensors,
switches, etc.). In simulation, values are supplied via runner.patch() or
runner.force() during the Read Inputs scan phase.
Example
pyrung.OutputBlock
dataclass
Bases: Block
Factory for creating OutputTag instances from a physical output memory region.
OutputBlock is identical to Block except:
- Indexing returns
LiveOutputTag(notLiveTag), so elements have.immediate. - Always non-retentive — physical outputs do not survive power cycles.
Writes to OutputTag elements are immediately visible to subsequent rungs
within the same scan (standard PLC behavior). The actual hardware write
happens at the Write Outputs scan phase (phase 6).
Example
pyrung.RangeComparison
dataclass
Comparison expression over a block range, used by search().
Created by applying a comparison operator to a .select() result::
DS.select(1, 100) >= 100 # RangeComparison(range, ">=", 100)
Txt.select(1, 50) == "A" # RangeComparison(range, "==", "A")