Skip to content

Commit 2eeec1d

Browse files
author
Sam Hatchett
committed
adds a quite-thin swig wrapper
1 parent 4264eec commit 2eeec1d

File tree

15 files changed

+370
-1
lines changed

15 files changed

+370
-1
lines changed

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,9 @@ output.py
7878
output_wrap.c
7979
toolkit.py
8080
toolkit_wrap.c
81+
MANIFEST
82+
owa-epanet/dist/
83+
owa-epanet/_skbuild/
84+
85+
*.so
86+
*.dylib

.gitmodules

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[submodule "owa-epanet/EPANET"]
2+
path = owa-epanet/EPANET
3+
url = https://github.com/OpenWaterAnalytics/EPANET.git
4+
branch = dev

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ Is home for Python packages related to the EPANET engine.
99

1010

1111
## Contents
12+
* owa-epanet - the thinnest of SWIG-generated wrappers for EPANET.
1213
* epanet-module - A ctypes based wrapper for the EPANET Toolkit (with support for reading EPANET binary output files).
1314
* epanet-python - SWIG based wrappers for the EPANET Toolkit and Output libraries.
1415
* ...
1516

1617

1718
## Motivation
18-
These Python wrappers for EPANET are available to developers interested in building higher level functionality on top. Starting with a clean, auto-generated python API wrapper is a good foundation for building more abstractions. This also intersects with the near-term needs of the GUI work (which uses python and QT) - SWIG-wrapping will mean that the epanet library becomes scriptable from within the GUI.
19+
These Python wrappers for EPANET are available to developers interested in building higher level functionality on top. Starting with a clean, auto-generated python API wrapper is a good foundation for building more abstractions. SWIG-wrapping will mean that the epanet library becomes scriptable from within a GUI.
1920

2021
Another benefit of auto-generating the wrapper is that it's fairly unambiguous; nobody's personal preferences get involved until we get to slightly higher-level abstractions, but everyone can share and benefit from the foundational SWIG layer.
2122

owa-epanet/CMakeLists.txt

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
cmake_minimum_required(VERSION 3.8)
2+
3+
if(SKBUILD)
4+
message(STATUS "The project is built using scikit-build")
5+
endif()
6+
7+
find_package(PythonLibs 3 REQUIRED)
8+
find_package(PythonInterp ${PYTHONLIBS_VERSION_STRING} REQUIRED)
9+
find_package(SWIG REQUIRED)
10+
include(${SWIG_USE_FILE})
11+
set(CMAKE_SWIG_FLAGS -py3)
12+
13+
message("PYTHONLIBS_VERSION_STRING: ${PYTHONLIBS_VERSION_STRING}")
14+
message("CMAKE_SWIG_FLAGS: ${CMAKE_SWIG_FLAGS}")
15+
16+
INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_PATH})
17+
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})
18+
INCLUDE_DIRECTORIES(EPANET/include)
19+
INCLUDE_DIRECTORIES(EPANET/src/outfile/include)
20+
21+
SET(CMAKE_SWIG_FLAGS "")
22+
23+
SET_SOURCE_FILES_PROPERTIES(epanet.i PROPERTIES CPLUSPLUS ON)
24+
SET_SOURCE_FILES_PROPERTIES(epanet.i PROPERTIES SWIG_FLAGS "-includeall")
25+
26+
# build the EPANET library
27+
ADD_SUBDIRECTORY(EPANET)
28+
29+
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
30+
SWIG_ADD_LIBRARY( toolkit LANGUAGE python SOURCES wrapper/toolkit.i)
31+
set_property(SOURCE toolkit.i PROPERTY USE_LIBRARY_INCLUDE_DIRECTORIES TRUE)
32+
set_property(TARGET epanet2 PROPERTY SWIG_USE_TARGET_INCLUDE_DIRECTORIES TRUE)
33+
SWIG_LINK_LIBRARIES(toolkit epanet2)
34+
SWIG_LINK_LIBRARIES(toolkit ${PYTHON_LIBRARIES})
35+
set_property(TARGET _toolkit PROPERTY INSTALL_RPATH "$ORIGIN")
36+
37+
IF (APPLE)
38+
set_target_properties(_toolkit PROPERTIES INSTALL_RPATH "@loader_path")
39+
ENDIF (APPLE)
40+
41+
install(TARGETS _toolkit LIBRARY DESTINATION packages/epanet)
42+
43+
add_custom_command(
44+
TARGET _toolkit POST_BUILD
45+
COMMAND ${CMAKE_COMMAND} -E copy
46+
${CMAKE_CURRENT_BINARY_DIR}/toolkit.py
47+
${CMAKE_CURRENT_BINARY_DIR}/../../../packages/epanet/toolkit.py)
48+
49+
add_custom_command(
50+
TARGET _toolkit POST_BUILD
51+
COMMAND ${CMAKE_COMMAND} -E copy
52+
${CMAKE_CURRENT_BINARY_DIR}/lib/libepanet2.*
53+
${CMAKE_CURRENT_BINARY_DIR}/../../../packages/epanet/)

