Skip to content

Commit 262fd6e

Browse files
adedamola-sodeOCopping
authored andcommitted
Seperated dataclasses in datatypes from guibuilder
Rebasing phoebus class redesign with changes made to testing and dependencies.
1 parent 8e56985 commit 262fd6e

File tree

7 files changed

+204
-174
lines changed

7 files changed

+204
-174
lines changed

.pdm-python

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/workspaces/guibuilder/.venv/bin/python

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.12

src/phoebus_guibuilder/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@
88

99
from ._version import __version__
1010

11-
__all__ = ["__version__"]
11+
from phoebus_guibuilder.datatypes import Beamline, Component, Entry
12+
from phoebus_guibuilder.guibuilder import Guibuilder
13+
14+
__all__ = ["__version__", "Beamline", "Component", "Entry", "Guibuilder"]

src/phoebus_guibuilder/__main__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
from argparse import ArgumentParser
44
from collections.abc import Sequence
5+
from fileinput import filename
56

6-
from . import __version__, guibuilder
7+
from . import __version__
8+
from .guibuilder import Guibuilder
79

810
__all__ = ["main"]
911

@@ -20,7 +22,8 @@ def main(args: Sequence[str] | None = None) -> None:
2022
)
2123
_args = parser.parse_args(args)
2224

23-
guibuilder.main(_args.filename)
25+
gb = Guibuilder(_args.filename)
26+
gb.extract_from_create_gui()
2427

2528

2629
if __name__ == "__main__":
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import re
2+
from dataclasses import dataclass
3+
4+
5+
@dataclass
6+
class Beamline:
7+
dom: str
8+
desc: str
9+
10+
11+
@dataclass
12+
class Entry:
13+
type: str
14+
DESC: str | None
15+
P: str
16+
M: str | None
17+
R: str | None
18+
19+
20+
@dataclass
21+
class Component:
22+
name: str
23+
desc: str
24+
prefix: str
25+
filename: str | None = None
26+
27+
def __post_init__(self):
28+
self._extract_p_and_r()
29+
30+
def __repr__(self) -> str:
31+
return f"Component(name={self.name}, desc={self.desc}, \
32+
prefix={self.P}, suffix={self.R}, filename={self.filename})"
33+
34+
def _extract_p_and_r(self):
35+
pattern = re.compile(
36+
r"""
37+
^ # start of string
38+
(?= # lookahead to ensure the following pattern matches
39+
[A-Za-z0-9-]{14,16} # match 14 to 16 alphanumeric characters or hyphens
40+
[:A-Za-z0-9]* # match zero or more colons or alphanumeric characters
41+
[.A-Za-z0-9] # match a dot or alphanumeric character
42+
)
43+
(?!.*--) # negative lookahead to ensure no double hyphens
44+
(?!.*:\..) # negative lookahead to ensure no colon followed by a dot
45+
( # start of capture group 1
46+
(?:[A-Za-z0-9]{2,5}-){3} # match 2 to 5 alphanumeric characters followed
47+
# by a hyphen, repeated 3 times
48+
[\d]* # match zero or more digits
49+
[^:]? # match zero or one non-colon character
50+
)
51+
(?::([a-zA-Z0-9:]*))? # match zero or one colon followed by zero or more
52+
# alphanumeric characters or colons (capture group 2)
53+
(?:\.([a-zA-Z0-9]+))? # match zero or one dot followed by one or more
54+
# alphanumeric characters (capture group 3)
55+
$ # end of string
56+
""",
57+
re.VERBOSE,
58+
)
59+
60+
match = re.match(pattern, self.prefix)
61+
if match:
62+
self.P: str = match.group(1)
63+
self.R: str = match.group(2)
64+
# TODO: Is this needed?
65+
self.attribute: str | None = match.group(3)
66+
else:
67+
raise AttributeError(f"No valid PV prefix found for {self.name}.")
Lines changed: 123 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -1,171 +1,129 @@
11
import os
2-
import pprint
32
import re
4-
from dataclasses import dataclass
3+
import sys
54

65
import yaml
76

