I try to install the GDAL library (to use the OGR module >> FlattenTo2D() method) on Heroku. I found 2 sources how to do this:
- Using buildstacks with github (https://github.com/heroku/heroku-geo-buildpack.git)
- Working with an Apt file
It is not clear to me which procedure is still valid so I used both (In the mean time I heard from Heroku support that both procedures are still valid...) Once I deploy my code to Heroku it seems that the GDAL library is installed. However, when I run my python script (with command: from osgeo import ogr) I get the following error:'ModuleNotFoundError: No module named 'osgeo'.
I then also added 'gdal' to the requirements.txt file to import the module and see if this would solve the problem. However, when I try to deploy the requirements.txt list, I get the following Heroku log error (=extraction since > 30k characters):
-----> Building on the Heroku-20 stack
-----> Using buildpacks:
1. https://github.com/heroku/heroku-geo-buildpack.git
2. heroku/python
-----> Geo Packages (GDAL/GEOS/PROJ) app detected
-----> Installing GDAL-3.5.0
-----> Installing GEOS-3.10.2
-----> Installing PROJ-8.2.1
-----> Python app detected
-----> No Python version was specified. Using the same version as the last build: python-3.11.2
To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes
!
! A Python security update is available! Upgrade as soon as possible to: python-3.11.6
! See: https://devcenter.heroku.com/articles/python-runtimes
!
-----> Requirements file has been changed, clearing cached dependencies
-----> Installing python-3.11.2
-----> Installing pip 23.3.1, setuptools 68.0.0 and wheel 0.41.3
-----> Installing SQLite3
-----> Installing requirements with pip
Collecting requests (from -r requirements.txt (line 1))
Downloading requests-2.31.0-py3-none-any.whl.metadata (4.6 kB)
Collecting geojson (from -r requirements.txt (line 2))
Downloading geojson-3.1.0-py3-none-any.whl.metadata (16 kB)
Collecting gdal (from -r requirements.txt (line 9))
Downloading GDAL-3.7.3.tar.gz (777 kB)
Preparing metadata (setup.py): started
Preparing metadata (setup.py): finished with status 'done'
Building wheel for gdal (setup.py): started
Building wheel for gdal (setup.py): finished with status 'error'
error: subprocess-exited-with-error
× python setup.py bdist_wheel did not run successfully.
│ exit code: 1
╰─> [710 lines of output]
WARNING: numpy not available! Array support will not be enabled
running bdist_wheel
running build
running build_py
creating build
creating build/lib.linux-x86_64-cpython-311
creating build/lib.linux-x86_64-cpython-311/osgeo
-> build/lib.linux-x86_64-cpython-311/osgeo_utils/samples
copying gdal-utils/osgeo_utils/samples/gdallocationinfo.py -> build/lib.linux-x86_64-cpython-311/osgeo_utils/samples
copying gdal-utils/osgeo_utils/samples/gdal_rm.py -> build/lib.linux-x86_64-cpython-311/osgeo_utils/samples
copying gdal-utils/osgeo_utils/samples/gdal_create_pdf.py -> build/lib.linux-x86_64-cpython-311/osgeo_utils/samples
copying gdal-utils/osgeo_utils/samples/vec_tr_spat.py -> build/lib.linux-x86_64-cpython-311/osgeo_utils/samples
copying gdal-utils/osgeo_utils/samples/magphase.py -> build/lib.linux-x86_64-cpython-311/osgeo_utils/samples
copying gdal-utils/osgeo_utils/samples/get_soundg.py -> build/lib.linux-x86_64-cpython-311/osgeo_utils/samples
copying gdal-utils/osgeo_utils/samples/gdalfilter.py -> build/lib.linux-x86_64-cpython-311/osgeo_utils/samples
copying gdal-utils/osgeo_utils/auxiliary/color_table.py -> build/lib.linux-x86_64-cpython-311/osgeo_utils/auxiliary
running build_ext
gcc -pthread -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/app/.heroku/python/include/python3.11 -I/tmp/tmp.rTYO0BrQbc/include -c gdal_python_cxx11_test.cpp -o gdal_python_cxx11_test.o
building 'osgeo._gdal' extension
building 'osgeo._gdalconst' extension
creating build/temp.linux-x86_64-cpython-311
creating build/temp.linux-x86_64-cpython-311
creating build/temp.linux-x86_64-cpython-311/extensions
creating build/temp.linux-x86_64-cpython-311/extensions
gcc -pthread -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/app/.heroku/python/include/python3.11 -I/tmp/tmp.rTYO0BrQbc/include -c extensions/gdalconst_wrap.c -o build/temp.linux-x86_64-cpython-311/extensions/gdalconst_wrap.o -I/tmp/tmp.rTYO0BrQbc/include
gcc -pthread -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/app/.heroku/python/include/python3.11 -I/tmp/tmp.rTYO0BrQbc/include -c extensions/gdal_wrap.cpp -o build/temp.linux-x86_64-cpython-311/extensions/gdal_wrap.o -I/tmp/tmp.rTYO0BrQbc/include
extensions/gdalconst_wrap.c: In function ‘PyInit__gdalconst’:
extensions/gdalconst_wrap.c:3522:61: error: ‘GDT_Int8’ undeclared (first use in this function); did you mean ‘GDT_Int64’?
3522 | SWIG_Python_SetConstant(d, "GDT_Int8",SWIG_From_int((int)(GDT_Int8)));
| ^~~~~~~~
| GDT_Int64
extensions/gdalconst_wrap.c:3522:61: note: each undeclared identifier is reported only once for each function it appears in
extensions/gdalconst_wrap.c:3631:83: error: ‘GDAL_DMD_MULTIDIM_ARRAY_OPENOPTIONLIST’ undeclared (first use in this function); did you mean ‘GDAL_DMD_MULTIDIM_ARRAY_CREATIONOPTIONLIST’?
3631 | SWIG_Python_SetConstant(d, "DMD_MULTIDIM_ARRAY_OPENOPTIONLIST",SWIG_FromCharPtr(GDAL_DMD_MULTIDIM_ARRAY_OPENOPTIONLIST));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| GDAL_DMD_MULTIDIM_ARRAY_CREATIONOPTIONLIST
extensions/gdalconst_wrap.c:3743:68: error: ‘GRT_AGGREGATION’ undeclared (first use in this function)
3743 | SWIG_Python_SetConstant(d, "GRT_AGGREGATION",SWIG_From_int((int)(GRT_AGGREGATION)));
| ^~~~~~~~~~~~~~~
extensions/gdalconst_wrap.c:3746:3: warning: ‘PyEval_InitThreads’ is deprecated [-Wdeprecated-declarations]
3746 | SWIG_PYTHON_INITIALIZE_THREADS;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /app/.heroku/python/include/python3.11/Python.h:95,
from extensions/gdalconst_wrap.c:156:
/app/.heroku/python/include/python3.11/ceval.h:132:37: note: declared here
132 | Py_DEPRECATED(3.9) PyAPI_FUNC(void) PyEval_InitThreads(void);
| ^~~~~~~~~~~~~~~~~~
building 'osgeo._osr' extension
gcc -pthread -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/app/.heroku/python/include/python3.11 -I/tmp/tmp.rTYO0BrQbc/include -c extensions/osr_wrap.cpp -o build/temp.linux-x86_64-cpython-311/extensions/osr_wrap.o -I/tmp/tmp.rTYO0BrQbc/include
extensions/gdal_wrap.cpp: In function ‘int getAlignment(GDALDataType)’:
extensions/gdal_wrap.cpp:2947:14: error: ‘GDT_Int8’ was not declared in this scope; did you mean ‘GDT_Int64’?
2947 | case GDT_Int8:
| ^~~~~~~~
| GDT_Int64
extensions/gdal_wrap.cpp: In function ‘void pushErrorHandler()’:
extensions/gdal_wrap.cpp:3352:39: error: ‘CPLGetErrorHandler’ was not declared in this scope; did you mean ‘CPLSetErrorHandler’?
3352 | CPLErrorHandler previousHandler = CPLGetErrorHandler(&pPreviousHandlerUserData);
| ^~~~~~~~~~~~~~~~~~
| CPLSetErrorHandler
| void CPLErr
extensions/osr_wrap.cpp: In function ‘void pushErrorHandler()’:
extensions/osr_wrap.cpp:3169:39: error: ‘CPLGetErrorHandler’ was not declared in this scope; did you mean ‘CPLSetErrorHandler’?
3169 | CPLErrorHandler previousHandler = CPLGetErrorHandler(&pPreviousHandlerUserData);
| ^~~~~~~~~~~~~~~~~~
| CPLSetErrorHandler
extensions/gdal_wrap.cpp: At global scope:
extensions/gdal_wrap.cpp:6839:133: error: ‘GDALRelationshipCardinality’ has not been declared
6839 | SWIGINTERN GDALRelationshipShadow *new_GDALRelationshipShadow(char const *name,char const *leftTableName,char const *rightTableName,GDALRelationshipCardinality cardinality){
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
| ^~~~~~~~~~~~~~~~~~~~~~~~
| _wrap_SetPathSpecificOption
extensions/osr_wrap.cpp: In function ‘PyObject* PyInit__osr()’:
extensions/osr_wrap.cpp:982:65: warning: ‘void PyEval_InitThreads()’ is deprecated [-Wdeprecated-declarations]
982 | # define SWIG_PYTHON_INITIALIZE_THREADS PyEval_InitThreads()
| ^
extensions/osr_wrap.cpp:20291:3: note: in expansion of macro ‘SWIG_PYTHON_INITIALIZE_THREADS’
20291 | SWIG_PYTHON_INITIALIZE_THREADS;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /app/.heroku/python/include/python3.11/Python.h:95,
from extensions/osr_wrap.cpp:180:
/app/.heroku/python/include/python3.11/ceval.h:132:37: note: declared here
132 | Py_DEPRECATED(3.9) PyAPI_FUNC(void) PyEval_InitThreads(void);
| ^~~~~~~~~~~~~~~~~~
extensions/osr_wrap.cpp:982:65: warning: ‘void PyEval_InitThreads()’ is deprecated [-Wdeprecated-declarations]
982 | # define SWIG_PYTHON_INITIALIZE_THREADS PyEval_InitThreads()
| ^
extensions/osr_wrap.cpp:20291:3: note: in expansion of macro ‘SWIG_PYTHON_INITIALIZE_THREADS’
20291 | SWIG_PYTHON_INITIALIZE_THREADS;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /app/.heroku/python/include/python3.11/Python.h:95,
from extensions/osr_wrap.cpp:180:
/app/.heroku/python/include/python3.11/ceval.h:132:37: note: declared here
132 | Py_DEPRECATED(3.9) PyAPI_FUNC(void) PyEval_InitThreads(void);
| ^~~~~~~~~~~~~~~~~~
building 'osgeo._ogr' extension
gcc -pthread -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/app/.heroku/python/include/python3.11 -I/tmp/tmp.rTYO0BrQbc/include -c extensions/ogr_wrap.cpp -o build/temp.linux-x86_64-cpython-311/extensions/ogr_wrap.o -I/tmp/tmp.rTYO0BrQbc/include
extensions/ogr_wrap.cpp:2852:10: fatal error: ogr_recordbatch.h: No such file or directory
2852 | #include "ogr_recordbatch.h"
| ^~~~~~~~~~~~~~~~~~~
compilation terminated.
building 'osgeo._gnm' extension
gcc -pthread -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/app/.heroku/python/include/python3.11 -I/tmp/tmp.rTYO0BrQbc/include -c extensions/gnm_wrap.cpp -o build/temp.linux-x86_64-cpython-311/extensions/gnm_wrap.o -I/tmp/tmp.rTYO0BrQbc/include
extensions/gdal_wrap.cpp: In function ‘PyObject* _wrap_new_Relationship(PyObject*, PyObject*)’:
extensions/gdal_wrap.cpp:38836:3: error: ‘GDALRelationshipCardinality’ was not declared in this scope
38836 | GDALRelationshipCardinality arg4 ;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
extensions/gdal_wrap.cpp:38871:3: error: ‘arg4’ was not declared in this scope; did you mean ‘arg3’?
38871 | arg4 = static_cast< GDALRelationshipCardinality >(val4);
| ^~~~
| arg3
extensions/gdal_wrap.cpp:38871:23: error: ‘GDALRelationshipCardinality’ does not name a type
38871 | arg4 = static_cast< GDALRelationshipCardinality >(val4);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
extensions/gdal_wrap.cpp: In function ‘PyObject* _wrap_Relationship_GetCardinality(PyObject*, PyObject*)’:
extensions/gdal_wrap.cpp:39008:3: error: ‘GDALRelationshipCardinality’ was not declared in this scope; did you mean ‘_wrap_Relationship_GetCardinality’?
39008 | GDALRelationshipCardinality result;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
| _wrap_Relationship_GetCardinality
extensions/gdal_wrap.cpp:39024:7: error: ‘result’ was not declared in this scope
39024 | result = (GDALRelationshipCardinality)GDALRelationshipShadow_GetCardinality(arg1);
| ^~~~~~
extensions/gdal_wrap.cpp:39039:48: error: ‘result’ was not declared in this scope
39039 | resultobj = SWIG_From_int(static_cast< int >(result));
| ^~~~~~
extensions/gdal_wrap.cpp: In function ‘PyObject* _wrap_Relationship_GetType(PyObject*, PyObject*)’:
extensions/gdal_wrap.cpp:39748:3: error: ‘GDALRelationshipType’ was not declared in this scope; did you mean ‘GDALRelationshipShadow’?
39748 | GDALRelationshipType result;
| ^~~~~~~~~~~~~~~~~~~~
| GDALRelationshipShadow
extensions/gdal_wrap.cpp:39764:7: error: ‘result’ was not declared in this scope
39764 | result = (GDALRelationshipType)GDALRelationshipShadow_GetType(arg1);
| ^~~~~~
extensions/gdal_wrap.cpp:39779:48: error: ‘result’ was not declared in this scope
39779 | resultobj = SWIG_From_int(static_cast< int >(result));
| ^~~~~~
extensions/gdal_wrap.cpp: In function ‘PyObject* _wrap_Relationship_SetType(PyObject*, PyObject*)’:
extensions/gdal_wrap.cpp:39790:3: error: ‘GDALRelationshipType’ was not declared in this scope; did you mean ‘GDALRelationshipShadow’?
39790 | GDALRelationshipType arg2 ;
| ^~~~~~~~~~~~~~~~~~~~
| GDALRelationshipShadow
extensions/gdal_wrap.cpp:39807:3: error: ‘arg2’ was not declared in this scope; did you mean ‘arg1’?
39807 | arg2 = static_cast< GDALRelationshipType >(val2);
| ^~~~
| arg1
extensions/gdal_wrap.cpp:39807:23: error: ‘GDALRelationshipType’ does not name a type; did you mean ‘GDALRelationshipShadow’?
39807 | arg2 = static_cast< GDALRelationshipType >(val2);
| ^~~~~~~~~~~~~~~~~~~~
| GDALRelationshipShadow
extensions/gdal_wrap.cpp: In function ‘PyObject* _wrap_new_GDALVectorInfoOptions(PyObject*, PyObject*)’:
extensions/gdal_wrap.cpp:46039:3: error: ‘GDALVectorInfoOptions’ was not declared in this scope; did you mean ‘GDALInfoOptions’?
46039 | GDALVectorInfoOptions *result = 0 ;
| ^~~~~~~~~~~~~~~~~~~~~
| GDALInfoOptions
extensions/gdal_wrap.cpp:46039:26: error: ‘result’ was not declared in this scope; did you mean ‘resultobj’?
46039 | GDALVectorInfoOptions *result = 0 ;
| ^~~~~~
| resultobj
extensions/gdal_wrap.cpp:46059:40: error: expected primary-expression before ‘)’ token
46059 | result = (GDALVectorInfoOptions *)new_GDALVectorInfoOptions(arg1);
| ^
extensions/gdal_wrap.cpp: In function ‘PyObject* _wrap_delete_GDALVectorInfoOptions(PyObject*, PyObject*)’:
extensions/gdal_wrap.cpp:46092:3: error: ‘GDALVectorInfoOptions’ was not declared in this scope; did you mean ‘GDALInfoOptions’?
46092 | GDALVectorInfoOptions *arg1 = (GDALVectorInfoOptions *) 0 ;
| ^~~~~~~~~~~~~~~~~~~~~
| GDALInfoOptions
extensions/gdal_wrap.cpp:46092:26: error: ‘arg1’ was not declared in this scope; did you mean ‘args’?
46092 | GDALVectorInfoOptions *arg1 = (GDALVectorInfoOptions *) 0 ;
| ^~~~
| args
extensions/gdal_wrap.cpp:46092:57: error: expected primary-expression before ‘)’ token
46092 | GDALVectorInfoOptions *arg1 = (GDALVectorInfoOptions *) 0 ;
| ^
extensions/gdal_wrap.cpp:46103:28: error: ‘GDALVectorInfoOptions’ does not name a type; did you mean ‘GDALInfoOptions’?
46103 | arg1 = reinterpret_cast< GDALVectorInfoOptions * >(argp1);
| ^~~~~~~~~~~~~~~~~~~~~
| GDALInfoOptions
extensions/gdal_wrap.cpp:46103:50: error: expected ‘>’ before ‘*’ token
46103 | arg1 = reinterpret_cast< GDALVectorInfoOptions * >(argp1);
| ^
extensions/gdal_wrap.cpp:46103:50: error: expected ‘(’ before ‘*’ token
46103 | arg1 = reinterpret_cast< GDALVectorInfoOptions * >(argp1);
| ^
| (
extensions/gdal_wrap.cpp:46103:52: error: expected primary-expression before ‘>’ token
46103 | arg1 = reinterpret_cast< GDALVectorInfoOptions * >(argp1);
| ^
extensions/gdal_wrap.cpp:46103:60: error: expected ‘)’ before ‘;’ token
46103 | arg1 = reinterpret_cast< GDALVectorInfoOptions * >(argp1);
| ^
| )
extensions/gdal_wrap.cpp:46111:7: error: ‘delete_GDALVectorInfoOptions’ was not declared in this scope; did you mean ‘_wrap_delete_GDALVectorInfoOptions’?
46111 | delete_GDALVectorInfoOptions(arg1);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
| _wrap_delete_GDALVectorInfoOptions
extensions/gdal_wrap.cpp: In function ‘PyObject* _wrap_VectorInfoInternal(PyObject*, PyObject*)’:
extensions/gdal_wrap.cpp:46148:3: error: ‘GDALVectorInfoOptions’ was not declared in this scope; did you mean ‘GDALInfoOptions’?
| (
extensions/gdal_wrap.cpp:46166:52: error: expected primary-expression before ‘>’ token
46166 | arg2 = reinterpret_cast< GDALVectorInfoOptions * >(argp2);
| ^
extensions/gdal_wrap.cpp:46166:60: error: expected ‘)’ before ‘;’ token
46166 | arg2 = reinterpret_cast< GDALVectorInfoOptions * >(argp2);
| ^
| )
extensions/gdal_wrap.cpp:46174:39: error: ‘GDALVectorInfo’ was not declared in this scope; did you mean ‘GDALVersionInfo’?
46174 | result = (retStringAndCPLFree *)GDALVectorInfo(arg1,arg2);
| ^~~~~~~~~~~~~~
| GDALVersionInfo
extensions/gdal_wrap.cpp: In function ‘PyObject* PyInit__gdal()’:
extensions/gdal_wrap.cpp:982:65: warning: ‘void PyEval_InitThreads()’ is deprecated [-Wdeprecated-declarations]
982 | # define SWIG_PYTHON_INITIALIZE_THREADS PyEval_InitThreads()
| ^~~~~~~~~~~~~~~~~~
extensions/gnm_wrap.cpp: In function ‘void pushErrorHandler()’:
extensions/gnm_wrap.cpp:3140:39: error: ‘CPLGetErrorHandler’ was not declared in this scope; did you mean ‘CPLSetErrorHandler’?
3140 | CPLErrorHandler previousHandler = CPLGetErrorHandler(&pPreviousHandlerUserData);
| ^~~~~~~~~~~~~~~~~~
| CPLSetErrorHandler
extensions/gnm_wrap.cpp: In function ‘PyObject* PyInit__gnm()’:
extensions/gnm_wrap.cpp:982:65: warning: ‘void PyEval_InitThreads()’ is deprecated [-Wdeprecated-declarations]
982 | # define SWIG_PYTHON_INITIALIZE_THREADS PyEval_InitThreads()
| ^
extensions/gnm_wrap.cpp:6770:3: note: in expansion of macro ‘SWIG_PYTHON_INITIALIZE_THREADS’
6770 | SWIG_PYTHON_INITIALIZE_THREADS;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /app/.heroku/python/include/python3.11/Python.h:95,
from extensions/gnm_wrap.cpp:180:
/app/.heroku/python/include/python3.11/ceval.h:132:37: note: declared here
132 | Py_DEPRECATED(3.9) PyAPI_FUNC(void) PyEval_InitThreads(void);
| ^~~~~~~~~~~~~~~~~~
extensions/gnm_wrap.cpp:982:65: warning: ‘void PyEval_InitThreads()’ is deprecated [-Wdeprecated-declarations]
982 | # define SWIG_PYTHON_INITIALIZE_THREADS PyEval_InitThreads()
| ^
extensions/gnm_wrap.cpp:6770:3: note: in expansion of macro ‘SWIG_PYTHON_INITIALIZE_THREADS’
6770 | SWIG_PYTHON_INITIALIZE_THREADS;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /app/.heroku/python/include/python3.11/Python.h:95,
from extensions/gnm_wrap.cpp:180:
/app/.heroku/python/include/python3.11/ceval.h:132:37: note: declared here
132 | Py_DEPRECATED(3.9) PyAPI_FUNC(void) PyEval_InitThreads(void);
| ^~~~~~~~~~~~~~~~~~
error: command '/usr/bin/gcc' failed with exit code 1
[end of output]
note: This error originates from a subprocess, and is likely not a problem with pip.
ERROR: Failed building wheel for gdal
Running setup.py clean for gdal
Building wheel for typing (setup.py): started
Building wheel for typing (setup.py): finished with status 'done'
Created wheel for typing: filename=typing-3.7.4.3-py3-none-any.whl size=26308 sha256=7ad5349105b142e3c06b3d1732ebfc8df0883cc868d8749af0bf8be0fb2424ce
Stored in directory: /tmp/pip-ephem-wheel-cache-0qd1bc96/wheels/9d/67/2f/53e3ef32ec48d11d7d60245255e2d71e908201d20c880c08ee
Successfully built psycopg2 area typing
Failed to build gdal
ERROR: Could not build wheels for gdal, which is required to install pyproject.toml-based projects
! Push rejected, failed to compile Python app.
! Push failed
A 'wheel' error or something to do with the python version...?
Any idea what could be the issue with GDAL not being found and particularely how I can install the OGR module that I am looking for? Thanks for helping.
The GDAL packages you get via the Geo buildpack and Apt contain the base library, which appears to be implemented in some combination of C and C++.
Bindings for Python and other languages need to be installed separately. It looks like you'll need to specify a version of the Python
gdal
library that's compatible with the underlying low-level library being used (3.5.0).gdal==3.5.0.3
appears to be the latest such version:Add this dependency to your
Pipfile
,requirements.txt
, orsetup.py
file (do whatever you're already doing for other dependencies), commit, and redeploy.The Python bindings depend on the low-level library, so you'll need the base GDAL library as well as the Python library.
Side note: I don't recommend using the Geo buildpack and apt GDAL packages together. They could interfere with each other. Pick one.