owa-epanet/EPANET

Submodule EPANET added at a740120

owa-epanet/LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2019 CitiLogics
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.

owa-epanet/MANIFEST.in

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
include packages/epanet/*.py
2+
include setup.py
3+
include README.md
4+
include CMakeLists.txt
5+
include LICENSE
6+
include wrapper/*.i
7+
graft EPANET
8+
prune _skbuild
9+
prune _cmake_test_compile
10+
prune *.so
11+
prune *.dylib
12+
prune *.dll

owa-epanet/README.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# epanet python package
2+
3+
A slender, auto-generated python wrapper around owa:epanet hydraulic network analysis toolkit. This package uses SWIG and scikit-build to generate python bindings into the C library. The goal of this package is to establish basic python support for the toolkit, rather than present a "pythonic" interface. More abstractions can be built atop this package to further abstract the API, but the set of functions here is meant to (as closely as practical) mirror the well-known and established C API.
4+
5+
Where possible, SWIG has been configured to throw warnings/exceptions instead of using the customary EPANET return integer value for success-checking. Also any output (pointer) parameters from the C API have been re-routed to return values. In these cases, the return tuple from the Python API will contain the values desired.
6+
7+
```
8+
9+
./scripts/clean.sh
10+
python3 setup.py sdist bdist_wheel
11+
cd test && pipenv install ../dist/*.whl && pipenv run python -c 'from epanet import toolkit; print(toolkit.__dict__)'
12+
13+
```
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
name = "epanet"
2+
__all__ = ["epanet"]

owa-epanet/pyproject.toml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[build-system]
2+
requires = ["setuptools", "wheel", "scikit-build", "cmake"]

owa-epanet/scripts/clean.sh

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env bash
2+
3+
rm -rf \
4+
packages/epanet/toolkit.py \
5+
packages/epanet/output.py \
6+
packages/epanet/*.so \
7+
packages/epanet/*.dylib \
8+
_skbuild \
9+
_cmake_test_compile \
10+
dist \
11+
packages/epanet.egg-info \
12+
test/*
13+
14+
touch test/Pipfile

owa-epanet/setup.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from skbuild import setup
2+
from setuptools import find_packages
3+
4+
with open("README.md", "r") as fh:
5+
long_description = fh.read()
6+
7+
setup(
8+
name = "owa-epanet",
9+
version = "1.0.0",
10+
author = "Sam Hatchett",
11+
author_email = "[email protected]",
12+
description = "a thin wrapper for epanet hydraulic toolkit",
13+
long_description = long_description,
14+
long_description_content_type="text/markdown",
15+
url = "https://github.com/OpenWaterAnalytics/epanet-python",
16+
cmake_args=["-DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.9"],
17+
#cmake_with_sdist = True,
18+
package_dir = {"":"packages"},
19+
packages = ["epanet"],
20+
package_data = {"epanet":["*.dylib", "*.dll", "*.so"]},
21+
zip_safe=False
22+
)

owa-epanet/test/Pipfile

Whitespace-only changes.

owa-epanet/wrapper/output.i

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
%include "typemaps.i"
2+
%include "cstring.i"
3+
4+
/* epanet simple python wrapper */
5+
%module (package="epanet") output
6+
%{
7+
#define SHARED_EXPORTS_BUILT_AS_STATIC
8+
#include <epanet_output.h>
9+
%}
10+
%include <epanet_output_enums.h>
11+
12+
/* strip the pseudo-scope from function declarations */
13+
%rename("%(strip:[ENR_])s") "";
14+
15+
%typemap(in,numinputs=0) ENR_Handle* p_handle_out (ENR_Handle temp) {
16+
$1 = &temp;
17+
}
18+
19+
%typemap(argout) ENR_Handle* p_handle_out {
20+
%append_output(SWIG_NewPointerObj(SWIG_as_voidptr(retval$argnum), $1_descriptor, 0));
21+
}
22+
23+
/* TYPEMAP FOR IGNORING INT ERROR CODE RETURN VALUE */
24+
%typemap(out) int {
25+
$result = Py_None;
26+
Py_INCREF($result);
27+
}
28+
29+
30+
/* TYPEMAPS FOR MEMORY MANAGEMENT OF FLOAT ARRAYS */
31+
%typemap(in, numinputs=0)float** float_out (float* temp), int* int_dim (int temp){
32+
$1 = &temp;
33+
}
34+
%typemap(argout) (float** float_out, int* int_dim) {
35+
if (*$1) {
36+
PyObject *o = PyList_New(*$2);
37+
int i;
38+
float* temp = *$1;
39+
for(i=0; i<*$2; i++) {
40+
PyList_SetItem(o, i, PyFloat_FromDouble((double)temp[i]));
41+
}
42+
$result = SWIG_Python_AppendOutput($result, o);
43+
free(*$1);
44+
}
45+
}
46+
47+
/* TYPEMAPS FOR MEMORY MANAGEMENT OF INT ARRAYS */
48+
%typemap(in, numinputs=0)int** int_out (long* temp), int* int_dim (int temp){
49+
$1 = &temp;
50+
}
51+
%typemap(argout) (int** int_out, int* int_dim) {
52+
if (*$1) {
53+
PyObject *o = PyList_New(*$2);
54+
int i;
55+
long* temp = *$1;
56+
for(i=0; i<*$2; i++) {
57+
PyList_SetItem(o, i, PyInt_FromLong(temp[i]));
58+
}
59+
$result = SWIG_Python_AppendOutput($result, o);
60+
free(*$1);
61+
}
62+
}
63+
64+
/* TYPEMAP FOR MEMORY MANAGEMENT AND ENCODING OF STRINGS */
65+
%typemap(in, numinputs=0)char** string_out (char* temp), int* slen (int temp){
66+
$1 = &temp;
67+
}
68+
%typemap(argout)(char** string_out, int* slen) {
69+
if (*$1) {
70+
PyObject* o;
71+
o = PyUnicode_FromStringAndSize(*$1, *$2);
72+
73+
$result = SWIG_Python_AppendOutput($result, o);
74+
free(*$1);
75+
}
76+
}
77+
78+
%apply int *OUTPUT {
79+
int *int_out
80+
};
81+
82+
/* INSERTS CUSTOM EXCEPTION HANDLING IN WRAPPER */
83+
%exception
84+
{
85+
$action
86+
if ( result > 0) {
87+
PyErr_SetString(PyExc_Exception, "ERROR");
88+
SWIG_fail;
89+
}
90+
}
91+
92+
%feature("autodoc", "2");
93+
#define SHARED_EXPORTS_BUILT_AS_STATIC
94+
%include <epanet_output.h>
95+
96+
%exception;