8-
pp = pprint.PrettyPrinter()
9-
10-
11-
@dataclass
12-
class Beamline:
13-
dom: str
14-
desc: str
15-
16-
17-
@dataclass
18-
class Entry:
19-
type: str
20-
DESC: str | None
21-
P: str
22-
M: str | None
23-
R: str | None
24-
25-
26-
@dataclass
27-
class Component:
28-
name: str
29-
desc: str
30-
prefix: str
31-
filename: str | None = None
32-
33-
def __post_init__(self):
34-
self._extract_p_and_r()
35-
36-
def __repr__(self) -> str:
37-
return f"Component(name={self.name}, desc={self.desc}, prefix={self.P}, \
38-
suffix={self.R}, filename={self.filename})"
39-
40-
def _extract_p_and_r(self):
41-
pattern = re.compile(
42-
r"""
43-
^ # start of string
44-
(?= # lookahead to ensure the following pattern matches
45-
[A-Za-z0-9-]{14,16} # match 14 to 16 alphanumeric characters or hyphens
46-
[:A-Za-z0-9]* # match zero or more colons or alphanumeric characters
47-
[.A-Za-z0-9] # match a dot or alphanumeric character
48-
)
49-
(?!.*--) # negative lookahead to ensure no double hyphens
50-
(?!.*:\..) # negative lookahead to ensure no colon followed by a dot
51-
( # start of capture group 1
52-
(?:[A-Za-z0-9]{2,5}-){3} # match 2 to 5 alphanumeric characters followed
53-
# by a hyphen, repeated 3 times
54-
[\d]* # match zero or more digits
55-
[^:]? # match zero or one non-colon character
56-
)
57-
(?::([a-zA-Z0-9:]*))? # match zero or one colon followed by zero or more
58-
# alphanumeric characters or colons (capture group 2)
59-
(?:\.([a-zA-Z0-9]+))? # match zero or one dot followed by one or more
60-
# alphanumeric characters (capture group 3)
61-
$ # end of string
62-
""",
63-
re.VERBOSE,
64-
)
65-
66-
match = re.match(pattern, self.prefix)
67-
if match:
68-
self.P: str = match.group(1)
69-
self.R: str = match.group(2)
70-
# TODO: Is this needed?
71-
self.attribute: str | None = match.group(3)
72-
else:
73-
raise AttributeError(f"No valid PV prefix found for {self.name}.")
74-
75-
76-
def main(filename: str):
77-
components: list[Component] = [] # TODO Manage global lists better
78-
79-
with open(filename) as f:
80-
conf = yaml.safe_load(f)
81-
82-
bl: dict[str, str] = conf["beamline"]
83-
comps: dict[str, dict[str, str]] = conf["components"]
84-
85-
beamline = Beamline(**bl)
86-
87-
for key, comp in comps.items():
88-
components.append(Component(key, **comp))
89-
90-
print("BEAMLINE:")
91-
pp.pprint(beamline)
92-
93-
print("")
94-
print("COMPONENTS")
95-
pp.pprint(components)
96-
97-
98-
#####################################################
99-
# TODO Functionality should be in phoebusguibuilder class
100-
# class Phoebusguibuilder(beamline: Beamline, components: list[Component]):
101-
102-
103-
def find_services_folders(beamline: Beamline, components: list[Component]):
104-
services_directory = (
105-
beamline.dom + "-services/services"
106-
) # TODO: rm hardcoding, map to services.
107-
path = f"/dls/science/users/uns32131/{services_directory}"
108-
files = os.listdir(path)
109-
110-
# Attempting to match the prefix to the files in the services directory
111-
pattern = "^(.*)-(.*)-(.*)"
112-
113-
for component in components:
114-
domain: re.Match[str] | None = re.match(pattern, component.P)
115-
assert domain is not None, "Empty Prefix Field"
116-
117-
for file in files:
118-
match = re.match(pattern, file)
119-
if match:
120-
if match.group(1) == domain.group(1).lower():
121-
if os.path.exists(f"{path}/{file}/config/ioc.yaml"):
122-
valid_entities = extract_valid_entities(
123-
ioc_yaml=f"{path}/{file}/config/ioc.yaml",
124-
component=component,
7+
from phoebus_guibuilder.datatypes import Beamline, Component, Entry
8+
9+
10+
class Guibuilder:
11+
"""
12+
This class provides the functionality to process the required
13+
create_gui.yaml file into screens mapped from ioc.yaml and
14+
gui_map.yaml files.
15+
16+
"""
17+
18+
def __init__(self, create_gui: str):
19+
self.components: list[Component]
20+
21+
self.beamline: Beamline
22+
23+
self.valid_entities: list[Entry] = []
24+
25+
self.create_gui: str = create_gui
26+
27+
def extract_from_create_gui(
28+
self,
29+
):
30+
"""
31+
Extracts from the create_gui.yaml file to generate
32+
the required Beamline and components structures.
33+
"""
34+
35+
with open(self.create_gui) as f:
36+
conf = yaml.safe_load(f)
37+
38+
bl: dict[str, str] = conf["beamline"]
39+
comps: dict[str, dict[str, str]] = conf["components"]
40+
41+
self.beamline = Beamline(**bl)
42+
43+
for key, comp in comps.items():
44+
self.components.append(Component(key, **comp))
45+
46+
def find_services_folders(
47+
self,
48+
):
49+
"""
50+
Finds the related folders in the services directory
51+
and extracts the related entites with the matching prefixes
52+
"""
53+
54+
services_directory = (
55+
self.beamline.dom + "-services/services"
56+
) # TODO: rm hardcoding, map to services.
57+
path = f"{services_directory}"
58+
files = os.listdir(path)
59+
60+
# Attempting to match the prefix to the files in the services directory
61+
pattern = "^(.*)-(.*)-(.*)"
62+
63+
for component in self.components:
64+
domain: re.Match[str] | None = re.match(pattern, component.P)
65+
assert domain is not None, "Empty Prefix Field"
66+
67+
for file in files:
68+
match = re.match(pattern, file)
69+
if match:
70+
if match.group(1) == domain.group(1).lower():
71+
if os.path.exists(f"{path}/{file}/config/ioc.yaml"):
72+
self.extract_valid_entities(
73+
ioc_yaml=f"{path}/{file}/config/ioc.yaml",
74+
component=component,
75+
)
76+
else:
77+
print(f"No ioc.yaml file for service: {file}")
78+
79+
def extract_valid_entities(self, ioc_yaml: str, component: Component):
80+
"""
81+
Extracts the entities in ioc.yaml matching the defined prefix
82+
"""
83+
84+
entities: list[dict[str, str]] = []
85+
component_match = f"{component.P}:{component.R}"
86+
87+
with open(ioc_yaml) as ioc:
88+
conf = yaml.safe_load(ioc)
89+
entities = conf["entities"]
90+
for entity in entities:
91+
if (
92+
"P" in entity.keys() and entity["P"] == component_match
93+
): # the suffix could be M, could be R
94+
self.valid_entities.append(
95+
Entry(
96+
type=entity["type"],
97+
DESC=None,
98+
P=entity["P"],
99+
M=None,
100+
R=None,
125101
)
126-
return valid_entities
127-
else:
128-
print(f"No ioc.yaml file for service: {file}")
129-
130-
131-
def extract_valid_entities(ioc_yaml: str, component: Component) -> list[Entry]:
132-
print(type(ioc_yaml))
133-
entities: list[dict[str, str]] = []
134-
valid_entities: list[Entry] = []
135-
component_match = f"{component.P}:{component.R}"
136-
with open(ioc_yaml) as ioc:
137-
conf = yaml.safe_load(ioc)
138-
entities = conf["entities"]
139-
for entity in entities:
140-
if (
141-
"P" in entity.keys() and entity["P"] == component_match
142-
): # the suffix could be M, could be R
143-
valid_entities.append(
144-
Entry(type=entity["type"], DESC=None, P=entity["P"], M=None, R=None)
145-
)
146-
147-
return valid_entities
148-
149-
150-
def gui_map(entrys: list[Entry]):
151-
gui_map = "/dls/science/users/uns32131/BLGui/BLGuiApp/opi/bob/gui_map.yaml"
152-
153-
with open(gui_map) as map:
154-
conf = yaml.safe_load(map)
155-
156-
for entry in entrys:
157-
print(entry.type)
158-
if conf[entry.type]:
159-
print(
160-
conf[entry.type]["file"]
161-
) # Find correct .bob file, and injet macros
162-
# TODO: create a copy of the file, and replace the required macros
163-
# TODO: return the file to guibuilder
164-
165-
else:
166-
print("No BOB available")
167-
168-
169-
# find_services_folders()
170-
# print(valid_entities)
171-
# gui_map(valid_entities)
102+
)
103+
104+
def gui_map(self, entrys: list[Entry]):
105+
"""
106+
Maps the valid entities from the ioc.yaml file
107+
to the required screen in gui_map.yaml
108+
"""
109+
110+
gui_map = "/BLGui/BLGuiApp/opi/bob/gui_map.yaml"
111+
112+
with open(gui_map) as map:
113+
conf = yaml.safe_load(map)
114+
115+
for entry in entrys:
116+
print(entry.type)
117+
if conf[entry.type]:
118+
print(
119+
conf[entry.type]["file"]
120+
) # Find correct .bob file, and injet macros
121+
# TODO: create a copy of the file, and replace the required macros
122+
# TODO: return the file to guibuilder
123+
124+
else:
125+
print("No BOB available")
126+
127+
128+
if __name__ == "__main__":
129+
Guibuilder(sys.argv[1])

0 commit comments

Comments
 (0)