Author SHA1 Message Date
Oliver Hamlet 01a8567ed8 Move most reference docs into source as docstrings
It's easier to maintain, though there seems to be a bug in pybind11, as
it doesn't use the docstrings I've provided for some static read-only
properties, so Version and WrapperVersion are still documented
separately.

It's not related, but I've noticed that for some reason intersphinx
is unable to turn :cpp: domain references into hyperlinks, but no
related information is logged to say why.
2020-01-04 16:07:36 +00:00
Oliver Hamlet 7ccfe6d7f4 Expose a few more GameInterface and PluginInterface methods
The exposed methods are:

* GameInterface::LoadPlugins
* GameInterface::GetPlugin
* PluginInterface::GetName
* PluginInterface::IsMaster
* PluginInterface::IsLightMaster
* PluginInterface::IsValidAsLightMaster
2020-01-04 16:07:36 +00:00
Oliver Hamlet 2106ab4f01 Fix Python GIL causing a hang when running the logging callback
pybind11 holds the GIL whenever a C++ function is called from Python, so
the logging callback can't then execute Python code. To avoid this
problem, release the GIL for every function bound by pybind11 (in case
log statements are added to those that don't currently have any), and
wrap the logging callback in a lambda function that first aquires the
GIL lock for the function's scope.
2020-01-04 12:37:00 +00:00
6 changed files with 235 additions and 254 deletions
+22 -1
View File
@@ -84,6 +84,20 @@ link_directories(${LIBLOOT_EXTRACTED_PATH})
set(LIBLOOT_STATIC_LIBRARY "${CMAKE_STATIC_LIBRARY_PREFIX}loot${CMAKE_STATIC_LIBRARY_SUFFIX}")
set(LIBLOOT_SHARED_LIBRARY "${CMAKE_SHARED_LIBRARY_PREFIX}loot${CMAKE_SHARED_LIBRARY_SUFFIX}")
#######################################
# testing-plugins
#######################################
# This is a bit messy because the testing-plugins aren't actually required to
# build libloot-python, but having them as a dependency is the easiest way to
# manage downloading and moving them to the expected location.
ExternalProject_Add(testing-plugins
PREFIX "external"
URL "https://github.com/Ortham/testing-plugins/archive/1.4.1.tar.gz"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND "")
#######################################
# Python Module
#######################################
@@ -96,7 +110,7 @@ pybind11_add_module(libloot-python "${CMAKE_SOURCE_DIR}/src/main.cpp"
# conventions (no lib, no -python).
set_target_properties(libloot-python PROPERTIES OUTPUT_NAME loot)
add_dependencies(libloot-python libloot)
add_dependencies(libloot-python libloot testing-plugins)
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
target_link_libraries(libloot-python PRIVATE ${LIBLOOT_STATIC_LIBRARY})
@@ -116,6 +130,13 @@ add_custom_command(TARGET libloot-python POST_BUILD
"${LIBLOOT_EXTRACTED_PATH}/${LIBLOOT_SHARED_LIBRARY}"
"$<TARGET_FILE_DIR:libloot-python>/${LIBLOOT_SHARED_LIBRARY}")
# Copy testing plugins
ExternalProject_Get_Property(testing-plugins SOURCE_DIR)
add_custom_command(TARGET libloot-python POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${SOURCE_DIR}
$<TARGET_FILE_DIR:libloot-python>)
enable_testing()
add_test(NAME python
+3
View File
@@ -20,6 +20,9 @@ environment:
install:
- ps: (New-Object Net.WebClient).DownloadFile('https://raw.githubusercontent.com/Ortham/ci-scripts/2.0.0/delete_old_bintray_versions.py', "$env:APPVEYOR_BUILD_FOLDER\delete_old_bintray_versions.py")
- ps: (New-Object Net.WebClient).DownloadFile('https://github.com/WrinklyNinja/testing-plugins/archive/1.4.1.zip', "$PWD/1.4.1.zip")
- 7z x 1.4.1.zip
- mv testing-plugins-1.4.1 testing-plugins
before_build:
- ps: $env:PYTHON_DIRECTORY_VERSION=$env:PYTHON_VERSION.Replace(".", "")
+4 -2
View File
@@ -22,7 +22,7 @@
import os
import sys
sys.path.insert(0, os.path.abspath('../build/Release'))
sys.path.insert(0, os.path.abspath('../build/RelWithDebInfo'))
# -- General configuration ------------------------------------------------
@@ -34,6 +34,7 @@ sys.path.insert(0, os.path.abspath('../build/Release'))
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
]
@@ -346,5 +347,6 @@ texinfo_documents = [
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
'loot_api': ('http://loot.readthedocs.io/en/0.14.7/', None),
'loot': ('https://loot.readthedocs.io/en/0.15.0/', None),
'python': ('https://docs.python.org/3', None)
}
+7 -191
View File
@@ -5,188 +5,13 @@ API Reference
As this API is just a wrapper for libloot's C++ API, its documentation is linked
to for all non-Python-specific information.
Enumerations
============
The wrapped enumeration types below are classes in Python, but the distinction
The wrapped enumeration types are classes in Python, but the distinction
makes no difference in practice, so they're grouped here for semantics. All
values are unsigned integer constants.
their values are unsigned integer constants.
.. py:class:: loot.GameType
Wraps :cpp:type:`loot::GameType` to expose libloot's game codes.
.. py:attribute:: fo3
.. py:attribute:: fo4
.. py:attribute:: fonv
.. py:attribute:: tes4
.. py:attribute:: tes5
.. py:attribute:: tes5se
.. py:class:: loot.LogLevel
Wraps :cpp:type:`loot::LogLevel` to expose libloot's log level codes.
.. py:attribute:: trace
.. py:attribute:: debug
.. py:attribute:: info
.. py:attribute:: warning
.. py:attribute:: error
.. py:attribute:: fatal
.. py:class:: loot.MessageType
Wraps :cpp:type:`loot::MessageType` to expose libloot's message type
codes.
.. py:attribute:: error
.. py:attribute:: say
.. py:attribute:: warn
.. py:class:: loot.PluginCleanliness
Codes used to indicate the cleanliness of a plugin according to the
information contained within the loaded masterlist/userlist.
.. py:attribute:: clean
Indicates that the plugin is clean.
.. py:attribute:: dirty
Indicates that the plugin is dirty.
.. py:attribute:: do_not_clean
Indicates that the plugin contains dirty edits, but that they are part of
the plugin’s intended functionality and should not be removed.
.. py:attribute:: unknown
Indicates that no data is available on whether the plugin is dirty or not.
Public-Field Data Structures
============================
Classes with public fields and no member functions.
.. py:class:: loot.MasterlistInfo
Wraps :cpp:class:`loot::MasterlistInfo`.
.. py:attribute:: revision_id
A Unicode string containing a Git commit's SHA-1 checksum.
.. py:attribute:: revision_date
A Unicode string containing the date of the commit given by :py:attr:`revision_id`, in ISO 8601 format (YYYY-MM-DD).
.. py:attribute:: is_modified
A boolean that is true if the masterlist has been modified from its state
at the commit given by :py:attr:`revision_id`.
.. py:class:: loot.SimpleMessage
Wraps :cpp:class:`loot::SimpleMessage`.
.. py:attribute:: type
A :py:class:`loot.MessageType` giving the message type.
.. py:attribute:: language
A Unicode string giving the message text language.
.. py:attribute:: text
A Unicode string containing the message text.
.. py:attribute:: condition
A Unicode string containing the message condition.
.. py:class:: loot.PluginTags
Wraps :cpp:class:`loot::PluginTags`.
.. py:attribute:: added
A set of Unicode strings giving Bash Tags suggested for addition.
.. py:attribute:: removed
A set of Unicode strings giving Bash Tags suggested for removal.
.. py:attribute:: userlist_modified
A boolean that is true if the suggestions contain metadata obtained from a loaded userlist.
Functions
=========
.. py:function:: loot.set_logging_callback(callback) -> NoneType
Set the callback function that is called when logging. Wraps
:cpp:func:`loot::SetLoggingCallback`.
.. py:function:: loot.is_compatible(int, int, int) -> bool
Checks for API compatibility. Wraps :cpp:func:`loot::IsCompatible`.
.. py:function:: loot.create_game_handle(game : loot.GameType, game_path : unicode, [game_local_path : unicode = u'']) -> loot.GameInterface
Initialise a new game handle. Wraps :cpp:func:`loot::CreateGameHandle`.
Classes
=======
.. py:class:: loot.GameInterface
Wraps :cpp:class:`loot::GameInterface`.
.. py:function:: loot.get_database() -> loot.DatabaseInterface
Get a database handle. Wraps :cpp:func:`loot::GetDatabase`.
.. py:function:: loot.load_current_load_order_state() -> NoneType
Load the current load order state, discarding any previously held state.
Wraps :cpp:func:`loot::LoadCurrentLoadOrderState`.
.. py:class:: loot.DatabaseInterface
Wraps :cpp:class:`loot::DatabaseInterface`.
.. py:method:: get_masterlist_revision(loot.DatabaseInterface, unicode, bool) -> loot.MasterlistInfo
Gets the give masterlist’s source control revision. Wraps :cpp:func:`GetMasterlistRevision`.
.. py:method:: get_plugin_metadata(loot.DatabaseInterface, plugin : unicode, [includeUserMetadata : bool = True, [evaluateConditions : bool = False]]) -> loot.PluginMetadata
Get all a plugin’s loaded metadata. Wraps :cpp:func:`GetPluginMetadata`.
.. py:method:: get_plugin_cleanliness(loot.DatabaseInterface, plugin : unicode, [evaluateConditions : bool = False]) -> loot.PluginCleanliness
Determines the database’s knowledge of a plugin’s cleanliness. Outputs whether the plugin should be cleaned or not, or if no data is available.
.. py:method:: get_plugin_tags(loot.DatabaseInterface, plugin : unicode, [evaluateConditions : bool = False]) -> loot.PluginTags
Outputs the Bash Tags suggested for addition and removal by the database for the given plugin.
.. py:method:: load_lists(loot.DatabaseInterface, masterlist_path : unicode, [userlist_path : unicode = u'']) -> NoneType
Loads the masterlist and userlist from the paths specified. Wraps :cpp:func:`LoadLists`.
.. py:method:: update_masterlist(loot.DatabaseInterface, unicode, unicode, unicode) -> bool
Updates the given masterlist using the given Git repository details. Wraps :cpp:func:`UpdateMasterlist`.
.. py:method:: write_minimal_list(loot.DatabaseInterface, unicode, bool) -> NoneType
Writes a minimal metadata file containing only Bash Tag suggestions and/or cleanliness info from the loaded metadata. Wraps :cpp:func:`WriteMinimalList`.
.. automodule:: loot
:members:
:exclude-members: Version, WrapperVersion
.. py:class:: loot.Version
@@ -208,7 +33,7 @@ Classes
A Unicode string containing the SHA-1 of the Git revision that the wrapped C++ API was built from.
.. py:staticmethod:: string() -> unicode
.. py:staticmethod:: string() -> str
Returns the API version as a string of the form ``major.minor.patch``
@@ -232,15 +57,6 @@ Classes
A Unicode string containing the SHA-1 of the Git revision that the wrapped C++ API was built from.
.. py:staticmethod:: string() -> unicode
.. py:staticmethod:: string() -> str
Returns the API version as a string of the form ``major.minor.patch``
.. py:class:: loot.PluginMetadata
Wraps :cpp:class:`loot::PluginMetadata`.
.. py:method:: get_simple_messages(loot.PluginMetadata, unicode) -> list<loot.SimpleMessage>
Get the plugin’s messages as SimpleMessage objects for the given language.
Wraps :cpp:func:`GetPluginMessages`.
+137 -52
View File
@@ -58,10 +58,21 @@ bool UpdateMasterlist(std::shared_ptr<DatabaseInterface> db, std::string masterl
MasterlistInfo GetMasterlistRevision(std::shared_ptr<DatabaseInterface> db, std::string masterlistPath, bool getShortId) {
return db->GetMasterlistRevision(u8path(masterlistPath), getShortId);
}
void SetLoggingCallback(std::function<void(LogLevel, const char*)> callback) {
callback = [callback](LogLevel level, const char* message) {
pybind11::gil_scoped_acquire acquire;
callback(level, message);
};
loot::SetLoggingCallback(callback);
}
}
void bindEnums(pybind11::module& module) {
enum_<GameType>(module, "GameType")
pybind11::options options;
options.disable_function_signatures();
enum_<GameType>(module, "GameType", "Wraps :cpp:enum:`loot::GameType` to expose libloot's game codes.")
.value("tes4", GameType::tes4)
.value("tes5", GameType::tes5)
.value("tes5se", GameType::tes5se)
@@ -71,7 +82,7 @@ void bindEnums(pybind11::module& module) {
.value("fo4", GameType::fo4)
.value("fo4vr", GameType::fo4vr);
enum_<LogLevel>(module, "LogLevel")
enum_<LogLevel>(module, "LogLevel", "Wraps :cpp:enum:`loot::LogLevel` to expose libloot's log level codes.")
.value("trace", LogLevel::trace)
.value("debug", LogLevel::debug)
.value("info", LogLevel::info)
@@ -79,75 +90,140 @@ void bindEnums(pybind11::module& module) {
.value("error", LogLevel::error)
.value("fatal", LogLevel::fatal);
enum_<MessageType>(module, "MessageType")
enum_<MessageType>(module, "MessageType", "Wraps :cpp:enum:`loot::MessageType` to expose libloot's message type codes.")
.value("say", MessageType::say)
.value("warn", MessageType::warn)
.value("error", MessageType::error);
enum_<PluginCleanliness>(module, "PluginCleanliness")
.value("clean", PluginCleanliness::clean)
.value("dirty", PluginCleanliness::dirty)
.value("do_not_clean", PluginCleanliness::do_not_clean)
.value("unknown", PluginCleanliness::unknown);
enum_<PluginCleanliness>(module, "PluginCleanliness", "Codes used to indicate the cleanliness of a plugin according to the information contained within the loaded masterlist / userlist.")
.value("clean", PluginCleanliness::clean, "Indicates that the plugin is clean.")
.value("dirty", PluginCleanliness::dirty, "Indicates that the plugin is dirty.")
.value("do_not_clean", PluginCleanliness::do_not_clean, "Indicates that the plugin contains dirty edits, but that they are part of the plugin's intended functionality and should not be removed.")
.value("unknown", PluginCleanliness::unknown, "Indicates that no data is available on whether the plugin is dirty or not.");
}
void bindMetadataClasses(pybind11::module& module) {
class_<MasterlistInfo>(module, "MasterlistInfo")
.def_readwrite("revision_id", &MasterlistInfo::revision_id)
.def_readwrite("revision_date", &MasterlistInfo::revision_date)
.def_readwrite("is_modified", &MasterlistInfo::is_modified);
class_<MasterlistInfo>(module, "MasterlistInfo", "Wraps :cpp:class:`loot::MasterlistInfo`.")
.def_readwrite("revision_id", &MasterlistInfo::revision_id, "A Unicode string containing a Git commit's SHA-1 checksum.")
.def_readwrite("revision_date", &MasterlistInfo::revision_date, "A Unicode string containing the date of the commit given by :py:attr:`~loot.MasterlistInfo.revision_id`, in ISO 8601 format (YYYY-MM-DD).")
.def_readwrite("is_modified", &MasterlistInfo::is_modified, "A boolean that is true if the masterlist has been modified from its state at the commit given by :py:attr:`~loot.MasterlistInfo.revision_id`.");
class_<SimpleMessage>(module, "SimpleMessage")
.def_readwrite("type", &SimpleMessage::type)
.def_readwrite("language", &SimpleMessage::language)
.def_readwrite("text", &SimpleMessage::text)
.def_readwrite("condition", &SimpleMessage::condition);
class_<SimpleMessage>(module, "SimpleMessage", "Wraps :cpp:class:`loot::SimpleMessage`.")
.def_readwrite("type", &SimpleMessage::type, "A :py:class:`loot.MessageType` giving the message type.")
.def_readwrite("language", &SimpleMessage::language, "A Unicode string giving the message text language.")
.def_readwrite("text", &SimpleMessage::text, "A Unicode string containing the message text.")
.def_readwrite("condition", &SimpleMessage::condition, "A Unicode string containing the message condition.");
class_<PluginTags>(module, "PluginTags")
.def_readwrite("added", &PluginTags::added)
.def_readwrite("removed", &PluginTags::removed)
.def_readwrite("userlist_modified", &PluginTags::userlist_modified);
.def_readwrite("added", &PluginTags::added, "A set of Unicode strings giving Bash Tags suggested for addition.")
.def_readwrite("removed", &PluginTags::removed, "A set of Unicode strings giving Bash Tags suggested for removal.")
.def_readwrite("userlist_modified", &PluginTags::userlist_modified, "A boolean that is true if the suggestions contain metadata obtained from a loaded userlist.");
class_<PluginMetadata>(module, "PluginMetadata")
.def("get_simple_messages", &PluginMetadata::GetSimpleMessages);
class_<PluginMetadata>(module, "PluginMetadata", "Wraps :cpp:class:`loot::PluginMetadata`.")
.def("get_simple_messages",
&PluginMetadata::GetSimpleMessages,
pybind11::call_guard<pybind11::gil_scoped_release>(),
"Get the plugin's messages as SimpleMessage objects for the given language. Wraps :cpp:func:`GetSimpleMessages`.");
}
void bindVersionClasses(pybind11::module& module) {
class_<LootVersion>(module, "Version")
.def_readonly_static("major", &LootVersion::major)
.def_readonly_static("minor", &LootVersion::minor)
.def_readonly_static("patch", &LootVersion::patch)
.def_readonly_static("revision", &LootVersion::revision)
.def_static("string", LootVersion::GetVersionString);
// FIXME: For some reason the static properties have their docstrings ignored.
class_<LootVersion>(module, "Version", "Wraps :cpp:class:`loot::LootVersion`.")
.def_readonly_static("major", &LootVersion::major, "An unsigned integer giving the major version number. Read-only.")
.def_readonly_static("minor", &LootVersion::minor, "An unsigned integer giving the minor version number. Read-only.")
.def_readonly_static("patch", &LootVersion::patch, "An unsigned integer giving the patch version number. Read-only.")
.def_readonly_static("revision", &LootVersion::revision, "A Unicode string containing the Git commit hash that the wrapped libloot was built from.")
.def_static("string",
LootVersion::GetVersionString,
pybind11::call_guard<pybind11::gil_scoped_release>(),
"Returns the libloot version as a string of the form ``major.minor.patch``.");
class_<WrapperVersion>(module, "WrapperVersion")
.def_readonly_static("major", &WrapperVersion::major)
.def_readonly_static("minor", &WrapperVersion::minor)
.def_readonly_static("patch", &WrapperVersion::patch)
.def_readonly_static("revision", &WrapperVersion::revision)
.def_static("string", WrapperVersion::string);
class_<WrapperVersion>(module, "WrapperVersion", "Provides information about the version of libloot-python that is being run.")
.def_readonly_static("major", &WrapperVersion::major, "An unsigned integer giving the major version number. Read-only.")
.def_readonly_static("minor", &WrapperVersion::minor, "An unsigned integer giving the minor version number. Read-only.")
.def_readonly_static("patch", &WrapperVersion::patch, "An unsigned integer giving the patch version number. Read-only.")
.def_readonly_static("revision", &WrapperVersion::revision, "A Unicode string containing the Git commit hash that the Python module was built from.")
.def_static("string",
WrapperVersion::string,
pybind11::call_guard<pybind11::gil_scoped_release>(),
"Returns the module version as a string of the form ``major.minor.patch``.");
}
void bindInterfaceClasses(pybind11::module& module) {
class_<GameInterface, std::shared_ptr<GameInterface>>(module, "GameInterface")
.def("load_current_load_order_state", &GameInterface::LoadCurrentLoadOrderState)
.def("get_database", &GameInterface::GetDatabase);
class_<GameInterface, std::shared_ptr<GameInterface>>(module, "GameInterface", "Wraps :cpp:class:`loot::GameInterface`.")
.def("load_current_load_order_state",
&GameInterface::LoadCurrentLoadOrderState,
pybind11::call_guard<pybind11::gil_scoped_release>(),
"Load the current load order state, discarding any previously held state. Wraps :cpp:func:`LoadCurrentLoadOrderState`.")
.def("get_database",
&GameInterface::GetDatabase,
pybind11::call_guard<pybind11::gil_scoped_release>(),
"Get a database handle. Wraps :cpp:func:`GetDatabase`.")
.def("load_plugins",
&GameInterface::LoadPlugins,
pybind11::call_guard<pybind11::gil_scoped_release>(),
"Load the given plugins. Wraps :cpp:func:`LoadPlugins`.")
.def("get_plugin",
&GameInterface::GetPlugin,
pybind11::call_guard<pybind11::gil_scoped_release>(),
"Get the given loaded plugin. Wraps :cpp:func:`GetPlugin`.");
class_<DatabaseInterface, std::shared_ptr<DatabaseInterface>>(module, "DatabaseInterface")
.def("load_lists", &py::LoadLists, arg("masterlist_path"), arg("userlist_path") = "")
.def("update_masterlist", &py::UpdateMasterlist)
.def("get_masterlist_revision", &py::GetMasterlistRevision)
.def("get_plugin_metadata", &DatabaseInterface::GetPluginMetadata,
class_<DatabaseInterface, std::shared_ptr<DatabaseInterface>>(module, "DatabaseInterface", "Wraps :cpp:class:`loot::DatabaseInterface`.")
.def("load_lists",
&py::LoadLists,
arg("masterlist_path"),
arg("userlist_path") = "",
pybind11::call_guard<pybind11::gil_scoped_release>(),
"Loads the masterlist and userlist from the paths specified. Wraps :cpp:func:`LoadLists`.")
.def("update_masterlist",
&py::UpdateMasterlist,
pybind11::call_guard<pybind11::gil_scoped_release>(),
"Updates the given masterlist using the given Git repository details. Wraps :cpp:func:`UpdateMasterlist`.")
.def("get_masterlist_revision",
&py::GetMasterlistRevision,
pybind11::call_guard<pybind11::gil_scoped_release>(),
"Gets the give masterlist's source control revision. Wraps :cpp:func:`GetMasterlistRevision`.")
.def("get_plugin_metadata",
&DatabaseInterface::GetPluginMetadata,
arg("plugin"),
arg("includeUserMetadata") = true,
arg("evaluateConditions") = false)
.def("get_plugin_tags", &GetPluginTags,
arg("evaluateConditions") = false,
pybind11::call_guard<pybind11::gil_scoped_release>(),
"Get all a plugin's loaded metadata. Wraps :cpp:func:`GetPluginMetadata`.")
.def("get_plugin_tags",
&GetPluginTags,
arg("plugin"),
arg("evaluateConditions") = false)
.def("get_plugin_cleanliness", &GetPluginCleanliness,
arg("evaluateConditions") = false,
pybind11::call_guard<pybind11::gil_scoped_release>(),
"Outputs the Bash Tags suggested for addition and removal by the database for the given plugin.")
.def("get_plugin_cleanliness",
&GetPluginCleanliness,
arg("plugin"),
arg("evaluateConditions") = false)
.def("write_minimal_list", &py::WriteMinimalList);
arg("evaluateConditions") = false,
pybind11::call_guard<pybind11::gil_scoped_release>(),
"Determines the database's knowledge of a plugin's cleanliness. Outputs whether the plugin should be cleaned or not, or if no data is available.")
.def("write_minimal_list",
&py::WriteMinimalList,
pybind11::call_guard<pybind11::gil_scoped_release>(),
"Writes a minimal metadata file containing only Bash Tag suggestions and/or cleanliness info from the loaded metadata. Wraps :cpp:func:`WriteMinimalList`.");
class_<PluginInterface, std::shared_ptr<PluginInterface>>(module, "PluginInterface")
.def_property_readonly("name",
&PluginInterface::GetName,
pybind11::call_guard<pybind11::gil_scoped_release>(),
"The plugin's name. Read-only. Wraps :cpp:func:`GetName`.")
.def("is_master",
&PluginInterface::IsMaster,
pybind11::call_guard<pybind11::gil_scoped_release>(),
"Check if the plugin is a master. Wraps :cpp:func:`IsMaster`.")
.def("is_light_master",
&PluginInterface::IsLightMaster,
pybind11::call_guard<pybind11::gil_scoped_release>(),
"Check if the plugin is a light master. Wraps :cpp:func:`IsLightMaster`.")
.def("is_valid_as_light_master",
&PluginInterface::IsValidAsLightMaster,
pybind11::call_guard<pybind11::gil_scoped_release>(),
"Check if the plugin contains only records with FormIDs that are valid in a light master. Wraps :cpp:func:`IsValidAsLightMaster`.");
}
void bindClasses(pybind11::module& module) {
@@ -157,7 +233,10 @@ void bindClasses(pybind11::module& module) {
}
void bindFunctions(pybind11::module& module) {
module.def("set_logging_callback", &SetLoggingCallback);
module.def("set_logging_callback",
&py::SetLoggingCallback,
arg("callback"),
"Set the callback function that is called when logging. Wraps :cpp:func:`loot::SetLoggingCallback`.");
// Need to clear the stored logging callback when exiting, or Python will
// hang because the callback pointer is still stored by libloot.
@@ -166,12 +245,18 @@ void bindFunctions(pybind11::module& module) {
SetLoggingCallback(nullptr);
}));
module.def("is_compatible", &IsCompatible);
module.def("is_compatible",
&IsCompatible,
pybind11::call_guard<pybind11::gil_scoped_release>(),
"Checks for API compatibility. Wraps :cpp:func:`loot::IsCompatible`.");
module.def("create_game_handle", &py::CreateGameHandle,
module.def("create_game_handle",
&py::CreateGameHandle,
arg("game"),
arg("game_path"),
arg("game_local_path") = "");
arg("game_local_path") = "",
pybind11::call_guard<pybind11::gil_scoped_release>(),
"Initialise a new game handle. Wraps :cpp:func:`loot::CreateGameHandle`.");
}
}
+62 -8
View File
@@ -20,29 +20,28 @@ from loot import is_compatible
from loot import set_logging_callback
def logging_callback(level, message):
pass
print(level, message)
set_logging_callback(logging_callback)
class GameFixture(unittest.TestCase):
game_path = os.path.join(u'.', u'Oblivion')
local_path = os.path.join(u'.', u'local')
master_filename = u'Oblivion.esm'
def setUp(self):
data_path = os.path.join(self.game_path, 'Data')
master_file = os.path.join(data_path, 'Oblivion.esm')
if not os.path.exists(data_path):
os.makedirs(data_path)
open(master_file, 'a').close()
open(self.master_file_path(), 'a').close()
if not os.path.exists(self.local_path):
os.makedirs(self.local_path)
def tearDown(self):
shutil.rmtree(self.game_path)
os.remove(self.master_file_path())
shutil.rmtree(self.local_path)
def master_file_path(self):
return os.path.join(self.game_path, 'Data', self.master_filename)
class TestLootApi(GameFixture):
def test_is_compatible(self):
self.assertFalse(is_compatible(0, 9, 0))
@@ -68,6 +67,61 @@ class TestLootApi(GameFixture):
db = game.get_database()
self.assertNotEqual(db, None)
class TestGameInterface(GameFixture):
def setUp(self):
super(TestGameInterface, self).setUp()
self.game = create_game_handle(GameType.tes4, self.game_path, self.local_path)
def test_load_plugins(self):
self.game.load_plugins([u'Blank.esm'], True)
self.game.load_plugins([u'Blank.esm'], False)
def test_get_plugin(self):
self.game.load_plugins([u'Blank.esm'], True)
plugin = self.game.get_plugin(u'Blank.esm')
self.assertNotEqual(plugin, None)
class TestPluginInterface(GameFixture):
game_path = os.path.join(u'.', u'SkyrimSE')
master_filename = u'Skyrim.esm'
def setUp(self):
super(TestPluginInterface, self).setUp()
self.game = create_game_handle(GameType.tes5se, self.game_path, self.local_path)
self.game.load_plugins([u'Blank.esm', u'Blank.esl'], False)
def test_name(self):
plugin = self.game.get_plugin(u'Blank.esm')
self.assertNotEqual(plugin, None)
self.assertEqual(plugin.name, u'Blank.esm')
def test_is_master(self):
plugin = self.game.get_plugin(u'Blank.esm')
self.assertNotEqual(plugin, None)
self.assertTrue(plugin.is_master())
def test_is_light_master(self):
plugin = self.game.get_plugin(u'Blank.esm')
self.assertNotEqual(plugin, None)
self.assertFalse(plugin.is_light_master())
plugin = self.game.get_plugin(u'Blank.esl')
self.assertNotEqual(plugin, None)
self.assertTrue(plugin.is_light_master())
def test_is_valid_as_light_master(self):
plugin = self.game.get_plugin(u'Blank.esm')
self.assertNotEqual(plugin, None)
self.assertTrue(plugin.is_valid_as_light_master())
class TestDatabaseInterface(GameFixture):
masterlist_path = os.path.join(os.path.dirname(__file__), u'masterlist.yaml')