Skip to content

Commit

Permalink
first pass of SubEntity implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
gilesknap committed May 10, 2024
1 parent 7674437 commit 5f4f435
Show file tree
Hide file tree
Showing 15 changed files with 6,578 additions and 30 deletions.
12 changes: 6 additions & 6 deletions src/ibek/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@

from typing import Sequence, Union

from pydantic import Field
from pydantic import BaseModel, Field

from .args import Arg, Value
from .globals import BaseSettings
from .ioc import Entity
from .sub_entity import SubEntity


class CollectionDefinition(BaseSettings):
class CollectionDefinition(BaseModel):
name: str = Field(
description="Publish Definition as type <module>.<name> for IOC instances"
)
Expand All @@ -28,6 +27,7 @@ class CollectionDefinition(BaseSettings):
description="Calculated values to use as additional arguments", default=()
)

entities: Sequence[Entity] = Field(
description="The entities that this collection is to instantiate", default=()
entities: Sequence[SubEntity] = Field(
description="The sub-entity instances that this collection is to instantiate",
default=(),
)
2 changes: 1 addition & 1 deletion src/ibek/entity_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def make_entity_models(support: Support):
if isinstance(definition, Definition):
entity_models.append(make_entity_model(definition, support))
elif isinstance(definition, CollectionDefinition):
"""TODO: do we need to do anything here?"""
pass

if definition.name in entity_names:
# not tested because schema validation will always catch this first
Expand Down
33 changes: 30 additions & 3 deletions src/ibek/gen_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@

from ibek.utils import UTILS

from .collection import CollectionDefinition
from .definition import Database
from .entity_model import make_entity_models, make_ioc_model
from .globals import TEMPLATES
from .ioc import IOC, Entity, clear_entity_model_ids
from .render import Render
from .render_db import RenderDb
from .sub_entity import SubEntity
from .support import Support

log = logging.getLogger(__name__)
Expand All @@ -27,11 +29,14 @@
url_f = r"file://"


def ioc_create_model(definitions: List[Path]) -> Type[IOC]:
def ioc_create_model(
definitions: List[Path],
) -> Tuple[Type[IOC], List[CollectionDefinition]]:
"""
Take a list of definitions YAML and create an IOC model from it
"""
entity_models = []
entity_collections = []

clear_entity_model_ids()
for definition in definitions:
Expand All @@ -44,10 +49,28 @@ def ioc_create_model(definitions: List[Path]) -> Type[IOC]:
# make Entity classes described in the support module definition file
entity_models += make_entity_models(support)

# collect up the IOCs CollectionDefinitions
for definition in support.defs:
if isinstance(definition, CollectionDefinition):
entity_collections.append(definition)

# Save the schema for IOC
model = make_ioc_model(entity_models)

return model
return model, entity_collections


def process_sub_entities(collections: List[CollectionDefinition]):
"""
Render all the SubEntities in this IOC
"""

for collection in collections:
for entity in collection.entities:
if isinstance(entity, SubEntity):
entity_cls = name_to_entity_cls[entity.type]
entity = entity_cls(**entity.model_dump())
entity.__is_sub_entity__ = True


def ioc_deserialize(ioc_instance_yaml: Path, definition_yaml: List[Path]) -> IOC:
Expand All @@ -56,10 +79,11 @@ def ioc_deserialize(ioc_instance_yaml: Path, definition_yaml: List[Path]) -> IOC
Returns a model of the resulting ioc instance
"""
ioc_model = ioc_create_model(definition_yaml)
ioc_model, entity_collections = ioc_create_model(definition_yaml)

# extract the ioc instance yaml into a dict
ioc_instance_dict = YAML(typ="safe").load(ioc_instance_yaml)

if ioc_instance_dict is None or "ioc_name" not in ioc_instance_dict:
raise RuntimeError(
f"Failed to load a valid ioc config from {ioc_instance_yaml}"
Expand All @@ -73,6 +97,9 @@ def ioc_deserialize(ioc_instance_yaml: Path, definition_yaml: List[Path]) -> IOC
# Create an IOC instance from the instance dict and the model
ioc_instance = ioc_model(**ioc_instance_dict)

# now that entity instance are created, we can process the SubEntities
process_sub_entities(entity_collections)

return ioc_instance


Expand Down
22 changes: 9 additions & 13 deletions src/ibek/ioc.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from .globals import BaseSettings
from .utils import UTILS

# a global dict of all entity instances indexed by their ID
id_to_entity: Dict[str, Entity] = {}


Expand Down Expand Up @@ -75,29 +76,24 @@ def add_ibek_attributes(self):
if value in id_to_entity:
raise ValueError(f"Duplicate id {value} in {list(id_to_entity)}")
id_to_entity[value] = self
return self

def __str__(self):
# if this entity has an id then its string representation is the value of id
id_name = self.__definition__._get_id_arg()
return getattr(self, id_name) if id_name else super().__str__()

@model_validator(mode="after")
def check_objects(self) -> Entity:
"""
If an object field was populated by a default value it will currently
just be a string representation of the object. This function will convert
that string into the actual object.
"""
# If an object field was populated by a default value it will currently
# just be the object id. Now convert id into the actual object.
for field in self.model_fields_set:
prop = getattr(self, field)
model_field = self.model_fields[field]

if model_field.annotation == object:
if isinstance(prop, str):
setattr(self, field, get_entity_by_id(prop))

return self

def __str__(self):
# if this entity has an id then its string representation is the value of id
id_name = self.__definition__._get_id_arg()
return getattr(self, id_name) if id_name else super().__str__()


class IOC(BaseSettings):
"""
Expand Down
5 changes: 2 additions & 3 deletions src/ibek/ioc_cmds/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ def build_docker(
help="The filepath to the Dockerfile to build",
autocompletion=lambda: [], # Forces path autocompletion
),
] = Path.cwd()
/ "Dockerfile",
] = Path.cwd() / "Dockerfile",
):
"""
EXPERIMENTAL: Attempt to interpret the Dockerfile and run it's commands
Expand Down Expand Up @@ -79,7 +78,7 @@ def generate_schema(
log.error(f"No `definitions` given and none found in {GLOBALS.IBEK_DEFS}")
raise typer.Exit(1)

ioc_model = ioc_create_model(definitions)
ioc_model, _ = ioc_create_model(definitions)
schema = json.dumps(ioc_model.model_json_schema(), indent=2)
if output is None:
typer.echo(schema)
Expand Down
27 changes: 27 additions & 0 deletions src/ibek/sub_entity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from typing import Sequence

from pydantic import BaseModel, Field, model_validator


class SubEntity(BaseModel, extra="allow"):
"""
A loosely defined class to declare the Entities
in an ibek.support.yaml file in the CollectionDefinition section
"""

type: str = Field(description="The type of this entity")

@model_validator(mode="after")
def store(self):
"""
Store the SubEntity instance in the global list of SubEntities
"""

# empty extra implies this is the base class being validated
if self.model_extra is not None:
sub_entities.append(self)
print(f"sub-entity {self.type}")
return self


sub_entities: Sequence[SubEntity] = []
9 changes: 8 additions & 1 deletion tests/generate_samples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ ibek ioc generate-schema --no-ibek-defs support/utils.ibek.support.yaml --output
echo making an ioc schema using asyn and gauges support yaml
ibek ioc generate-schema --no-ibek-defs support/asyn.ibek.support.yaml support/gauges.ibek.support.yaml --output schemas/gauges.ibek.ioc.schema.json

echo making an ioc schema using ADCore and quadem support yaml
ibek ioc generate-schema --no-ibek-defs support/ADCore.ibek.support.yaml support/quadem.ibek.support.yaml --output schemas/quadem.ibek.ioc.schema.json

echo making ioc based on ibek-mo-ioc-01.yaml
EPICS_ROOT=`pwd`/epics ibek runtime generate iocs/ibek-mo-ioc-01.yaml support/asyn.ibek.support.yaml support/motorSim.ibek.support.yaml
mv `pwd`/epics/{runtime,opi}/* `pwd`/outputs/motorSim
Expand All @@ -52,4 +55,8 @@ mv `pwd`/epics/{runtime,opi}/* `pwd`/outputs/ipac

echo making ioc based on gauges support yaml
EPICS_ROOT=`pwd`/epics ibek runtime generate iocs/gauges.ibek.ioc.yaml support/asyn.ibek.support.yaml support/gauges.ibek.support.yaml
mv `pwd`/epics/{runtime,opi}/* `pwd`/outputs/gauges
mv `pwd`/epics/{runtime,opi}/* `pwd`/outputs/gauges

echo making ioc based on quadem support yaml
EPICS_ROOT=`pwd`/epics ibek runtime generate iocs/quadem.ibek.ioc.yaml support/ADCore.ibek.support.yaml support/quadem.ibek.support.yaml
mv `pwd`/epics/{runtime,opi}/* `pwd`/outputs/quadem
20 changes: 20 additions & 0 deletions tests/samples/iocs/quadem.ibek.ioc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# yaml-language-server: $schema=../schemas/quadem.ibek.ioc.schema.json

ioc_name: "{{ __utils__.get_env('IOC_NAME') }}"
description: Example TetrAMM for BL03I

entities:
- type: quadEM.TetrAMM
PORT: XBPM1.DRV
P: BL03I-EA-XBPM-01
R: ":DRV:"
QSIZE: 20
IP: "172.23.103.85:10001"

- type: quadEM.Plugins
DEVICE: XBPM1.DRV
PORTPREFIX: XBPM1
STAT_NCHAN: 1000
STAT_XSIZE: 10000

# - { type: ADCore.NDStats, PORT: "{{PORTPREFIX}}.STATS.I1", R: Cur1 }
28 changes: 28 additions & 0 deletions tests/samples/outputs/quadem/index.bob
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<display version="2.0.0">
<name>test-bad-db-ioc</name>
<x>0</x>
<y use_class="true">0</y>
<width>10</width>
<height>35</height>
<grid_step_x>4</grid_step_x>
<grid_step_y>4</grid_step_y>
<widget type="label" version="2.0.0">
<name>Title</name>
<class>TITLE</class>
<text>test-bad-db-ioc</text>
<x use_class="true">0</x>
<y use_class="true">0</y>
<width>10</width>
<height>25</height>
<font use_class="true">
<font name="Header 1" family="Liberation Sans" style="BOLD" size="22.0">
</font>
</font>
<foreground_color use_class="true">
<color name="Text" red="0" green="0" blue="0">
</color>
</foreground_color>
<transparent use_class="true">true</transparent>
<horizontal_alignment>1</horizontal_alignment>
</widget>
</display>
9 changes: 9 additions & 0 deletions tests/samples/outputs/quadem/ioc.subst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#############################################################################
# DB substitution file generated by http://github.com/epics-containers/ibek #
#############################################################################

file "$(QUADEM)/db/TetrAMM.template" {
pattern
{ "P", "R", "PORT" }
{ "BL03I-EA-XBPM-01", ":DRV:", "XBPM1.DRV" }
}
10 changes: 10 additions & 0 deletions tests/samples/outputs/quadem/st.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# EPICS IOC Startup Script generated by https://github.com/epics-containers/ibek

cd "/epics/ioc"
dbLoadDatabase dbd/ioc.dbd
ioc_registerRecordDeviceDriver pdbbase


dbLoadRecords /epics/runtime/ioc.db
iocInit

Loading

0 comments on commit 5f4f435

Please sign in to comment.