| 180 | class PythonEngine final : public ScriptEngine { |
| 181 | public: |
| 182 | Expected<std::vector<FilterClass>> inspectModule(const std::string& source, const std::string& origin) override { |
| 183 | ensureInterpreter(); |
| 184 | py::gil_scoped_acquire gil; |
| 185 | try { |
| 186 | py::dict ns; |
| 187 | py::exec(source, ns); |
| 188 | if (!ns.contains("T")) { |
| 189 | return PJ::unexpected("Python filter module must define a top-level class named 'T'"); |
| 190 | } |
| 191 | py::object cls = ns["T"]; |
| 192 | if (!py::hasattr(cls, "id")) { |
| 193 | return PJ::unexpected("Python filter class 'T' must define an 'id'"); |
| 194 | } |
| 195 | FilterClass fc; |
| 196 | fc.id = attrString(cls, "id", ""); |
| 197 | if (fc.id.empty()) { |
| 198 | return PJ::unexpected("Python filter class 'T' has an empty 'id'"); |
| 199 | } |
| 200 | fc.name = attrString(cls, "name", fc.id); |
| 201 | fc.description = attrString(cls, "description", ""); |
| 202 | fc.version = attrString(cls, "version", ""); |
| 203 | fc.output_kind = attrString(cls, "output", "double"); |
| 204 | fc.source = source; |
| 205 | fc.origin = origin; |
| 206 | return std::vector<FilterClass>{std::move(fc)}; |
| 207 | } catch (const py::error_already_set& e) { |
| 208 | return PJ::unexpected(std::string("Python error: ") + e.what()); |
| 209 | } catch (const std::exception& e) { |
| 210 | return PJ::unexpected(std::string("Python error: ") + e.what()); |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | Expected<std::unique_ptr<FilterInstance>> createInstance( |
| 215 | const FilterClass& klass, const std::string& params_json) override { |
nothing calls this directly
no test coverage detected