Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
.venv/
__pycache__/

example/plugins/*.py
example/plugins/*.py

docs/build/
22 changes: 22 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the OS, Python version, and other tools you might need
build:
os: ubuntu-24.04
tools:
python: "3.13"

# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/source/conf.py

# Optionally, but recommended,
# declare the Python requirements required to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: docs/requirements.txt
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
[![](https://img.shields.io/github/license/summersweet-software/OpenPluginLoader)](https://github.com/Summersweet-Software/OpenPluginLoader?tab=MIT-1-ov-file) [![Read the Docs](https://img.shields.io/readthedocs/openpluginloader)](https://openpluginloader.readthedocs.io/en/latest/) [![PyPI Downloads](https://img.shields.io/pypi/dm/openpluginloader)](https://pypi.org/project/openpluginloader/) [![GitHub Sponsors](https://img.shields.io/github/sponsors/summersweet-software)](https://github.com/sponsors/Summersweet-Software)

# Open Plugin Loader

An MIT LICENSE library made to make packaging and loading of plugins easy for projects.
Expand All @@ -14,14 +16,17 @@ OPL is not going to help you make your plugin api itself, but it will make your
- Plugin Manager using the strategy pattern. Change any individual component to suite your needs.

## Features Not Provided

- Sandboxed loading strategy. Sandbox security is not our expertise. I would rather we provide only what we can reasonable ensure the quality of.

## Planned Features

- [ ] Plugin Signing

# Getting Started: Making a Plugin (Default Strategy):

Making a plugin is fairly simple. A plugin has a few basic parts:

- A `pyproject.toml` (optional)
- A `plugin.toml`
- A plugin entry point (default `__init__.py`, can be modified in your `plugin.toml`)
Expand Down Expand Up @@ -74,13 +79,13 @@ dependencies = [
```

## main.py (our entry in this example)

```
# Whatever you want here!

print("Holy moly, we loaded it!")
```


## Final structure

Here is the final file structure of our plugin
Expand All @@ -101,24 +106,25 @@ exampleplugin/
└── uv.lock
```


> [!TIP]
> When we lay it out like this, its easy to see why we should modify the `src` option
> in our `pyproject.toml`

## Packaging Our Plugin:

This heavily depends on your loading strategy, but there is a generic command provided.
It uses the default strategy.

```sh
uv run build-plugin ./ ./build
# ^ ^
# ^ ^
# | | - build/output directory
# |
# | - src directory (gets modified if a pyproject is present in that folder)
```

### Alternatively: We Can Make a Build Script.

This should mostly be provided by the plugin api you are using or create, but for testing we can also just build out our own.

```python
Expand All @@ -138,6 +144,7 @@ archiver.archive_plugin(meta, src, dest)
# Getting Started: Loading plugins (Default Strategy):

### Project structure

```
ourproject/
├── .venv/ # our virtual environment during development
Expand Down Expand Up @@ -167,7 +174,7 @@ from openpluginloader.utility import set_default_module_cache

from pathlib import Path

import ourproject # import our api.
import ourproject # import our api.

# makes our current imports available to plugins. VERY IMPORTANT
set_default_module_cache()
Expand All @@ -184,14 +191,15 @@ manager.load_all_plugins()
```

## Loading individual plugins:

```python
from openpluginloader.defaultstrategy import create_default_manager
from openpluginloader.versioning import ApiVersion
from openpluginloader.utility import set_default_module_cache

from pathlib import Path

import ourproject # import our api.
import ourproject # import our api.

# makes our current imports available to plugins. VERY IMPORTANT
set_default_module_cache()
Expand Down
20 changes: 20 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
35 changes: 35 additions & 0 deletions docs/make.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@ECHO OFF

pushd %~dp0

REM Command file for Sphinx documentation

if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build

%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)

if "%1" == "" goto help

%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end

:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%

:end
popd
1 change: 1 addition & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sphinx-rtd-theme
121 changes: 121 additions & 0 deletions docs/source/api_docs/apiversion.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
Versions
==========

This module contains all files related to versioning. This is mostly the actual :py:class:`ApiVersion`
class itself as well as several utility functions for loading versions.

.. py:currentmodule:: openpluginloader.versioning


Dataclasses
-------------

.. py:class:: ApiVersion(major: int, minor: int, patch: int | None, tag: str | None)

A :py:class:`NamedTuple` representing a version. Formatted using `Semantic Versioning <https://semver.org/>`_.

.. py:attribute:: major
:type: int

.. py:attribute:: minor
:type: int

.. py:attribute:: patch
:type: int | None

Optional patch version.

.. py:attribute:: tag
:type: str | None

Optional tag version. Things like :code:`-alpha` or :code:`-rc1`

.. py:method:: __gt__(self, other: ApiVersion | tuple[int | str | None, ...]) -> bool:

Checks if our own version is greater than another version

.. py:method:: __lt__(self, other: ApiVersion | tuple[int | str | None, ...]) -> bool:

Checks if our own version is less than another version

.. py:method:: __ge__(self, other: ApiVersion | tuple[int | str | None, ...]) -> bool:

Checks if our own version is greater than or equal to another version

.. py:method:: __le__(self, other: ApiVersion | tuple[int | str | None, ...]) -> bool:

Checks if our own version is less than or equal to another version

.. py:method:: __repr__(self) -> str:

Prints a version in its shortest form in general semantic versioning style.
ex:

- :code:`v1.0`
- :code:`v1.0.2`
- :code:`v1.0-alpha`
- :code:`v1.0.2-alpha`



Parsing Functions
-------------------


.. py:function:: parse_string_api_version(version: str) -> ApiVersion

Parses a string formatted in this way: `<major>.<minor>[.<patch>][-<tag>]`
major, minor, and patch version must be numeric characters only ([0-9]).
version `tag` can be anything.

Returns a validated api version

:raises VersionStringFormatError: When the formatting is incorrect

.. py:function:: parse_table_api_version(version: VersionDict) -> ApiVersion

Parses a table containing a version (containing major and minor version. Optionally containing patch and tag)

Returns a validated api version

:raises VersionTableFormatError: When the formatting is incorrect

.. py:function:: parse_tuple_api_version(version: VersionDict) -> ApiVersion

Parses a tuple containing a version (containing major and minor version. Optionally containing patch and tag)

Returns a validated api version

:raises VersionTupleFormatError: When the formatting is incorrect


Type Aliases
--------------


.. py:type:: VersionDict
:canonical: dict[str, float | int | str]

.. py:type:: VersionTuple
:canonical: tuple[int, int] | tuple[int, int, int | None] | tuple[int, int, int | None, str | None] | tuple


Exceptions
------------


.. py:exception:: VersionFormatError(ValueError)

Represents a generic formatting error in a version

.. py:exception:: VersionStringFormatError(VersionFormatError)

A version string was improperly formatted

.. py:exception:: VersionTableFormatError(VersionFormatError)

A version table was improperly formatted

.. py:exception:: VersionTupleFormatError(VersionFormatError)

A version tuple was improperly formatted
21 changes: 21 additions & 0 deletions docs/source/api_docs/loader.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Plugin Loader
==============

.. py:currentmodule:: openpluginloader.loader


.. py:class:: PluginLoader(Protocol)

An individual strategy used to load a plugin.
It acts as a collection of functions to control how
plugins are imported and sorted.


.. py:method:: load_plugin(self, plugin: PluginMetadata)

Loads the specified plugin given its metadata.

.. py:method:: sort_plugin_by_load_order(self, plugin: list[PluginMetadata]) -> list[PluginMetadata]

Sorts a list of plugins by its load order. Returns a sorted list of plugins.

Loading
Loading