Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add enumerated types #92

Merged
merged 5 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions .github/workflows/code.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,6 @@ jobs:
- name: Lint
run: tox -e pre-commit,mypy

system-test:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Run System Test
run: ./tests/sys-test.sh

test:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
strategy:
Expand Down
37 changes: 23 additions & 14 deletions src/ibek/ioc.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,27 @@
from __future__ import annotations

import builtins
from enum import Enum
from typing import Any, Dict, Literal, Sequence, Tuple, Type, Union

from pydantic import Field, RootModel, create_model, field_validator, model_validator
from pydantic.fields import FieldInfo

from .globals import BaseSettings, render_with_utils
from .support import Definition, IdArg, ObjectArg, Support
from .support import Definition, EnumArg, IdArg, ObjectArg, Support

id_to_entity: Dict[str, Entity] = {}


class EnumVal(Enum):
"""
An enum that is printed as its name only
"""

def __str__(self):
return self.name


class Entity(BaseSettings):
"""
A baseclass for all generated Entity classes.
Expand All @@ -31,16 +41,6 @@ class Entity(BaseSettings):
def add_ibek_attributes(cls, entity: Entity):
"""
Whole Entity model validation

TODO at present an object reference to an ID where the referred object violates
schema is seen as "KeyError: "object XXX not found in [...]" which hides the
schema violation error.

This could potentially be fixed by doing the validation here instead
(removing extra:forbid from the model_config). BUT, at present passing
the info arg to this function receives a dict of the IOC instance
that created this entity, not the entity itself. This may be a
pydantic bug?
"""

# find the id field in this Entity if it has one
Expand Down Expand Up @@ -69,10 +69,10 @@ def make_entity_model(definition: Definition, support: Support) -> Type[Entity]:
Create an Entity Model from a Definition instance and a Support instance.
"""

def add_arg(name, typ, description, default):
def add_arg(name, typ, description, default, options=None):
args[name] = (
typ,
FieldInfo(description=description, default=default),
FieldInfo(description=description, default=default, options=options),
)

args: Dict[str, Tuple[type, Any]] = {}
Expand Down Expand Up @@ -101,10 +101,19 @@ def lookup_instance(cls, id):
elif isinstance(arg, IdArg):
arg_type = str

elif isinstance(arg, EnumArg):
# Pydantic uses the values of the Enum as the options in the schema.
# Here we arrange for the keys to be in the schema (what a user supplies)
# but the values to be what is rendered when jinja refers to the enum
enum_swapped = {}
for k, v in arg.values.items():
enum_swapped[str(v) if v else str(k)] = k
val_enum = EnumVal(arg.name, enum_swapped) # type: ignore
arg_type = val_enum

else:
# arg.type is str, int, float, etc.
arg_type = getattr(builtins, arg.type)

default = getattr(arg, "default", None)
add_arg(arg.name, arg_type, arg.description, default)

Expand Down
12 changes: 12 additions & 0 deletions src/ibek/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,18 @@ class IdArg(Arg):
default: Optional[str] = None


class EnumArg(Arg):
"""An argument with an enum value"""

type: Literal["enum"] = "enum"
default: Optional[Any] = None

values: Dict[str, Any] = Field(
description="provides a list of values to make this argument an Enum",
default=(None),
)


