mi-mx 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mi_mx-0.1.0.dist-info/METADATA +142 -0
- mi_mx-0.1.0.dist-info/RECORD +55 -0
- mi_mx-0.1.0.dist-info/WHEEL +5 -0
- mi_mx-0.1.0.dist-info/entry_points.txt +2 -0
- mi_mx-0.1.0.dist-info/licenses/LICENSE +21 -0
- mi_mx-0.1.0.dist-info/top_level.txt +1 -0
- mx/__init__.py +1 -0
- mx/__main__.py +81 -0
- mx/actions/__init__.py +0 -0
- mx/actions/action.py +76 -0
- mx/actions/extract.py +98 -0
- mx/actions/flow.py +34 -0
- mx/actions/gate.py +95 -0
- mx/actions/project.py +122 -0
- mx/actions/rank_restrict.py +126 -0
- mx/actions/read.py +108 -0
- mx/actions/rename.py +88 -0
- mx/actions/restrict.py +208 -0
- mx/actions/scalar_switch.py +96 -0
- mx/actions/select.py +219 -0
- mx/actions/set_action.py +116 -0
- mx/actions/signal.py +27 -0
- mx/actions/single_select.py +12 -0
- mx/actions/traverse.py +314 -0
- mx/actions/zero_one_card_select.py +12 -0
- mx/activity.py +58 -0
- mx/assigner_state_machine.py +13 -0
- mx/completion_event.py +44 -0
- mx/db_names.py +6 -0
- mx/deprecated/__init__.py +0 -0
- mx/deprecated/bridge.py +42 -0
- mx/dispatched_event.py +37 -0
- mx/domain.py +231 -0
- mx/exceptions.py +47 -0
- mx/file_names.py +6 -0
- mx/initial_states.py +75 -0
- mx/instance.py +12 -0
- mx/instance_set.py +46 -0
- mx/interaction_event.py +91 -0
- mx/lifecycle_state_machine.py +26 -0
- mx/log.conf +42 -0
- mx/method.py +183 -0
- mx/multiple_assigner_state_machine.py +17 -0
- mx/mx_logger.py +52 -0
- mx/mxtypes.py +18 -0
- mx/operation.py +50 -0
- mx/rvname.py +39 -0
- mx/scenario.py +173 -0
- mx/scenarios/__init__.py +0 -0
- mx/scenarios/ev_scenario_REF.py +115 -0
- mx/single_assigner_state_machine.py +13 -0
- mx/state_activity.py +23 -0
- mx/state_machine.py +222 -0
- mx/system.py +62 -0
- mx/xe.py +109 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mi-mx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Shlaer-Mellor Executable UML Model Execution Environment
|
|
5
|
+
Author-email: Leon Starr <leon_starr@modelint.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 Leon Starr
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: repository, https://github.com/modelint/model-execution
|
|
29
|
+
Project-URL: documentation, https://github.com/modelint/model-execution/wiki
|
|
30
|
+
Keywords: shlaer-mellor,metamodel,executable uml,mbse,xuml,xtuml,platform independent,sysml
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Programming Language :: Python
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Requires-Python: >=3.11
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
License-File: LICENSE
|
|
37
|
+
Requires-Dist: xcm-parser
|
|
38
|
+
Requires-Dist: xsm-parser
|
|
39
|
+
Requires-Dist: mi-configurator
|
|
40
|
+
Requires-Dist: mi-pyral
|
|
41
|
+
Requires-Dist: sip-parser
|
|
42
|
+
Requires-Dist: PyYAML
|
|
43
|
+
Requires-Dist: tomli; python_version < "3.13"
|
|
44
|
+
Provides-Extra: build
|
|
45
|
+
Requires-Dist: build; extra == "build"
|
|
46
|
+
Requires-Dist: twine; extra == "build"
|
|
47
|
+
Provides-Extra: dev
|
|
48
|
+
Requires-Dist: bump2version; extra == "dev"
|
|
49
|
+
Requires-Dist: pytest; extra == "dev"
|
|
50
|
+
Dynamic: license-file
|
|
51
|
+
|
|
52
|
+
## Model execution engine
|
|
53
|
+
|
|
54
|
+
# Loading a System
|
|
55
|
+
|
|
56
|
+
We supply mx with a system to run, an initial context, and a scenario to execute
|
|
57
|
+
|
|
58
|
+
`mx -s elevator -c three_bank1 -x lobby_to_three`
|
|
59
|
+
|
|
60
|
+
The required arguments are:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
-s / --system
|
|
64
|
+
-c / --context
|
|
65
|
+
-x / --scenario
|
|
66
|
+
```
|
|
67
|
+
Optional arguments are:
|
|
68
|
+
```
|
|
69
|
+
-D / --debug
|
|
70
|
+
-V / --version
|
|
71
|
+
```
|
|
72
|
+
### -s or --system
|
|
73
|
+
|
|
74
|
+
Here you specify the name of a directory containing the following components:
|
|
75
|
+
```
|
|
76
|
+
system_name/
|
|
77
|
+
system.ral - Metamodel produced by xuml-populate (can be named anything you like, with any extension)
|
|
78
|
+
db_types/ - Directory of yaml files mapping user types to TclRAL system types (must have this name)
|
|
79
|
+
domain_name1_types.yaml - Each yaml file name must begin with domain name or alias
|
|
80
|
+
domain_name2_types.yaml
|
|
81
|
+
domain_name3_types.yaml
|
|
82
|
+
```
|
|
83
|
+
Example:
|
|
84
|
+
```
|
|
85
|
+
elevator/ - Any name ok
|
|
86
|
+
elevator.ral - Any name ok, ev.txt, mm3.ral, etc.
|
|
87
|
+
db_types/ - Must be this name
|
|
88
|
+
evman_types.yaml - could have named it evman.yaml, elevator_management.yaml,
|
|
89
|
+
- evman_user_to_tcl_types.yaml, etc
|
|
90
|
+
- but prefix must be either evman (alias) or elevator_management
|
|
91
|
+
- case insensitive, so EVMAN also okay
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
The `system.ral` file is actually just a text file serialization of a TclRAL database.
|
|
96
|
+
Specifically, it is a populated SM Metamodel that you can generate with the xuml-populate tool. The metamodel is populated with one or more modeled domains. In our example, this is the elevator management domain. We could also add in the transport and sio domains defined in the elevator case study if we like, but for now we just have the elevator management (EVMAN) domain.
|
|
97
|
+
|
|
98
|
+
### -c or --context
|
|
99
|
+
|
|
100
|
+
This is yet another directory that contains a single initial context per domain. It is structured like this:
|
|
101
|
+
```
|
|
102
|
+
context_name/ - Any name that describes your aggregate initial context
|
|
103
|
+
domain1.sip - One initial population per domain in the system
|
|
104
|
+
domain2.sip
|
|
105
|
+
domain3.sip
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
An initial context is a population of initial instances and states. That way we can begin a scenario with a known set of instances in known states for those instances with modeled lifecycles (state machines).
|
|
109
|
+
|
|
110
|
+
Each domain's context is defined with a single *.sip (scenario instance population) file.
|
|
111
|
+
|
|
112
|
+
In our example we have only one domain population:
|
|
113
|
+
```
|
|
114
|
+
three_bank/
|
|
115
|
+
evman_three_bank1.sip
|
|
116
|
+
```
|
|
117
|
+
We named it 'three_bank' since we are using three separate elevator banks, 'lower floors',
|
|
118
|
+
'express', and 'freight'.
|
|
119
|
+
|
|
120
|
+
If we were running the trans and sio domains, we could have supplied one *.sip file for each
|
|
121
|
+
in this directory as well.
|
|
122
|
+
|
|
123
|
+
Note that each *.sip file name must begin with a domain name or alias.
|
|
124
|
+
|
|
125
|
+
See the sip-parser repository wiki for details on the grammar.
|
|
126
|
+
|
|
127
|
+
MX invokes the sip-parser when it loads the file and will throw any errors it finds in the process.
|
|
128
|
+
|
|
129
|
+
### -x or --scenario
|
|
130
|
+
|
|
131
|
+
Finally, we provide a sequence of interactions in the form of a scenario file to run against the populated system.
|
|
132
|
+
|
|
133
|
+
This is specified in a *.scn file. *(grammar/parser yet to be designed)*
|
|
134
|
+
|
|
135
|
+
You use the sequence of interactions to drive validation or exploration.
|
|
136
|
+
|
|
137
|
+
For example, you might specify that a cabin going up is requested from the lobby floor
|
|
138
|
+
in the lower floors bank. It arrives, floor 3 is requested, the doors close, the cabin transits and then arrives at that floor with the doors opening.
|
|
139
|
+
|
|
140
|
+
This scenario will result in the appropriate events signaled in the loaded system and collect any responses and status updates of interest along the way.
|
|
141
|
+
|
|
142
|
+
You can define multiple initial contexts for a system and run the same or different scenarios against each.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
mi_mx-0.1.0.dist-info/licenses/LICENSE,sha256=hIfBeynReqzakO4zYIDreBqfwygbbdpAKwZMBFOr8BU,1067
|
|
2
|
+
mx/__init__.py,sha256=aOHawL1zuHMfBWKXqwUkXcW96oXLNCY-CXdHDqkz4g4,18
|
|
3
|
+
mx/__main__.py,sha256=sKbDVOJVT3QyMnUl2mafZ91IpWHK9ZnHft4W3i51Ns0,2831
|
|
4
|
+
mx/activity.py,sha256=B24XewtfWZZ8fdn6S8oNJ8JhNm04Dzz5S9x-aJKoNA8,1617
|
|
5
|
+
mx/assigner_state_machine.py,sha256=e4nQq7DRVl9ekk7p_uKRG2gRaRgEZyo9_RrDq0T5LKY,333
|
|
6
|
+
mx/completion_event.py,sha256=iI4RtNQ_T3C2Tt1VjpP5Z4xYzDNKYODRt9I_1oBMxh0,919
|
|
7
|
+
mx/db_names.py,sha256=coUqzTcv7vx3WqeSoJxLtffg9vFxCvO9MbIVQkOWbro,381
|
|
8
|
+
mx/dispatched_event.py,sha256=CXLnY_qg_CsZZi1GIf8cBzX5yoTcd4iQJ82o5G9r6QY,1170
|
|
9
|
+
mx/domain.py,sha256=ow_RROPZ22bib_wMWj2MkIFCwLwYpIWX9B75yWZd0W4,10399
|
|
10
|
+
mx/exceptions.py,sha256=oihSuTzUyaSnA55u9ME-p-2aGPKOp4w7uS_KZ3paWGY,1362
|
|
11
|
+
mx/file_names.py,sha256=u_rekTAXxVmhV5JvvIOesyW5oDOy2d1yISdJjaOpgJM,207
|
|
12
|
+
mx/initial_states.py,sha256=2KNtoZ8awih7bX1AWlIgK4qbdlGC3VZJ91l6B7eZLs4,3234
|
|
13
|
+
mx/instance.py,sha256=qH4sGHvZwfWszPItw-sVa1Lk_1hQ_xzTVXq1y_sT3qU,361
|
|
14
|
+
mx/instance_set.py,sha256=RXvxgejgcVTgTPMOlDo4DlplISxLQ5OaZ8ErpzJlOak,2205
|
|
15
|
+
mx/interaction_event.py,sha256=CYhmRpLwQ-48r7Cju4P0S55T0fT_QBraPRFQxZKKYdg,3851
|
|
16
|
+
mx/lifecycle_state_machine.py,sha256=3jnbtIEz_i_bvzn_RO3dabz2QBBsmnuzkTL9UdghEjw,770
|
|
17
|
+
mx/log.conf,sha256=g01dVwgSXgeEkmApdU1I4bhB6pVSlM23sV9A5qr_nj8,761
|
|
18
|
+
mx/method.py,sha256=utPQVrGCcA1iNTju9cjYeUQnELcHX4CYA4_UFhBS6bk,8510
|
|
19
|
+
mx/multiple_assigner_state_machine.py,sha256=gspey4ch9oq85JxL1PEWLvoKOjhrrCezHxIjQYEPrb0,516
|
|
20
|
+
mx/mx_logger.py,sha256=MmLGIUyFAXhXty1cFeVmyQ4Sljc9eKiCpi1V2z1Es7U,2049
|
|
21
|
+
mx/mxtypes.py,sha256=6fXHAfwZ-tqwDMxoVOa79FXdMd53Qgf7snbk7WkFsm8,471
|
|
22
|
+
mx/operation.py,sha256=bVE1MlgGCitr6sdCLdRr2Ri7gdMHBMJDFCGkuGnVfdU,1543
|
|
23
|
+
mx/rvname.py,sha256=YNvfowaVXBpi8DQLcTEJSUW1vtYBQ1qu_1MTm-7PKw0,1081
|
|
24
|
+
mx/scenario.py,sha256=a60da27mSmAXcv81bE-ajB3jlC276m9EcSNS-bLnqGw,5860
|
|
25
|
+
mx/single_assigner_state_machine.py,sha256=Y1N7doLytje1ou1_ZHCX9aPGqXTkuHYo1rjqpEm7OpE,335
|
|
26
|
+
mx/state_activity.py,sha256=yT7_3ZfDG6opmQX30BKYNDHFmmRzI1VnKuSPfMLKlY0,632
|
|
27
|
+
mx/state_machine.py,sha256=R6bXwCOPXOtq60sOYpfhfC4Mrpmi3DDTruH0svMw1zw,7932
|
|
28
|
+
mx/system.py,sha256=Em4zxjz9JjsvioSmNHSTQ5iLZ2IouN1WCJXS9TFbcKI,1756
|
|
29
|
+
mx/xe.py,sha256=YNRdwkTw2LvckHuto5f-xnv9a57LAIylYtjFwUsUePk,3818
|
|
30
|
+
mx/actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
+
mx/actions/action.py,sha256=5G20rnQOc0miNXlF9R1ZqdFoypSLxRTiGxASK7i5pCg,3845
|
|
32
|
+
mx/actions/extract.py,sha256=K1sMKMyG_cOCQxddxCyWkk3_DQWMYSTsGaqPkLHh4wg,4626
|
|
33
|
+
mx/actions/flow.py,sha256=UwVGctMKcRjvvlSfN1r8tO5aNemTTmhvX35FVCHMAIE,707
|
|
34
|
+
mx/actions/gate.py,sha256=_oCMY_AedHaasP9mBlBuejYThueb-bhmFQmigREHKFE,4224
|
|
35
|
+
mx/actions/project.py,sha256=NUzsumdR9pB67sqPN9zwOPnFV0asu1ctHZiBcrqgUM4,6082
|
|
36
|
+
mx/actions/rank_restrict.py,sha256=Gx4m9tLbp9hgMRRpnGEIGyBnKy_LHU14S9dQNcjATlQ,6097
|
|
37
|
+
mx/actions/read.py,sha256=xN-naRVPhmANgKzv4JBFhrdaLXAR9UuHDmeb7jZ7xLE,5467
|
|
38
|
+
mx/actions/rename.py,sha256=xTS9CPezWxBk4i9Ojgk-ymQUFanOnUSHz0Wjbz4wqbI,4781
|
|
39
|
+
mx/actions/restrict.py,sha256=nxsbZdzg_HgO8vO-peEjw1iaDGIBQXzOx_5nZckHvc8,9083
|
|
40
|
+
mx/actions/scalar_switch.py,sha256=ex69kGzfpovlAW7rhzoPzMGdMj2V5KOD1TbuWHkfks0,4906
|
|
41
|
+
mx/actions/select.py,sha256=GeCXZk24cSdL1NWNpddIRxIKhKOvQRtkl46f2BlJSro,9425
|
|
42
|
+
mx/actions/set_action.py,sha256=SwOeWWxdSMUkvIAqEsrnmgydfR-iKcYRUGuf22aIn_M,5804
|
|
43
|
+
mx/actions/signal.py,sha256=JA_Pc8mrBz7DVWN8bMPsfkjwRDO9I2fDKZIQNzXQVA8,806
|
|
44
|
+
mx/actions/single_select.py,sha256=o6oVF2YcJ04zsZRJy5G0cldOBaj-2oZ-HwT9mIwTV98,227
|
|
45
|
+
mx/actions/traverse.py,sha256=EQmmOA8Fv9yayYUoYqjyefvCoE8LXxc1Jne2HG3xB-s,15398
|
|
46
|
+
mx/actions/zero_one_card_select.py,sha256=TXscQjTiCEFAxcEK8FeRzkAwcmW3CkdzZTGqM4AL9rw,258
|
|
47
|
+
mx/deprecated/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
48
|
+
mx/deprecated/bridge.py,sha256=7V68JPpDsvpVgHkF3pmhI5PBmYCOGgKzmGPQEOZ94nQ,1020
|
|
49
|
+
mx/scenarios/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
50
|
+
mx/scenarios/ev_scenario_REF.py,sha256=NeNlb9KOlJPi3O6DO6VOmW1yXGa7mk4j2mWbmyn5uj0,5517
|
|
51
|
+
mi_mx-0.1.0.dist-info/METADATA,sha256=5rUrwUzcmcUzigOUacN12C4eEPa7XaByvKAAvZA0pic,6031
|
|
52
|
+
mi_mx-0.1.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
53
|
+
mi_mx-0.1.0.dist-info/entry_points.txt,sha256=KrL83CCgDv4Ku6LAyEZr5hQbmU0WHOUcR6dieo0sqnM,40
|
|
54
|
+
mi_mx-0.1.0.dist-info/top_level.txt,sha256=VqrBpn6wmSgznbspfOeRUxKpP1UgURlQ7u9eYUCMHhc,3
|
|
55
|
+
mi_mx-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Leon Starr
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mx
|
mx/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = "0.1.0"
|
mx/__main__.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Blueprint Model Execution
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
# System
|
|
6
|
+
import logging
|
|
7
|
+
import logging.config
|
|
8
|
+
import sys
|
|
9
|
+
import argparse
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import atexit
|
|
12
|
+
|
|
13
|
+
# MX
|
|
14
|
+
from mx.xe import XE
|
|
15
|
+
from mx import version
|
|
16
|
+
|
|
17
|
+
_logpath = Path("mx.log")
|
|
18
|
+
_progname = 'Blueprint Model Execution'
|
|
19
|
+
|
|
20
|
+
def clean_up():
|
|
21
|
+
"""Normal and exception exit activities"""
|
|
22
|
+
_logpath.unlink(missing_ok=True)
|
|
23
|
+
|
|
24
|
+
def get_logger():
|
|
25
|
+
"""Initiate the logger"""
|
|
26
|
+
log_conf_path = Path(__file__).parent / 'log.conf' # Logging configuration is in this file
|
|
27
|
+
logging.config.fileConfig(fname=log_conf_path, disable_existing_loggers=False)
|
|
28
|
+
return logging.getLogger(__name__) # Create a logger for this module
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Configure the expected parameters and actions for the argparse module
|
|
32
|
+
def parse(cl_input):
|
|
33
|
+
parser = argparse.ArgumentParser(description=_progname)
|
|
34
|
+
parser.add_argument('-s', '--system', action='store',
|
|
35
|
+
help='Name of the metamodel TclRAL database *.ral file populated with one or more domains')
|
|
36
|
+
parser.add_argument('-c', '--context', action='store',
|
|
37
|
+
help='Name of the context directory specifying the initialized domain dbs and a *.sip file')
|
|
38
|
+
parser.add_argument('-x', '--scenario', action='store',
|
|
39
|
+
help='Name of the scenario *.yaml file to run against the populated system')
|
|
40
|
+
parser.add_argument('-D', '--debug', action='store_true',
|
|
41
|
+
help='Debug mode'),
|
|
42
|
+
parser.add_argument('-L', '--log', action='store_true',
|
|
43
|
+
help='Generate a diagnostic log file')
|
|
44
|
+
parser.add_argument('-v', '--verbose', action='store_true',
|
|
45
|
+
help='Verbose messages')
|
|
46
|
+
parser.add_argument('-V', '--version', action='store_true',
|
|
47
|
+
help='Print the current version of the model execution app')
|
|
48
|
+
return parser.parse_args(cl_input)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def main():
|
|
52
|
+
# Start logging
|
|
53
|
+
logger = get_logger()
|
|
54
|
+
logger.info(f'{_progname} version: {version}')
|
|
55
|
+
|
|
56
|
+
# Parse the command line args
|
|
57
|
+
args = parse(sys.argv[1:])
|
|
58
|
+
|
|
59
|
+
if args.version:
|
|
60
|
+
# Just print the version and quit
|
|
61
|
+
print(f'{_progname} version: {version}')
|
|
62
|
+
sys.exit(0)
|
|
63
|
+
|
|
64
|
+
if not args.log:
|
|
65
|
+
# If no log file is requested, remove the log file before termination
|
|
66
|
+
atexit.register(clean_up)
|
|
67
|
+
|
|
68
|
+
# Domain specified
|
|
69
|
+
if args.system:
|
|
70
|
+
xe = XE() # Create the singleton instance
|
|
71
|
+
xe.initialize(mmdb_path=Path(args.system), context_dir=Path(args.context),
|
|
72
|
+
scenario_file=Path(args.scenario), verbose=args.verbose, debug=args.debug)
|
|
73
|
+
|
|
74
|
+
print("\nNo problemo") # Comment this line out before release
|
|
75
|
+
logger.info("No problemo") # We didn't die on an exception, basically
|
|
76
|
+
if args.verbose or args.debug:
|
|
77
|
+
print("\nNo problemo")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
main()
|
mx/actions/__init__.py
ADDED
|
File without changes
|
mx/actions/action.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
""" action.py -- manages Action execution """
|
|
2
|
+
|
|
3
|
+
# System
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from mx.method import Method # TOOD: Replace with Activity after refactoring State/Assigner Activities
|
|
8
|
+
|
|
9
|
+
# Model Integration
|
|
10
|
+
from pyral.relation import Relation
|
|
11
|
+
|
|
12
|
+
# MX
|
|
13
|
+
from mx.db_names import mmdb
|
|
14
|
+
|
|
15
|
+
# These actions do not output a non-scalar flow and, thus, do not declare
|
|
16
|
+
# an output database variable to be freed up upon activity completion
|
|
17
|
+
no_nsflow_output_actions = {"scalarswitch", "gate", "read", "extract"}
|
|
18
|
+
|
|
19
|
+
class Action:
|
|
20
|
+
|
|
21
|
+
def __init__(self, activity: "Method", anum: str, action_id: str):
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
:param activity: Name of the encompassing Activity
|
|
25
|
+
:param anum: The Activity number
|
|
26
|
+
:param action_id: The Action ID value unique to this Action within the Activity
|
|
27
|
+
"""
|
|
28
|
+
self.anum = anum
|
|
29
|
+
self.action_id = action_id
|
|
30
|
+
self.activity = activity
|
|
31
|
+
self.action_type = type(self).__name__.lower()
|
|
32
|
+
|
|
33
|
+
# Do not execute this action unless ALL input flows have been enabled.
|
|
34
|
+
# The preceding Wave should have enabled all required inputs, unless some upstream condition
|
|
35
|
+
# evaluated as false. If so, this action must be skipped.
|
|
36
|
+
|
|
37
|
+
# Check the Flow Dependency class for all required input Flow names
|
|
38
|
+
R = f"To_action:<{action_id}>, Activity:<{activity.anum}>, Domain:<{activity.domain}>"
|
|
39
|
+
dependencies_r = Relation.restrict(db=mmdb, relation="Flow Dependency", restriction=R)
|
|
40
|
+
required_flow_names = [d["Flow"] for d in dependencies_r.body]
|
|
41
|
+
# Check our dictionary of active flows and disable if any required input flows were not set
|
|
42
|
+
if self.action_type == "gate":
|
|
43
|
+
self.disabled = all(activity.flows[f] is None for f in required_flow_names)
|
|
44
|
+
else:
|
|
45
|
+
self.disabled = any(activity.flows[f] is None for f in required_flow_names)
|
|
46
|
+
|
|
47
|
+
if not self.disabled and self.action_type not in no_nsflow_output_actions:
|
|
48
|
+
# Since this Action will be executed, it may produce one or more non scalar flow
|
|
49
|
+
# (relation) outputs, and thus set a database variable for each. These rv variables
|
|
50
|
+
# will be freed up upon completion of the Activity.
|
|
51
|
+
# We can't free them up earlier since any flow content in an rv must be available while the activity
|
|
52
|
+
# executes. (Strictly speaking, we could free them up piecemeal once there are no more consumers, but
|
|
53
|
+
# it's easier and less error prone to just free them all up after the activity completes.
|
|
54
|
+
self.activity.executed_actions.append(self.action_id)
|
|
55
|
+
|
|
56
|
+
# Each subclass action should verify the disable status before attempting to execute
|
|
57
|
+
|
|
58
|
+
# The domain alias is also the name of the TclRAL domain database session
|
|
59
|
+
self.domdb = self.activity.domain_alias # Abbreviated access since we use it alot
|
|
60
|
+
|
|
61
|
+
# To make it possible to run Actions currently (in the future)
|
|
62
|
+
# we want to ensure that the temporary relational variable names between two
|
|
63
|
+
# concurrent executions of the same Activity (by different instances) do not collide.
|
|
64
|
+
|
|
65
|
+
# So we create a naming prefix unique to this action execution instance.
|
|
66
|
+
# We concatenate the anum, action id, and instance id value
|
|
67
|
+
# instnace id value is the conatenation of each identifier attribute value
|
|
68
|
+
instance_id_value = '_'.join(v for v in self.activity.instance.values())
|
|
69
|
+
# relation variable prefix (rvp) is the full concatenation
|
|
70
|
+
self.rvp = f"{self.activity.anum}_{action_id}_{instance_id_value}"
|
|
71
|
+
# This value is then prepended to a descriptive name to create a relational variable name
|
|
72
|
+
# used to access a relation stored inside the TclRAL db
|
|
73
|
+
|
|
74
|
+
if self.disabled:
|
|
75
|
+
self.activity.xe.mxlog.log(message="DISABLED")
|
|
76
|
+
|
mx/actions/extract.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
""" extract.py -- execute a relational extract action """
|
|
2
|
+
|
|
3
|
+
# System
|
|
4
|
+
from typing import TYPE_CHECKING, NamedTuple
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from mx.method import Method # TODO: Replace with Activity after refactoring State/Assigner Activities
|
|
8
|
+
|
|
9
|
+
# Model Integration
|
|
10
|
+
from pyral.relation import Relation
|
|
11
|
+
from pyral.database import Database # Diagnostics
|
|
12
|
+
|
|
13
|
+
# MX
|
|
14
|
+
from mx.db_names import mmdb
|
|
15
|
+
from mx.actions.action import Action
|
|
16
|
+
from mx.actions.flow import ActiveFlow, FlowDir
|
|
17
|
+
from mx.rvname import declare_rvs
|
|
18
|
+
|
|
19
|
+
# See comment in scalar_switch.py
|
|
20
|
+
class RVs(NamedTuple):
|
|
21
|
+
activity_extract_switch_actions: str
|
|
22
|
+
this_extract_action: str
|
|
23
|
+
attribute: str
|
|
24
|
+
|
|
25
|
+
# This wrapper calls the imported declare_rvs function to generate a NamedTuple instance with each of our
|
|
26
|
+
# variables above as a member.
|
|
27
|
+
def declare_my_module_rvs(db: str, owner: str) -> RVs:
|
|
28
|
+
rvs = declare_rvs(db, owner, "activity_extract_actions", "this_extract_action", "attribute"
|
|
29
|
+
)
|
|
30
|
+
return RVs(*rvs)
|
|
31
|
+
|
|
32
|
+
class Extract(Action):
|
|
33
|
+
|
|
34
|
+
def __init__(self, action_id: str, activity: "Method"):
|
|
35
|
+
"""
|
|
36
|
+
Perform the Extract Action on a domain model.
|
|
37
|
+
|
|
38
|
+
Note: For now we are only handling Methods, but State Activities will be incorporated eventually.
|
|
39
|
+
|
|
40
|
+
:param action_id: The ACTN<n> value identifying each Action instance
|
|
41
|
+
:param activity: The A<n> Activity ID (for Method and State Activities)
|
|
42
|
+
"""
|
|
43
|
+
super().__init__(activity=activity, anum=activity.anum, action_id=action_id)
|
|
44
|
+
|
|
45
|
+
# Do not execute this Action if it is not enabled, see comment in Action class
|
|
46
|
+
if self.disabled:
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
# Get a NamedTuple with a field for each relation variable name
|
|
50
|
+
rv = declare_my_module_rvs(db=mmdb, owner=self.rvp)
|
|
51
|
+
|
|
52
|
+
# Lookup the Action instance
|
|
53
|
+
# Start with all Rename actions in this Activity
|
|
54
|
+
Relation.semijoin(db=mmdb, rname1=activity.method_rvname, rname2="Extract_Action",
|
|
55
|
+
svar_name=rv.activity_extract_switch_actions)
|
|
56
|
+
# Narrow it down to this Extract Action instance
|
|
57
|
+
R = f"ID:<{action_id}>"
|
|
58
|
+
extract_action_r = Relation.restrict(db=mmdb, relation=rv.activity_extract_switch_actions, restriction=R,
|
|
59
|
+
svar_name=rv.this_extract_action)
|
|
60
|
+
if self.activity.xe.debug:
|
|
61
|
+
Relation.print(db=mmdb, variable_name=rv.this_extract_action)
|
|
62
|
+
|
|
63
|
+
extract_action_t = extract_action_r.body[0]
|
|
64
|
+
self.source_flow_name = extract_action_t["Input_tuple"]
|
|
65
|
+
self.source_flow = self.activity.flows[self.source_flow_name] # The active content of source flow (value, type)
|
|
66
|
+
self.dest_flow_name = extract_action_t["Output_scalar"]
|
|
67
|
+
|
|
68
|
+
self.attr = extract_action_t["Attribute"]
|
|
69
|
+
input_tuple_r = Relation.project(db=self.domdb, relation=self.source_flow.value, attributes=(self.attr,))
|
|
70
|
+
extracted_value = input_tuple_r.body[0][self.attr]
|
|
71
|
+
|
|
72
|
+
self.activity.xe.mxlog.log(message=f"- Attribute: {self.attr}")
|
|
73
|
+
self.activity.xe.mxlog.log(message="Flows")
|
|
74
|
+
self.activity.xe.mxlog.log_nsflow(flow_name=self.source_flow_name, flow_dir=FlowDir.IN,
|
|
75
|
+
flow_type=self.source_flow.flowtype, activity=self.activity,
|
|
76
|
+
db=self.domdb, rv_name=self.source_flow.value)
|
|
77
|
+
|
|
78
|
+
if self.activity.xe.debug:
|
|
79
|
+
print(f"\nScalar value: {extracted_value} output on Flow: {self.dest_flow_name}")
|
|
80
|
+
|
|
81
|
+
# Get attribute being read so we can look up its type
|
|
82
|
+
attr_r = Relation.semijoin(db=mmdb, rname1=rv.this_extract_action, rname2="Table Attribute",
|
|
83
|
+
attrs={"Attribute": "Name", "Table": "Table", "Domain": "Domain"},
|
|
84
|
+
svar_name=rv.attribute)
|
|
85
|
+
attr_t = attr_r.body[0]
|
|
86
|
+
|
|
87
|
+
self.activity.flows[self.dest_flow_name] = ActiveFlow(value=extracted_value, flowtype="scalar")
|
|
88
|
+
self.activity.xe.mxlog.log_sflow(flow_name=self.dest_flow_name, flow_dir=FlowDir.OUT,
|
|
89
|
+
flow_type=attr_t["Scalar"], activity=self.activity)
|
|
90
|
+
self.activity.xe.mxlog.log(message=f"Scalar value: [{extracted_value}]")
|
|
91
|
+
|
|
92
|
+
# This action's mmdb rvs are no longer needed)
|
|
93
|
+
Relation.free_rvs(db=mmdb, owner=self.rvp)
|
|
94
|
+
# And since we are outputing a scalar flow, there is no domain rv output to preserve
|
|
95
|
+
# In fact, we didn't define any domain rv's at all, so there are none to free
|
|
96
|
+
|
|
97
|
+
_rv_after_mmdb_free = Database.get_rv_names(db=mmdb)
|
|
98
|
+
_rv_after_dom_free = Database.get_rv_names(db=self.domdb)
|
mx/actions/flow.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
""" flow.py -- Active Flow type """
|
|
2
|
+
|
|
3
|
+
# System
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import TYPE_CHECKING, NamedTuple, Any
|
|
6
|
+
|
|
7
|
+
# Model Integration
|
|
8
|
+
from pyral.relation import Relation
|
|
9
|
+
|
|
10
|
+
# MX
|
|
11
|
+
from mx.db_names import mmdb
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from mx.activity import Activity
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ActiveFlow(NamedTuple):
|
|
17
|
+
value: Any
|
|
18
|
+
flowtype: str
|
|
19
|
+
|
|
20
|
+
class FlowDir(Enum):
|
|
21
|
+
IN = "in"
|
|
22
|
+
OUT = "out"
|
|
23
|
+
|
|
24
|
+
def label(name: str, activity: "Activity") -> str:
|
|
25
|
+
R = f"ID:<{name}>, Activity:<{activity.anum}>, Domain:<{activity.domain}>"
|
|
26
|
+
labeled_flow_r = Relation.restrict(db=mmdb, relation='Labeled Flow', restriction=R)
|
|
27
|
+
if labeled_flow_r.body:
|
|
28
|
+
return labeled_flow_r.body[0]["Name"]
|
|
29
|
+
else:
|
|
30
|
+
return ""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
mx/actions/gate.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
""" gate.py -- executes the Gate Action """
|
|
2
|
+
|
|
3
|
+
# System
|
|
4
|
+
from typing import TYPE_CHECKING, NamedTuple
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from mx.method import Method
|
|
8
|
+
|
|
9
|
+
# Model Integration
|
|
10
|
+
from pyral.relation import Relation
|
|
11
|
+
from pyral.database import Database # Diagnostics
|
|
12
|
+
from pyral.rtypes import Extent, Card
|
|
13
|
+
|
|
14
|
+
# MX
|
|
15
|
+
from mx.db_names import mmdb
|
|
16
|
+
from mx.actions.action import Action
|
|
17
|
+
from mx.actions.flow import FlowDir
|
|
18
|
+
from mx.rvname import declare_rvs
|
|
19
|
+
|
|
20
|
+
# See comment in scalar_switch.py
|
|
21
|
+
class MMRVs(NamedTuple):
|
|
22
|
+
activity_gate_actions: str
|
|
23
|
+
this_gate_action: str
|
|
24
|
+
flow_deps: str
|
|
25
|
+
|
|
26
|
+
# This wrapper calls the imported declare_rvs function to generate a NamedTuple instance with each of our
|
|
27
|
+
# variables above as a member.
|
|
28
|
+
def declare_mm_rvs(db: str, owner: str) -> MMRVs:
|
|
29
|
+
rvs = declare_rvs(db, owner, "activity_gate_actions", "this_gate_action", "flow_deps")
|
|
30
|
+
return MMRVs(*rvs)
|
|
31
|
+
|
|
32
|
+
class Gate(Action):
|
|
33
|
+
|
|
34
|
+
def __init__(self, action_id: str, activity: "Method"):
|
|
35
|
+
"""
|
|
36
|
+
Perform the Gate Action on a domain model.
|
|
37
|
+
|
|
38
|
+
:param action_id: ACTN<n> value identifying each Action instance
|
|
39
|
+
:param activity: A<n> Activity ID (for Method and State Activities)
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
super().__init__(activity=activity, anum=activity.anum, action_id=action_id)
|
|
43
|
+
|
|
44
|
+
# Do not execute this Action if it is not enabled, see comment in Action class
|
|
45
|
+
if self.disabled:
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
# Get a NamedTuple with a field for each relation variable name
|
|
49
|
+
self.mmrv = declare_mm_rvs(db=mmdb, owner=self.rvp)
|
|
50
|
+
mmrv = self.mmrv # For brevity
|
|
51
|
+
|
|
52
|
+
self.activity.xe.mxlog.log(message="Flows")
|
|
53
|
+
# Lookup the Action instance
|
|
54
|
+
# Start with all Gate actions in this Activity
|
|
55
|
+
Relation.semijoin(db=mmdb, rname1=activity.method_rvname, rname2="Gate Action",
|
|
56
|
+
svar_name=mmrv.activity_gate_actions)
|
|
57
|
+
|
|
58
|
+
# Narrow it down to this Restrict Action instance
|
|
59
|
+
R = f"ID:<{action_id}>"
|
|
60
|
+
gate_action_r = Relation.restrict(db=mmdb, relation=mmrv.activity_gate_actions, restriction=R,
|
|
61
|
+
svar_name=mmrv.this_gate_action)
|
|
62
|
+
if self.activity.xe.debug:
|
|
63
|
+
Relation.print(db=mmdb, variable_name=mmrv.this_gate_action)
|
|
64
|
+
|
|
65
|
+
# Lookup the Action flow dependency
|
|
66
|
+
flow_deps_r = Relation.semijoin(db=mmdb, rname1=mmrv.this_gate_action, rname2="Flow Dependency",
|
|
67
|
+
attrs={"ID": "To_action", "Activity": "Activity", "Domain": "Domain"},
|
|
68
|
+
svar_name=mmrv.flow_deps)
|
|
69
|
+
|
|
70
|
+
if self.activity.xe.debug:
|
|
71
|
+
Relation.print(db=mmdb, variable_name=mmrv.flow_deps)
|
|
72
|
+
|
|
73
|
+
gate_action_t = gate_action_r.body[0]
|
|
74
|
+
output_flow_name = gate_action_t["Output_flow"]
|
|
75
|
+
input_flow_names = [t["Flow"] for t in flow_deps_r.body]
|
|
76
|
+
input_values = {n: activity.flows[n] for n in input_flow_names if activity.flows[n]}
|
|
77
|
+
if len(input_values) > 1:
|
|
78
|
+
pass
|
|
79
|
+
assert(len(input_values) == 1)
|
|
80
|
+
input_flow_name, activity.flows[output_flow_name] = next(iter(input_values.items()))
|
|
81
|
+
if self.activity.xe.debug:
|
|
82
|
+
print(f"\nGate: {self.action_id} passing Flow: {input_flow_name} to Flow: {output_flow_name}")
|
|
83
|
+
Relation.print(db=self.domdb, variable_name=activity.flows[output_flow_name].value)
|
|
84
|
+
|
|
85
|
+
self.activity.xe.mxlog.log_nsflow(flow_name=input_flow_name, flow_dir=FlowDir.IN,
|
|
86
|
+
flow_type=activity.flows[input_flow_name].flowtype, activity=self.activity,
|
|
87
|
+
db=self.domdb, rv_name=activity.flows[input_flow_name].value)
|
|
88
|
+
self.activity.xe.mxlog.log_nsflow(flow_name=output_flow_name, flow_dir=FlowDir.OUT,
|
|
89
|
+
flow_type=activity.flows[output_flow_name].flowtype, activity=self.activity,
|
|
90
|
+
db=self.domdb, rv_name=activity.flows[output_flow_name].value)
|
|
91
|
+
|
|
92
|
+
# This action's mmdb rvs are no longer needed)
|
|
93
|
+
Relation.free_rvs(db=mmdb, owner=self.rvp)
|
|
94
|
+
_rv_after_mmdb_free = Database.get_rv_names(db=mmdb)
|
|
95
|
+
_rv_after_dom_free = Database.get_rv_names(db=self.domdb)
|