owa-epanet/wrapper/toolkit.i

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
%include "typemaps.i"
2+
%include "cstring.i"
3+
4+
/* epanet simple python wrapper */
5+
%module (package="epanet") toolkit
6+
%{
7+
#include <epanet2_2.h>
8+
%}
9+
%include <epanet2_enums.h>
10+
11+
/* strip the pseudo-scope from function declarations */
12+
%rename("%(strip:[EN_])s") "";
13+
14+
%typemap(in,numinputs=0) EN_Project* (EN_Project temp) {
15+
$1 = &temp;
16+
}
17+
18+
%typemap(argout) EN_Project* {
19+
%append_output(SWIG_NewPointerObj(*$1, SWIGTYPE_p_Project, SWIG_POINTER_NEW));
20+
}
21+
22+
/* TYPEMAP FOR IGNORING INT ERROR CODE RETURN VALUE */
23+
%typemap(out) int {
24+
$result = Py_None;
25+
Py_INCREF($result);
26+
}
27+
28+
%delobject deleteproject;
29+
30+
struct Project {};
31+
%extend Project {
32+
~Project() {
33+
EN_deleteproject($self);
34+
}
35+
};
36+
%ignore Project;
37+
38+
%apply int *OUTPUT {
39+
int *count,
40+
int *version,
41+
int *units,
42+
int *qualType,
43+
int *traceNode,
44+
int *index,
45+
int *nodeType,
46+
int *type,
47+
int *demandIndex,
48+
int *numDemands,
49+
int *patIndex,
50+
int *linkType,
51+
int *node1,
52+
int *node2,
53+
int *pumpType,
54+
int *curveIndex,
55+
int *len,
56+
int *nPoints,
57+
int *nodeIndex,
58+
int *linkIndex,
59+
int *nPremises,
60+
int *nThenActions,
61+
int *nElseActions,
62+
int *logop,
63+
int *object,
64+
int *objIndex,
65+
int *variable,
66+
int *relop,
67+
int *status
68+
};
69+
70+
%apply double *OUTPUT {
71+
double *value,
72+
double *x,
73+
double *y,
74+
double *baseDemand,
75+
double *pmin,
76+
double *preq,
77+
double *pexp,
78+
double *setting,
79+
double *level,
80+
double *priority
81+
};
82+
83+
%apply long *OUTPUT {
84+
long *value
85+
}
86+
87+
%cstring_bounded_output(char *OUTCHAR, EN_MAXMSG);
88+
89+
%apply char *OUTCHAR {
90+
char *out_line1,
91+
char *out_line2,
92+
char *out_line3,
93+
char *out_comment,
94+
char *out_errmsg,
95+
char *out_chemName,
96+
char *out_chemUnits,
97+
char *out_id,
98+
char *out_demandName
99+
};
100+
101+
%apply int *INOUT {
102+
int *inout_index
103+
}
104+
105+
106+
/* INSERTS CUSTOM EXCEPTION HANDLING IN WRAPPER */
107+
%exception
108+
{
109+
$action
110+
if ( result > 10) {
111+
PyErr_SetString(PyExc_Exception, "ERROR");
112+
SWIG_fail;
113+
}
114+
else if (result > 0) {
115+
PyErr_WarnEx(PyExc_Warning, "WARNING", 2);
116+
}
117+
}
118+
119+
%feature("autodoc", "2");
120+
%include <epanet2_2.h>
121+
122+
%exception;

0 commit comments

Comments
 (0)