Skip to content

Commit abf285a

Browse files
Revert compile option (#27)
* Revert "Fix deadlock issue" This reverts commit daa7935. * Revert "fix getClassName for compiled files (#24)" This reverts commit b264035. * Revert "Add compile option to compile project files to bytecode ref #22 (#23)" This reverts commit cf6bc1d.
1 parent 95021b3 commit abf285a

File tree

4 files changed

+29
-120
lines changed

4 files changed

+29
-120
lines changed

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pip install pythonfmu3
3636
3. Run `pythonfmu3 build` to create the fmu.
3737

3838
```
39-
usage: pythonfmu3 build [-h] -f SCRIPT_FILE [-d DEST] [--doc DOCUMENTATION_FOLDER] [--terminals TERMINALS_FILE] [--compile] [--no-external-tool]
39+
usage: pythonfmu3 build [-h] -f SCRIPT_FILE [-d DEST] [--doc DOCUMENTATION_FOLDER] [--terminals TERMINALS_FILE] [--no-external-tool]
4040
[--no-variable-step] [--interpolate-inputs] [--only-one-per-process] [--handle-state]
4141
[--serialize-state] [--use-memory-management]
4242
[Project files [Project files ...]]
@@ -55,7 +55,6 @@ optional arguments:
5555
Documentation folder to include in the FMU.
5656
--terminals TERMINALS_FILE
5757
Terminals file (terminalsAndIcons.xml) to include in the FMU.
58-
--compile If given, the script file and project files will be compiled to byte code.
5958
--no-external-tool If given, needsExecutionTool=false
6059
--no-variable-step If given, canHandleVariableCommunicationStepSize=false
6160
--interpolate-inputs If given, canInterpolateInputs=true

pythonfmu3/builder.py

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import importlib
44
import itertools
55
import logging
6-
import py_compile
76
import re
87
import shutil
98
import sys
@@ -21,29 +20,11 @@
2120

2221
logger = logging.getLogger(__name__)
2322

24-
def compile_source_file(source_file: FilePath, clean_existing: bool = False) -> Path:
25-
compiled_file = source_file.with_suffix(".pyc")
26-
py_compile.compile(source_file, cfile=compiled_file)
27-
if clean_existing:
28-
source_file.unlink(missing_ok=True)
29-
return compiled_file
3023

3124
def get_class_name(file_name: Path) -> str:
32-
33-
if file_name.suffix == ".py":
34-
with open(str(file_name), 'r') as file:
35-
data = file.read()
36-
return re.search(r'class (\w+)\(\s*Fmi3Slave\s*\)\s*:', data).group(1)
37-
38-
elif file_name.suffix == ".pyc":
39-
# For .pyc files, use importlib to load the module and inspect its classes
40-
import importlib.util
41-
spec = importlib.util.spec_from_file_location("module_name", file_name)
42-
module = importlib.util.module_from_spec(spec)
43-
spec.loader.exec_module(module)
44-
for name, obj in vars(module).items():
45-
if isinstance(obj, type) and issubclass(obj, Fmi3Slave) and obj is not Fmi3Slave:
46-
return name
25+
with open(str(file_name), 'r') as file:
26+
data = file.read()
27+
return re.search(r'class (\w+)\(\s*Fmi3Slave\s*\)\s*:', data).group(1)
4728

4829

4930
def get_model_description(filepath: Path, module_name: str) -> Tuple[str, Element]:
@@ -105,7 +86,7 @@ def build_FMU(
10586
raise ValueError(
10687
f"The documentation folder does not exists {documentation_folder!s}"
10788
)
108-
89+
10990
if terminals is not None:
11091
terminals = Path(terminals)
11192
if not terminals.exists():
@@ -118,14 +99,11 @@ def build_FMU(
11899
)
119100

120101
module_name = script_file.stem
121-
compile_flag = options.get("compile", False)
122102

123103
with tempfile.TemporaryDirectory(prefix="pythonfmu_") as tempd:
124104
temp_dir = Path(tempd)
125105
shutil.copy2(script_file, temp_dir)
126106

127-
if compile_flag:
128-
script_file = compile_source_file(temp_dir / script_file.name, clean_existing=True)
129107
# Embed pythonfmu in the FMU so it does not need to be included
130108
dep_folder = temp_dir / "pythonfmu3"
131109
dep_folder.mkdir()
@@ -152,11 +130,6 @@ def build_FMU(
152130
else:
153131
shutil.copy2(file_, temp_dir)
154132

155-
for f in temp_dir.rglob("*.py"):
156-
if f.suffix == ".py" and compile_flag:
157-
compile_source_file(f, clean_existing=True)
158-
159-
160133
model_identifier, xml = get_model_description(
161134
temp_dir.absolute() / script_file.name, module_name
162135
)
@@ -221,7 +194,7 @@ def build_FMU(
221194
terminalsFolder = Path("terminalsAndIcons")
222195
relative_f = terminals.relative_to(terminals.parent)
223196
zip_fmu.write(f, arcname=(terminalsFolder / relative_f))
224-
197+
225198

226199
# Add the model description
227200
xml_str = parseString(tostring(xml, "UTF-8"))
@@ -267,13 +240,6 @@ def create_command_parser(parser: argparse.ArgumentParser):
267240
default=None
268241
)
269242

270-
parser.add_argument(
271-
"--compile",
272-
dest="compile",
273-
help="If given, the Python script and project files will be compiled to bytecode.",
274-
action="store_true"
275-
)
276-
277243
for option in FMI3_MODEL_OPTIONS:
278244
action = "store_false" if option.value else "store_true"
279245
parser.add_argument(

pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp

Lines changed: 16 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -24,79 +24,28 @@ inline std::string getline(const std::string& fileName)
2424
return line;
2525
}
2626

27-
std::string getClassName(PyObject * pModule)
28-
{
29-
std::string className = "";
30-
PyObject* dict = PyModule_GetDict(pModule);
31-
PyObject* keys = PyDict_Keys(dict);
32-
// Iterate through the dictionary keys to find classes derived from Fmi3Slave
33-
for (int i = 0; i < PyList_Size(keys); i++) {
34-
PyObject* key = PyList_GetItem(keys, i);
35-
if (PyUnicode_Check(key)) {
36-
PyObject* utf8Str = PyUnicode_AsEncodedString(key, "utf-8", nullptr);
37-
if (utf8Str == nullptr) {
38-
return "";
39-
}
40-
std::string classNameCandidate = PyBytes_AsString(utf8Str);
41-
Py_DECREF(utf8Str);
42-
43-
// Check if the class is a subclass of Fmi3Slave
44-
PyObject* obj = PyDict_GetItem(dict, key);
45-
if (PyObject_HasAttrString(obj, "__bases__")) {
46-
PyObject* bases = PyObject_GetAttrString(obj, "__bases__");
47-
if (PyTuple_Check(bases)) {
48-
for (int j = 0; j < PyTuple_Size(bases); j++) {
49-
PyObject* base = PyTuple_GetItem(bases, j);
50-
PyObject* baseNameObj = PyObject_GetAttrString(base, "__name__");
51-
if (baseNameObj && PyUnicode_Check(baseNameObj)) {
52-
PyObject* baseUtf8Str = PyUnicode_AsEncodedString(baseNameObj, "utf-8", nullptr);
53-
if (baseUtf8Str != nullptr) {
54-
std::string baseName = PyBytes_AsString(baseUtf8Str);
55-
Py_DECREF(baseUtf8Str);
56-
if (baseName == "Fmi3Slave") {
57-
className = classNameCandidate;
58-
Py_DECREF(baseNameObj);
59-
Py_DECREF(bases);
60-
Py_DECREF(keys);
61-
return className;
62-
}
63-
}
64-
Py_DECREF(baseNameObj);
65-
}
66-
}
67-
}
68-
Py_DECREF(bases);
69-
}
70-
71-
if (!className.empty()) {
72-
break; // Break after finding the first subclass
73-
}
27+
inline std::string findClassName(const std::string& fileName)
28+
{
29+
std::string line;
30+
std::ifstream infile(fileName);
31+
std::string regexStr(R"(^class (\w+)\(\s*Fmi3Slave\s*\)\s*:)");
32+
while (getline(infile, line)) {
33+
std::smatch m;
34+
std::regex re(regexStr);
35+
if (std::regex_search(line, m, re)) {
36+
return m[1];
7437
}
7538
}
76-
return className;
77-
}
78-
79-
class GILStateGuard {
80-
public:
81-
GILStateGuard() : gil_state_(PyGILState_Ensure()) {}
82-
~GILStateGuard() { PyGILState_Release(gil_state_); }
83-
84-
GILStateGuard(const GILStateGuard&) = delete;
85-
GILStateGuard& operator=(const GILStateGuard&) = delete;
86-
87-
PyGILState_STATE get() const { return gil_state_; }
88-
89-
private:
90-
PyGILState_STATE gil_state_;
91-
};
39+
return "";
40+
}
9241

9342
inline void py_safe_run(const std::function<void(PyGILState_STATE gilState)>& f)
9443
{
95-
GILStateGuard guard;
96-
f(guard.get());
44+
PyGILState_STATE gil_state = PyGILState_Ensure();
45+
f(gil_state);
46+
PyGILState_Release(gil_state);
9747
}
9848

99-
10049
PySlaveInstance::PySlaveInstance(std::string instanceName, std::string resources, const cppfmu::Logger& logger, const bool visible, std::shared_ptr<IPyState> pyState)
10150
: pyState_{ std::move(pyState) }
10251
, instanceName_(std::move(instanceName))
@@ -128,7 +77,7 @@ PySlaveInstance::PySlaveInstance(std::string instanceName, std::string resources
12877
handle_py_exception("[ctor] PyImport_ImportModule", gilState);
12978
}
13079

131-
std::string className = getClassName(pModule);
80+
std::string className = findClassName(resources_ + "/" + moduleName + ".py");
13281
if (className.empty()) {
13382
cleanPyObject();
13483
throw cppfmu::FatalError("Unable to find class extending Fmi3Slave!");

pythonfmu3/tests/test_builder.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,16 @@
2222
)
2323

2424

25-
@pytest.mark.parametrize("compile", [False, True])
26-
def test_zip_content(tmp_path, compile):
27-
extension = ".pyc" if compile else ".py"
25+
def test_zip_content(tmp_path):
26+
2827
script_name = "pythonslave.py"
2928
script_file = Path(__file__).parent / "slaves" / script_name
30-
fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path, compile=compile)
29+
fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path)
3130
assert fmu.exists()
3231
assert zipfile.is_zipfile(fmu)
3332

3433
with zipfile.ZipFile(fmu) as files:
3534
names = files.namelist()
36-
script_name = script_name.replace(".py", extension)
3735

3836
assert "modelDescription.xml" in names
3937
assert "/".join(("resources", script_name)) in names
@@ -62,11 +60,8 @@ def test_zip_content(tmp_path, compile):
6260

6361
# Check pythonfmu is embedded
6462
pkg_folder = Path(pythonfmu3.__path__[0])
65-
for f in pkg_folder.rglob(f"*.py"):
63+
for f in pkg_folder.rglob("*.py"):
6664
relative_f = f.relative_to(pkg_folder).as_posix()
67-
# change relative f suffix
68-
if relative_f.endswith(".py"):
69-
relative_f = relative_f.replace(".py", extension)
7065
if "test" not in relative_f:
7166
assert "/".join(("resources", "pythonfmu3", relative_f)) in names
7267

@@ -106,6 +101,7 @@ def build():
106101
project_files.add(full_name.parent)
107102

108103
return FmuBuilder.build_FMU(script_file, dest=tmp_path, project_files=project_files)
104+
109105
fmu = build()
110106
with zipfile.ZipFile(fmu) as files:
111107
names = files.namelist()
@@ -123,8 +119,7 @@ def build():
123119

124120

125121
@pytest.mark.parametrize("pfiles", PROJECT_TEST_CASES)
126-
@pytest.mark.parametrize("compile", [False, True])
127-
def test_project_files_containing_script(tmp_path, pfiles, compile):
122+
def test_project_files_containing_script(tmp_path, pfiles):
128123
orig_script_file = Path(__file__).parent / "slaves/pythonslave.py"
129124
pfiles = map(Path, pfiles)
130125

@@ -144,7 +139,7 @@ def build():
144139
full_name.mkdir(parents=True, exist_ok=True)
145140

146141
return FmuBuilder.build_FMU(
147-
script_file, dest=tmp_path, project_files=[script_file.parent], compile=compile
142+
script_file, dest=tmp_path, project_files=[script_file.parent]
148143
)
149144

150145
fmu = build()

0 commit comments

Comments
 (0)