diff --git a/docs/api/datadir.rst b/docs/api/datadir.rst index 6e1a1d728..bfec66d41 100644 --- a/docs/api/datadir.rst +++ b/docs/api/datadir.rst @@ -1,7 +1,7 @@ -.. _data_and_ca_bundle_directory: +.. _data_directory: -Data Directory & CA Bundle Path -=============================== +Data Directory +=============== pyproj.datadir.get_data_dir --------------------------- @@ -25,9 +25,3 @@ pyproj.datadir.get_user_data_dir --------------------------------- .. autofunction:: pyproj.datadir.get_user_data_dir - - -pyproj.set_ca_bundle_path --------------------------- - -.. autofunction:: pyproj.set_ca_bundle_path diff --git a/docs/api/index.rst b/docs/api/index.rst index 4ff5f33fd..91877892d 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -11,6 +11,7 @@ API Documentation proj list datadir + network sync global_context enums diff --git a/docs/api/network.rst b/docs/api/network.rst new file mode 100644 index 000000000..b88eafbe1 --- /dev/null +++ b/docs/api/network.rst @@ -0,0 +1,10 @@ +.. _network: + +PROJ Network Settings +====================== + + +pyproj.network.set_ca_bundle_path +---------------------------------- + +.. autofunction:: pyproj.network.set_ca_bundle_path diff --git a/docs/history.rst b/docs/history.rst index 0824a1b20..497ac2588 100644 --- a/docs/history.rst +++ b/docs/history.rst @@ -22,7 +22,7 @@ Change Log * ENH: Added ability to use global context (issue #661) * ENH: Add support for temporal CRS CF coordinate system (issue #672) * BUG: Fix handling of polygon holes when calculating area in Geod (pull #686) -* ENH: Added :func:`pyproj.set_ca_bundle_path` (pull #691) +* ENH: Added :func:`pyproj.network.set_ca_bundle_path` (pull #691) 2.6.1 ~~~~~ diff --git a/docs/transformation_grids.rst b/docs/transformation_grids.rst index 832bcd0e5..95e9da928 100644 --- a/docs/transformation_grids.rst +++ b/docs/transformation_grids.rst @@ -9,7 +9,7 @@ More information about the data available is located under the PROJ `resource files `__ documentation. -`pyproj` API for managing the :ref:`data_and_ca_bundle_directory` +`pyproj` API for managing the :ref:`data_directory` and :ref:`network`. .. warning:: pyproj 2 includes datumgrid 1.8 in the wheels. pyproj 3 will not include any datum grids. diff --git a/pyproj/__init__.py b/pyproj/__init__.py index a4433ed6a..c3cfbbfec 100644 --- a/pyproj/__init__.py +++ b/pyproj/__init__.py @@ -46,10 +46,10 @@ ] import warnings +import pyproj.network from pyproj._datadir import ( # noqa: F401 _pyproj_global_context_initialize, is_global_context_network_enabled, - set_ca_bundle_path, set_global_context_network, set_use_global_context, ) @@ -81,4 +81,4 @@ except DataDirError as err: warnings.warn(str(err)) -set_ca_bundle_path() +pyproj.network.set_ca_bundle_path() diff --git a/pyproj/_datadir.pyi b/pyproj/_datadir.pyi index 4b25e90ba..3ad8ae4c8 100644 --- a/pyproj/_datadir.pyi +++ b/pyproj/_datadir.pyi @@ -1,9 +1,7 @@ -from pathlib import Path -from typing import Optional, Union +from typing import Optional def _pyproj_global_context_initialize() -> None: ... def get_user_data_dir(create: bool = False) -> str: ... -def set_ca_bundle_path(ca_bundle_path: Union[Path, str, bool, None] = None) -> None: ... def _global_context_set_data_dir() -> None: ... def set_use_global_context(active: Optional[bool] = None) -> None: ... def set_global_context_network(active: Optional[bool] = None) -> None: ... diff --git a/pyproj/_datadir.pyx b/pyproj/_datadir.pyx index a7ebd5e17..ca3c90037 100644 --- a/pyproj/_datadir.pyx +++ b/pyproj/_datadir.pyx @@ -1,9 +1,7 @@ import os import warnings from distutils.util import strtobool -from pathlib import Path -import certifi from libc.stdlib cimport free, malloc from pyproj.compat import cstrencode, pystrdecode @@ -113,47 +111,6 @@ def get_user_data_dir(create=False): return pystrdecode(proj_context_get_user_writable_directory(NULL, bool(create))) -def set_ca_bundle_path(ca_bundle_path=None): - """ - .. versionadded:: 3.0.0 - - Sets the path to the CA Bundle used by the `curl` - built into PROJ. - - Environment variables that can be used with PROJ 7.2+: - - PROJ_CURL_CA_BUNDLE - - CURL_CA_BUNDLE - - SSL_CERT_FILE - - Parameters - ---------- - ca_bundle_path: Union[Path, str, bool, None], optional - Default is None, which only uses the `certifi` package path as a fallback if - the environment variables are not set. If a path is passed in, then - that will be the path used. If it is set to True, then it will default - to using the path provied by the `certifi` package. If it is set to False, - then it will not set the path. - """ - if ca_bundle_path is False: - return None - - env_var_names = ( - "PROJ_CURL_CA_BUNDLE", - "CURL_CA_BUNDLE", - "SSL_CERT_FILE", - ) - if isinstance(ca_bundle_path, (str, Path)): - ca_bundle_path = str(ca_bundle_path) - elif ( - (ca_bundle_path is True) or - not any(env_var_name in os.environ for env_var_name in env_var_names) - ): - ca_bundle_path = certifi.where() - - if isinstance(ca_bundle_path, str): - proj_context_set_ca_bundle_path(NULL, cstrencode(ca_bundle_path)) - - cdef void pyproj_log_function(void *user_data, int level, const char *error_msg): """ Log function for catching PROJ errors. diff --git a/pyproj/_network.pyi b/pyproj/_network.pyi new file mode 100644 index 000000000..3caa17660 --- /dev/null +++ b/pyproj/_network.pyi @@ -0,0 +1 @@ +def _set_ca_bundle_path(ca_bundle_path: str) -> None: ... diff --git a/pyproj/_network.pyx b/pyproj/_network.pyx new file mode 100644 index 000000000..c1d1be74a --- /dev/null +++ b/pyproj/_network.pyx @@ -0,0 +1,16 @@ +include "proj.pxi" + +from pyproj.compat import cstrencode + + +def _set_ca_bundle_path(ca_bundle_path): + """ + Sets the path to the CA Bundle used by the `curl` + built into PROJ. + + Parameters + ---------- + ca_bundle_path: str + The path to the CA Bundle. + """ + proj_context_set_ca_bundle_path(NULL, cstrencode(ca_bundle_path)) diff --git a/pyproj/network.py b/pyproj/network.py new file mode 100644 index 000000000..363ac2036 --- /dev/null +++ b/pyproj/network.py @@ -0,0 +1,51 @@ +""" +Module for managing the PROJ network settings. +""" +import os +from pathlib import Path +from typing import Union + +import certifi + +from pyproj._network import _set_ca_bundle_path + + +def set_ca_bundle_path(ca_bundle_path: Union[Path, str, bool, None] = None) -> None: + """ + .. versionadded:: 3.0.0 + + Sets the path to the CA Bundle used by the `curl` + built into PROJ. + + Environment variables that can be used with PROJ 7.2+: + + - PROJ_CURL_CA_BUNDLE + - CURL_CA_BUNDLE + - SSL_CERT_FILE + + Parameters + ---------- + ca_bundle_path: Union[Path, str, bool, None], optional + Default is None, which only uses the `certifi` package path as a fallback if + the environment variables are not set. If a path is passed in, then + that will be the path used. If it is set to True, then it will default + to using the path provied by the `certifi` package. If it is set to False, + then it will not set the path. + """ + if ca_bundle_path is False: + return + + env_var_names = ( + "PROJ_CURL_CA_BUNDLE", + "CURL_CA_BUNDLE", + "SSL_CERT_FILE", + ) + if isinstance(ca_bundle_path, (str, Path)): + ca_bundle_path = str(ca_bundle_path) + elif (ca_bundle_path is True) or not any( + env_var_name in os.environ for env_var_name in env_var_names + ): + ca_bundle_path = certifi.where() + + if isinstance(ca_bundle_path, str): + _set_ca_bundle_path(ca_bundle_path) diff --git a/setup.py b/setup.py index e93d0c6bb..b3845a5ac 100644 --- a/setup.py +++ b/setup.py @@ -166,6 +166,7 @@ def get_extension_modules(): ), Extension("pyproj._datadir", ["pyproj/_datadir.pyx"], **ext_options), Extension("pyproj._list", ["pyproj/_list.pyx"], **ext_options), + Extension("pyproj._network", ["pyproj/_network.pyx"], **ext_options), Extension("pyproj._sync", ["pyproj/_sync.pyx"], **ext_options), ], quiet=True, diff --git a/test/test_network.py b/test/test_network.py new file mode 100644 index 000000000..b83a5b22e --- /dev/null +++ b/test/test_network.py @@ -0,0 +1,49 @@ +import certifi +import pytest +from mock import patch + +from pyproj.network import set_ca_bundle_path + + +@patch.dict("os.environ", {}, clear=True) +@patch("pyproj.network._set_ca_bundle_path") +def test_ca_bundle_path__default(c_set_ca_bundle_path_mock): + set_ca_bundle_path() + c_set_ca_bundle_path_mock.assert_called_with(certifi.where()) + + +@pytest.mark.parametrize( + "env_var", ["PROJ_CURL_CA_BUNDLE", "CURL_CA_BUNDLE", "SSL_CERT_FILE"] +) +@patch("pyproj.network._set_ca_bundle_path") +def test_ca_bundle_path__always_certifi(c_set_ca_bundle_path_mock, env_var): + with patch.dict("os.environ", {env_var: "/tmp/dummy/path/cacert.pem"}, clear=True): + set_ca_bundle_path(True) + c_set_ca_bundle_path_mock.assert_called_with(certifi.where()) + + +@patch.dict("os.environ", {}, clear=True) +@patch("pyproj.network._set_ca_bundle_path") +def test_ca_bundle_path__skip(c_set_ca_bundle_path_mock): + set_ca_bundle_path(False) + c_set_ca_bundle_path_mock.assert_not_called() + + +@pytest.mark.parametrize( + "env_var", ["PROJ_CURL_CA_BUNDLE", "CURL_CA_BUNDLE", "SSL_CERT_FILE"] +) +@patch("pyproj.network._set_ca_bundle_path") +def test_ca_bundle_path__env_var_skip(c_set_ca_bundle_path_mock, env_var): + with patch.dict("os.environ", {env_var: "/tmp/dummy/path/cacert.pem"}, clear=True): + set_ca_bundle_path() + c_set_ca_bundle_path_mock.assert_not_called() + + +@pytest.mark.parametrize( + "env_var", ["PROJ_CURL_CA_BUNDLE", "CURL_CA_BUNDLE", "SSL_CERT_FILE"] +) +@patch("pyproj.network._set_ca_bundle_path") +def test_ca_bundle_path__custom_path(c_set_ca_bundle_path_mock, env_var): + with patch.dict("os.environ", {env_var: "/tmp/dummy/path/cacert.pem"}, clear=True): + set_ca_bundle_path("/my/path/to/cacert.pem") + c_set_ca_bundle_path_mock.assert_called_with("/my/path/to/cacert.pem")