class Database(BaseSettings):
"""
A database file that should be loaded by the startup script and its args
Expand Down
56 changes: 0 additions & 56 deletions tests/generate_samples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,59 +30,3 @@ ibek build-startup yaml/objects.ibek.ioc.yaml yaml/objects.ibek.support.yaml --o

# echo making ioc based on mutiple support yaml
ibek build-startup yaml/all.ibek.ioc.yaml yaml/objects.ibek.support.yaml yaml/all.ibek.support.yaml --out outputs/all.st.cmd --db-out outputs/all.make_db.sh


# echo making the global ioc schema using all support yaml in ibek-defs
# ibek ioc-schema ./*/*.ibek.support.yaml ${SAMPLES_DIR}/schemas/all.ibek.support.schema.json



# echo making the epics definition schema
# ibek ioc-schema ${DEFS}/_global/epics.ibek.support.yaml $SAMPLES_DIR/schemas/epics.ibek.support.schema.json
# echo making the pmac support module definition schema
# ibek ioc-schema ${DEFS}/pmac/pmac.ibek.support.yaml $SAMPLES_DIR/schemas/pmac.ibek.entities.schema.json
# echo making the asyn support module definition schema
# ibek ioc-schema ${DEFS}/asyn/asyn.ibek.support.yaml $SAMPLES_DIR/schemas/asyn.ibek.entities.schema.json
# echo making a container definition schema
# ibek ioc-schema ${DEFS}/asyn/asyn.ibek.support.yaml ${DEFS}/pmac/pmac.ibek.support.yaml $SAMPLES_DIR/schemas/container.ibek.entities.schema.json
# echo making a schema for bl45p-mo-ioc-04
# ibek ioc-schema ${DEFS}/_global/epics.ibek.support.yaml ${DEFS}/pmac/pmac.ibek.support.yaml $SAMPLES_DIR/schemas/bl45p-mo-ioc-04.ibek.entities.schema.json

# echo making bl45p-mo-ioc-02
# ibek build-startup ${SAMPLES_DIR}/yaml/bl45p-mo-ioc-02.ibek.ioc.yaml ${DEFS}/pmac/pmac.ibek.support.yaml --out /tmp/ioc/st.cmd --db-out /tmp/ioc/make_db.sh
# cp /tmp/ioc/st.cmd ${SAMPLES_DIR}/boot_scripts/
# echo making bl45p-mo-ioc-03
# ibek build-startup ${SAMPLES_DIR}/yaml/bl45p-mo-ioc-03.ibek.ioc.yaml ${DEFS}/pmac/pmac.ibek.support.yaml ${DEFS}/asyn/asyn.ibek.support.yaml --out /tmp/ioc/st.cmd --db-out /tmp/ioc/make_db.sh
# cp /tmp/ioc/st.cmd ${SAMPLES_DIR}/boot_scripts/stbl45p-mo-ioc-03
# echo making bl45p-mo-ioc-04
# ibek build-startup ${SAMPLES_DIR}/yaml/bl45p-mo-ioc-04.ibek.ioc.yaml ${DEFS}/_global/epics.ibek.support.yaml ${DEFS}/pmac/pmac.ibek.support.yaml --out /tmp/ioc/st.cmd --db-out /tmp/ioc/make_db.sh
# cp /tmp/ioc/st.cmd ${SAMPLES_DIR}/boot_scripts/stbl45p-mo-ioc-04
# echo making test-ioc
# ibek build-startup ${SAMPLES_DIR}/example-ibek-config/ioc.yaml ${DEFS}/_global/epics.ibek.support.yaml ${DEFS}/_global/devIocStats.ibek.support.yaml --out /tmp/ioc/st.cmd --db-out /tmp/ioc/make_db.sh
# cp /tmp/ioc/st.cmd ${SAMPLES_DIR}/boot_scripts/test.ioc.cmd
# cp /tmp/ioc/make_db.sh ${SAMPLES_DIR}/boot_scripts/test.ioc.make_db.sh

# echo making SR-RF-IOC-08 IOC
# ibek build-startup ${SAMPLES_DIR}/example-srrfioc08/SR-RF-IOC-08.ibek.ioc.yaml ${DEFS}/*/*.support.yaml --out /tmp/ioc/st.cmd --db-out /tmp/ioc/make_db.sh
# cp /tmp/ioc/st.cmd ${SAMPLES_DIR}/example-srrfioc08
# cp /tmp/ioc/make_db.sh ${SAMPLES_DIR}/example-srrfioc08

# echo making values_test IOC
# ibek build-startup ${SAMPLES_DIR}/values_test/values.ibek.ioc.yaml ${DEFS}/*/*.support.yaml --out /tmp/ioc/st.cmd --db-out /tmp/ioc/make_db.sh
# cp /tmp/ioc/st.cmd ${SAMPLES_DIR}/values_test

# PYDANTIC_DIR=${SAMPLES_DIR}/pydantic
# cd $PYDANTIC_DIR

# echo making the support yaml schema
# ibek ibek-schema ${PYDANTIC_DIR}/../schemas/ibek.defs.schema.json

# echo making the pydantic test definition schema
# ibek ioc-schema ${PYDANTIC_DIR}/test.ibek.support.yaml $PYDANTIC_DIR/test.ibek.ioc.schema.json

# echo making the pydantic test ioc startup script
# ibek build-startup ${PYDANTIC_DIR}/test.ibek.ioc.yaml ${PYDANTIC_DIR}/test.ibek.support.yaml --out $PYDANTIC_DIR/st.cmd --db-out $PYDANTIC_DIR/make_db.sh




6 changes: 6 additions & 0 deletions tests/samples/outputs/all.st.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,16 @@ TestValues Ref1.127.0.0.1

# testPreInit identifier TestValue
testPreInit A Consumer test_value:
The value of my_inferred_enum is third
The value of clock_rate is dummy
testPreInit Another Consumer test_value:
The value of my_inferred_enum is hello
The value of clock_rate is 1

dbLoadRecords /tmp/ioc.db
iocInit


testPostInit A Consumer test_value:
testPostInit Another Consumer test_value:

46 changes: 46 additions & 0 deletions tests/samples/schemas/ibek.support.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@
},
{
"$ref": "#/$defs/IdArg"
},
{
"$ref": "#/$defs/EnumArg"
}
]
},
Expand Down Expand Up @@ -214,6 +217,49 @@
"title": "Definition",
"type": "object"
},
"EnumArg": {
"additionalProperties": false,
"description": "An argument with an enum value",
"properties": {
"type": {
"const": "enum",
"default": "enum",
"title": "Type"
},
"name": {
"description": "Name of the argument that the IOC instance should pass",
"title": "Name",
"type": "string"
},
"description": {
"description": "Description of what the argument will be used for",
"title": "Description",
"type": "string"
},
"default": {
"anyOf": [
{},
{
"type": "null"
}
],
"default": null,
"title": "Default"
},
"values": {
"default": null,
"description": "provides a list of values to make this argument an Enum",
"title": "Values",
"type": "object"
}
},
"required": [
"name",
"description"
],
"title": "EnumArg",
"type": "object"
},
"EnvironmentVariable": {
"additionalProperties": false,
"description": "An environment variable that should be set in the startup script",
Expand Down
77 changes: 76 additions & 1 deletion tests/samples/schemas/multiple.ibek.ioc.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@
],
"title": "EntityModel"
},
"clock_rate": {
"enum": [
"1Hz",
"2Hz",
"5Hz",
"10Hz",
"dummy"
],
"title": "clock_rate",
"type": "string"
},
"module_AllObject": {
"additionalProperties": false,
"properties": {
Expand All @@ -47,8 +58,44 @@
"title": "Name",
"type": "string"
},
"my_int_enum": {
"allOf": [
{
"$ref": "#/$defs/my_int_enum"
}
],
"default": 2,
"description": "integer enumerated type"
},
"my_mixed_enum": {
"allOf": [
{
"$ref": "#/$defs/my_mixed_enum"
}
],
"default": null,
"description": "mixed enumerated type"
},
"my_inferred_enum": {
"allOf": [
{
"$ref": "#/$defs/my_inferred_enum"
}
],
"default": null,
"description": "integer enumerated type with inferred value"
},
"clock_rate": {
"allOf": [
{
"$ref": "#/$defs/clock_rate"
}
],
"default": null,
"description": "demonstrates use of VME clock rates as an enum\nthis is an example of an 'illegal' python enum\n"
},
"my_str": {
"default": "default",
"default": null,
"description": "string arg",
"title": "My Str",
"type": "string"
Expand Down Expand Up @@ -86,6 +133,34 @@
"title": "module_AllObject",
"type": "object"
},
"my_inferred_enum": {
"enum": [
"first",
"second",
"third"
],
"title": "my_inferred_enum",
"type": "string"
},
"my_int_enum": {
"enum": [
"all_ahead",
"full_speed",
"ramming_speed",
"stop"
],
"title": "my_int_enum",
"type": "string"
},
"my_mixed_enum": {
"enum": [
"first",
"second",
"third"
],
"title": "my_mixed_enum",
"type": "string"
},
"object_module_Consumer": {
"additionalProperties": false,
"properties": {
Expand Down
10 changes: 10 additions & 0 deletions tests/samples/yaml/all.ibek.ioc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ entities:
- type: module.AllObject
name: A Consumer
my_object: Ref1
my_int_enum: full_speed
my_inferred_enum: third
clock_rate: dummy

- type: module.AllObject
name: Another Consumer
my_object: Ref1
my_int_enum: all_ahead
my_inferred_enum: first
clock_rate: 2Hz

- type: object_module.ConsumerTwo
name: Disabled Consumer
Expand Down
Loading