mirror of
https://git.notmuchmail.org/git/notmuch
synced 2024-11-21 18:38:08 +01:00
python-cffi: switch to notmuch_database_{open,create}_with_config
Since release 0.32, libnotmuch provides searching for database and configuration paths. This commit changes the python module notmuch2 to use those facilities. This fixes the bug reported in [1], along with a couple of the deprecation warnings in the python bindings. Database.default_path is deprecated, since it no longer faithfully reflects what libnotmuch is doing, and it is also no longer used in the bindings themselves. This commit choose the default of config=CONFIG.EMPTY (equivalent to passing "" to notmuch_database_open_with_config). This makes the change upward compatible API-wise (at least as far as the test suite verifies), but changing the default to CONFIG.SEARCH would probably be more convenient for bindings users. [1]: id:87h7d4wp6b.fsf@tethera.net
This commit is contained in:
parent
caafab01a4
commit
d7f9572413
5 changed files with 78 additions and 39 deletions
|
@ -103,20 +103,18 @@ ffibuilder.cdef(
|
||||||
notmuch_status_to_string (notmuch_status_t status);
|
notmuch_status_to_string (notmuch_status_t status);
|
||||||
|
|
||||||
notmuch_status_t
|
notmuch_status_t
|
||||||
notmuch_database_create_verbose (const char *path,
|
notmuch_database_create_with_config (const char *database_path,
|
||||||
notmuch_database_t **database,
|
const char *config_path,
|
||||||
char **error_message);
|
const char *profile,
|
||||||
|
notmuch_database_t **database,
|
||||||
|
char **error_message);
|
||||||
notmuch_status_t
|
notmuch_status_t
|
||||||
notmuch_database_create (const char *path, notmuch_database_t **database);
|
notmuch_database_open_with_config (const char *database_path,
|
||||||
notmuch_status_t
|
notmuch_database_mode_t mode,
|
||||||
notmuch_database_open_verbose (const char *path,
|
const char *config_path,
|
||||||
notmuch_database_mode_t mode,
|
const char *profile,
|
||||||
notmuch_database_t **database,
|
notmuch_database_t **database,
|
||||||
char **error_message);
|
char **error_message);
|
||||||
notmuch_status_t
|
|
||||||
notmuch_database_open (const char *path,
|
|
||||||
notmuch_database_mode_t mode,
|
|
||||||
notmuch_database_t **database);
|
|
||||||
notmuch_status_t
|
notmuch_status_t
|
||||||
notmuch_database_close (notmuch_database_t *database);
|
notmuch_database_close (notmuch_database_t *database);
|
||||||
notmuch_status_t
|
notmuch_status_t
|
||||||
|
|
|
@ -31,6 +31,9 @@ class Mode(enum.Enum):
|
||||||
READ_ONLY = capi.lib.NOTMUCH_DATABASE_MODE_READ_ONLY
|
READ_ONLY = capi.lib.NOTMUCH_DATABASE_MODE_READ_ONLY
|
||||||
READ_WRITE = capi.lib.NOTMUCH_DATABASE_MODE_READ_WRITE
|
READ_WRITE = capi.lib.NOTMUCH_DATABASE_MODE_READ_WRITE
|
||||||
|
|
||||||
|
class ConfigFile(enum.Enum):
|
||||||
|
EMPTY = b''
|
||||||
|
SEARCH = capi.ffi.NULL
|
||||||
|
|
||||||
class QuerySortOrder(enum.Enum):
|
class QuerySortOrder(enum.Enum):
|
||||||
OLDEST_FIRST = capi.lib.NOTMUCH_SORT_OLDEST_FIRST
|
OLDEST_FIRST = capi.lib.NOTMUCH_SORT_OLDEST_FIRST
|
||||||
|
@ -71,6 +74,9 @@ class Database(base.NotmuchObject):
|
||||||
:cvar EXCLUDE: Which messages to exclude from queries, ``TRUE``,
|
:cvar EXCLUDE: Which messages to exclude from queries, ``TRUE``,
|
||||||
``FLAG``, ``FALSE`` or ``ALL``. See the query documentation
|
``FLAG``, ``FALSE`` or ``ALL``. See the query documentation
|
||||||
for details.
|
for details.
|
||||||
|
:cvar CONFIG: Control loading of config file. Enumeration of
|
||||||
|
``EMPTY`` (don't load a config file), and ``SEARCH`` (search as
|
||||||
|
in :ref:`config_search`)
|
||||||
:cvar AddedMessage: A namedtuple ``(msg, dup)`` used by
|
:cvar AddedMessage: A namedtuple ``(msg, dup)`` used by
|
||||||
:meth:`add` as return value.
|
:meth:`add` as return value.
|
||||||
:cvar STR_MODE_MAP: A map mapping strings to :attr:`MODE` items.
|
:cvar STR_MODE_MAP: A map mapping strings to :attr:`MODE` items.
|
||||||
|
@ -81,9 +87,8 @@ class Database(base.NotmuchObject):
|
||||||
still open.
|
still open.
|
||||||
|
|
||||||
:param path: The directory of where the database is stored. If
|
:param path: The directory of where the database is stored. If
|
||||||
``None`` the location will be read from the user's
|
``None`` the location will be searched according to
|
||||||
configuration file, respecting the ``NOTMUCH_CONFIG``
|
:ref:`database`
|
||||||
environment variable if set.
|
|
||||||
:type path: str, bytes, os.PathLike or pathlib.Path
|
:type path: str, bytes, os.PathLike or pathlib.Path
|
||||||
:param mode: The mode to open the database in. One of
|
:param mode: The mode to open the database in. One of
|
||||||
:attr:`MODE.READ_ONLY` OR :attr:`MODE.READ_WRITE`. For
|
:attr:`MODE.READ_ONLY` OR :attr:`MODE.READ_WRITE`. For
|
||||||
|
@ -91,6 +96,8 @@ class Database(base.NotmuchObject):
|
||||||
:attr:`MODE.READ_ONLY` and ``rw`` for :attr:`MODE.READ_WRITE`.
|
:attr:`MODE.READ_ONLY` and ``rw`` for :attr:`MODE.READ_WRITE`.
|
||||||
:type mode: :attr:`MODE` or str.
|
:type mode: :attr:`MODE` or str.
|
||||||
|
|
||||||
|
:param config: Where to load the configuration from, if any.
|
||||||
|
:type config: :attr:`CONFIG.EMPTY`, :attr:`CONFIG.SEARCH`, str, bytes, os.PathLike, pathlib.Path
|
||||||
:raises KeyError: if an unknown mode string is used.
|
:raises KeyError: if an unknown mode string is used.
|
||||||
:raises OSError: or subclasses if the configuration file can not
|
:raises OSError: or subclasses if the configuration file can not
|
||||||
be opened.
|
be opened.
|
||||||
|
@ -102,6 +109,7 @@ class Database(base.NotmuchObject):
|
||||||
MODE = Mode
|
MODE = Mode
|
||||||
SORT = QuerySortOrder
|
SORT = QuerySortOrder
|
||||||
EXCLUDE = QueryExclude
|
EXCLUDE = QueryExclude
|
||||||
|
CONFIG = ConfigFile
|
||||||
AddedMessage = collections.namedtuple('AddedMessage', ['msg', 'dup'])
|
AddedMessage = collections.namedtuple('AddedMessage', ['msg', 'dup'])
|
||||||
_db_p = base.MemoryPointer()
|
_db_p = base.MemoryPointer()
|
||||||
STR_MODE_MAP = {
|
STR_MODE_MAP = {
|
||||||
|
@ -109,18 +117,40 @@ class Database(base.NotmuchObject):
|
||||||
'rw': MODE.READ_WRITE,
|
'rw': MODE.READ_WRITE,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, path=None, mode=MODE.READ_ONLY):
|
@staticmethod
|
||||||
|
def _cfg_path_encode(path):
|
||||||
|
if isinstance(path,ConfigFile):
|
||||||
|
path = path.value
|
||||||
|
elif path is None:
|
||||||
|
path = capi.ffi.NULL
|
||||||
|
elif not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path):
|
||||||
|
path = bytes(path)
|
||||||
|
else:
|
||||||
|
path = os.fsencode(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _db_path_encode(path):
|
||||||
|
if path is None:
|
||||||
|
path = capi.ffi.NULL
|
||||||
|
elif not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path):
|
||||||
|
path = bytes(path)
|
||||||
|
else:
|
||||||
|
path = os.fsencode(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def __init__(self, path=None, mode=MODE.READ_ONLY, config=CONFIG.EMPTY):
|
||||||
if isinstance(mode, str):
|
if isinstance(mode, str):
|
||||||
mode = self.STR_MODE_MAP[mode]
|
mode = self.STR_MODE_MAP[mode]
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
if path is None:
|
|
||||||
path = self.default_path()
|
|
||||||
if not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path):
|
|
||||||
path = bytes(path)
|
|
||||||
db_pp = capi.ffi.new('notmuch_database_t **')
|
db_pp = capi.ffi.new('notmuch_database_t **')
|
||||||
cmsg = capi.ffi.new('char**')
|
cmsg = capi.ffi.new('char**')
|
||||||
ret = capi.lib.notmuch_database_open_verbose(os.fsencode(path),
|
ret = capi.lib.notmuch_database_open_with_config(self._db_path_encode(path),
|
||||||
mode.value, db_pp, cmsg)
|
mode.value,
|
||||||
|
self._cfg_path_encode(config),
|
||||||
|
capi.ffi.NULL,
|
||||||
|
db_pp, cmsg)
|
||||||
if cmsg[0]:
|
if cmsg[0]:
|
||||||
msg = capi.ffi.string(cmsg[0]).decode(errors='replace')
|
msg = capi.ffi.string(cmsg[0]).decode(errors='replace')
|
||||||
capi.lib.free(cmsg[0])
|
capi.lib.free(cmsg[0])
|
||||||
|
@ -132,18 +162,20 @@ class Database(base.NotmuchObject):
|
||||||
self.closed = False
|
self.closed = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, path=None):
|
def create(cls, path=None, config=ConfigFile.EMPTY):
|
||||||
"""Create and open database in READ_WRITE mode.
|
"""Create and open database in READ_WRITE mode.
|
||||||
|
|
||||||
This is creates a new notmuch database and returns an opened
|
This is creates a new notmuch database and returns an opened
|
||||||
instance in :attr:`MODE.READ_WRITE` mode.
|
instance in :attr:`MODE.READ_WRITE` mode.
|
||||||
|
|
||||||
:param path: The directory of where the database is stored. If
|
:param path: The directory of where the database is stored.
|
||||||
``None`` the location will be read from the user's
|
If ``None`` the location will be read searched by the
|
||||||
configuration file, respecting the ``NOTMUCH_CONFIG``
|
notmuch library (see notmuch(3)::notmuch_open_with_config).
|
||||||
environment variable if set.
|
|
||||||
:type path: str, bytes or os.PathLike
|
:type path: str, bytes or os.PathLike
|
||||||
|
|
||||||
|
:param config: The pathname of the notmuch configuration file.
|
||||||
|
:type config: :attr:`CONFIG.EMPTY`, :attr:`CONFIG.SEARCH`, str, bytes, os.PathLike, pathlib.Path
|
||||||
|
|
||||||
:raises OSError: or subclasses if the configuration file can not
|
:raises OSError: or subclasses if the configuration file can not
|
||||||
be opened.
|
be opened.
|
||||||
:raises configparser.Error: or subclasses if the configuration
|
:raises configparser.Error: or subclasses if the configuration
|
||||||
|
@ -154,14 +186,13 @@ class Database(base.NotmuchObject):
|
||||||
|
|
||||||
:returns: The newly created instance.
|
:returns: The newly created instance.
|
||||||
"""
|
"""
|
||||||
if path is None:
|
|
||||||
path = cls.default_path()
|
|
||||||
if not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path):
|
|
||||||
path = bytes(path)
|
|
||||||
db_pp = capi.ffi.new('notmuch_database_t **')
|
db_pp = capi.ffi.new('notmuch_database_t **')
|
||||||
cmsg = capi.ffi.new('char**')
|
cmsg = capi.ffi.new('char**')
|
||||||
ret = capi.lib.notmuch_database_create_verbose(os.fsencode(path),
|
ret = capi.lib.notmuch_database_create_with_config(cls._db_path_encode(path),
|
||||||
db_pp, cmsg)
|
cls._cfg_path_encode(config),
|
||||||
|
capi.ffi.NULL,
|
||||||
|
db_pp, cmsg)
|
||||||
if cmsg[0]:
|
if cmsg[0]:
|
||||||
msg = capi.ffi.string(cmsg[0]).decode(errors='replace')
|
msg = capi.ffi.string(cmsg[0]).decode(errors='replace')
|
||||||
capi.lib.free(cmsg[0])
|
capi.lib.free(cmsg[0])
|
||||||
|
@ -176,7 +207,7 @@ class Database(base.NotmuchObject):
|
||||||
ret = capi.lib.notmuch_database_destroy(db_pp[0])
|
ret = capi.lib.notmuch_database_destroy(db_pp[0])
|
||||||
if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
|
if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
|
||||||
raise errors.NotmuchError(ret)
|
raise errors.NotmuchError(ret)
|
||||||
return cls(path, cls.MODE.READ_WRITE)
|
return cls(path, cls.MODE.READ_WRITE, config=config)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def default_path(cfg_path=None):
|
def default_path(cfg_path=None):
|
||||||
|
@ -200,6 +231,9 @@ class Database(base.NotmuchObject):
|
||||||
file can not be parsed.
|
file can not be parsed.
|
||||||
:raises NotmuchError: if the config file does not have the
|
:raises NotmuchError: if the config file does not have the
|
||||||
database.path setting.
|
database.path setting.
|
||||||
|
|
||||||
|
.. deprecated:: 0.35
|
||||||
|
Use the ``config`` parameter to :meth:`__init__` or :meth:`__create__` instead.
|
||||||
"""
|
"""
|
||||||
if not cfg_path:
|
if not cfg_path:
|
||||||
cfg_path = _config_pathname()
|
cfg_path = _config_pathname()
|
||||||
|
|
|
@ -259,6 +259,8 @@ paths are presumed relative to `$HOME` for items in section
|
||||||
FILES
|
FILES
|
||||||
=====
|
=====
|
||||||
|
|
||||||
|
.. _config_search:
|
||||||
|
|
||||||
CONFIGURATION
|
CONFIGURATION
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
|
|
@ -309,11 +309,10 @@ EOF
|
||||||
;&
|
;&
|
||||||
split)
|
split)
|
||||||
test_begin_subtest "'to' header does not crash (python-cffi) ($config)"
|
test_begin_subtest "'to' header does not crash (python-cffi) ($config)"
|
||||||
test_subtest_known_broken
|
|
||||||
echo 'notmuch@notmuchmail.org' > EXPECTED
|
echo 'notmuch@notmuchmail.org' > EXPECTED
|
||||||
test_python <<EOF
|
test_python <<EOF
|
||||||
import notmuch2
|
from notmuch2 import Database
|
||||||
db=notmuch2.Database()
|
db=Database(config=Database.CONFIG.SEARCH)
|
||||||
m=db.find('20091117232137.GA7669@griffis1.net')
|
m=db.find('20091117232137.GA7669@griffis1.net')
|
||||||
to=m.header('To')
|
to=m.header('To')
|
||||||
print(to)
|
print(to)
|
||||||
|
|
|
@ -7,8 +7,14 @@ if [ $NOTMUCH_HAVE_PYTHON3_CFFI -eq 0 -o $NOTMUCH_HAVE_PYTHON3_PYTEST -eq 0 ]; t
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
test_begin_subtest "python cffi tests"
|
test_begin_subtest "python cffi tests (NOTMUCH_CONFIG set)"
|
||||||
pytest_dir=$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage
|
pytest_dir=$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage
|
||||||
printf "[pytest]\nminversion = 3.0\naddopts = -ra\n" > $pytest_dir/pytest.ini
|
printf "[pytest]\nminversion = 3.0\naddopts = -ra\n" > $pytest_dir/pytest.ini
|
||||||
test_expect_success "(cd $pytest_dir && ${NOTMUCH_PYTHON} -m pytest --verbose --log-file=$TMP_DIRECTORY/test.output)"
|
test_expect_success "(cd $pytest_dir && ${NOTMUCH_PYTHON} -m pytest --verbose --log-file=$TMP_DIRECTORY/test.output)"
|
||||||
|
|
||||||
|
test_begin_subtest "python cffi tests (NOTMUCH_CONFIG unset)"
|
||||||
|
pytest_dir=$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage
|
||||||
|
printf "[pytest]\nminversion = 3.0\naddopts = -ra\n" > $pytest_dir/pytest.ini
|
||||||
|
unset NOTMUCH_CONFIG
|
||||||
|
test_expect_success "(cd $pytest_dir && ${NOTMUCH_PYTHON} -m pytest --verbose --log-file=$TMP_DIRECTORY/test.output)"
|
||||||
test_done
|
test_done
|
||||||
|
|
Loading…
Reference in a new issue