Contents
Plugin Development Guide
This page documents some best practices and guidelines for plugin development. It is a community driven document, and everyone is encouraged to contribute to it. If you have questions or comments, please raise them on the Trac-dev MailingList.
Licensing
Plugin authors are encouraged to clearly indicate how the contribution is licensed. This is important for both users and future developers of your plugin, because if you choose to no longer support the plugin, the terms under which someone else can adopt and develop it are clear. It is also important for users and administrators who need to understand the terms and conditions under which they can use or modify the code.
Trac-Hacks is an open source community driven by voluntary contributions and made successful by collaboration. Therefore we encourage the use of licenses that foster collaboration and minimise restrictions on future use of the code. Trac has adopted the BSD 3-Clause license, and use of the same license in any plugin code is encouraged. One of the many benefits to adopting this license is that any plugin code can be integrated into the Trac core.
The following steps are suggested to add a license:
- Add the
license
keyword insetup.py
(example). - Add a license header to every Python source file (example).
Note: For an executable file such as
# -*- coding: utf-8 -*- # # Copyright (C) YYYY-YYYY your name here <your email here> # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution.
setup.py
, the header will follow the line#!/usr/bin/env python
(example). - Add a license header to every RSS and XHTML Genshi template (example: todo).
The use of the XML comment marker as shown is important, so that the text does not get rendered to the output. Make sure not to use the alternate form, which is rendered to the output as a hidden comment:
<!--! Copyright (C) YYYY-YYYY your name here <your email here> All rights reserved. This software is licensed as described in the file COPYING, which you should have received as part of this distribution. -->
<!-- This is also a comment -->
- Add a
COPYING
file with the license text in the top-level directory of the project (example). - Add an appropriate tag to the wiki page:
New tags can be added for more descriptive license types.
Currently it is not recommended to add license text to static resources (ie file in htdocs
), since doing so increases the size of the content that is sent to the client. This issue will be addressed in the Trac core when support is added for minimization (trac:#10672).
Metadata for Single-File Plugins
Plugins are typically packaged using setuptools egg format, but Trac also supports single-file plugins. A single-file plugin is a single .py
file that is placed in the project or shared plugins
directory. While the metadata for a packaged plugin is stored in its setup.py
file, the metadata for a single-file plugin can be added using file-scope attributes. The supported attributes and their aliases are: author
, author_email
, home_page
(url
), license
, trac
and version
(revision
):
version = "$Rev$" home_page = "https://trac-hacks.org/wiki/MyAmazingPlugin" license = "3-Clause BSD" author = "Joe Bloggs" author_email = "trac@python.org"
The attributes should be self-explanatory with the possible exception of the trac
attribute. The trac
attribute is used to direct bug reports to an issue tracker that differs from home_page
. If the plugin is hosted on trac-hacks.org and the home_page
attribute is set to point to the project wiki page, the trac
attribute will not need to be set.
For files stored in Subversion, Keyword Substitution is supported for the version
(revision
) attribute.
version = '$Rev$'
The file's svn:keywords
property must be edited to append Rev
. Note that the aliases Revision
, LastChangedRevision
and HeadURL
are not supported by Trac.
svn propedit svn:keywords MyAmazingMacro.py
Coding Style
Authors are encouraged to conform to the Trac Style Guide and PEP-0008 style guide.
Assert Minimum Trac Version Requirement
A common method of specifying a minimum required Trac version is to add an installation requirement in setup.py
, for example: install_requires=['Trac >= 0.12']
. However, this may cause issues, some of which are documented in #9800, and is not recommended. One of the more severe consequences is that setuptools may download and install a newer version of Trac during the installation of a plugin and the result can be an unintended upgrade of a user's installation (#10607).
A better approach is to place the following in the package __init__.py
, modifying min_trac_version
as appropriate for your plugin:
import pkg_resources pkg_resources.require('Trac >= 1.0')
You should still specify that Trac is an installation requirement, but without enforcing a version requirement: install_requires=['Trac']
.
The check in __init__.py
is performed at runtime, so the egg can be built in an environment that does not satisfy the installation requirements. One use-case for this behavior is building the egg on a development computer and uploading it through the plugin admin page. An error such as the following will be seen in the logs if the plugin fails to load due to a failed requirement:
02:39:22 PM Trac[loader] DEBUG: Loading changelog.ChangeLogMacro from /home/user/Workspace/clm/lib/python2.7/site-packages/ChangeLogMacro-0.2-py2.7.egg 02:39:22 PM Trac[loader] ERROR: Skipping "changelog.ChangeLogMacro = changelog.ChangeLogMacro": (version conflict "VersionConflict: (Trac 0.12 (/home/user/Workspace/clm/trac-0.12), Requirement.parse('Trac>=1.0'))")
Documenting required and optional components
The docstring for a Component class is displayed as the Component description on the plugin admin panel.
It is recommended that the description be prefixed with [required]
or [optional]
, to guide end-users in enabling the proper Components. The [extra]
descriptor can also be used for features that have specialized or narrow use-cases.
If your plugin ships with other code, such as jQuery or a Python library, then mention this in your plugin description.
Plugin version identification
You should use the major.minor.micro
semantic versioning scheme for Trac plugins as also used by Trac (version identification). Some additional guidance can be found in PEP:0440.
In the event that a defect or packaging distribution error is discovered after a release is made, the micro version number should be incremented and a new minor release created. Note that a filename cannot be reused when uploading to PyPI more than once, so the micro version must also be incremented in the event of a packaging or uploading error.
Development releases are created directly from the current source. These are not uploaded to PyPI and usually done by the user to get the latest snapshot. To distinguish them from final releases a dev
suffix should be added. This can be done for you by creating a setup.cfg
file in the same directory where setup.py
is located with the following content:
[egg_info] tag_build = dev
Development cycle
The development and release cycle of plugins also adheres to standards. In the following examples the current final version of a plugin is 1.0.1
and the next planned release version is 1.2.0
.
- In
setup.py
specify the next version:setup( name='YourPluginsName', version='1.2.0', [...] )
- in
setup.cfg
add the development tag as described before:[egg_info] tag_build = dev
When consequently a plugin is further developed, then the development release should be labeled as YourPluginName-1.2.0.dev0-py3.8.egg
. So when creating a release do the following:
- Temporarily remove the entry from
setup.cfg
:#[egg_info] #tag_build = dev
- Create a release version which will be named like
YourPluginName-1.2.0-py3.8.egg
. If you also want to publish to PyPI, then see the Instructions below. - After publishing the release version prepare the next development cycle by increasing the version number in
setup.py
and putting back the development tag insetup.cfg
:setup( name='YourPluginsName', # Note that the version number was changed from 1.2.0 to 1.3.0 # which will be the next release. version='1.3.0', [...] )
[egg_info] tag_build = dev
Publishing Packages to PyPI
There are no strict rules on how you should publish your packages to PyPI, but for those unfamiliar with the process we present some recommendations. Most Trac plugins contain only Python code and static assets, and therefore packages can be published in a platform and Python-version independent wheel format. It is also recommended that you publish your package in the sdist
(tarball) format. There is also a list of all Trac plugins that have been published to PyPI.
- Register yourself an account on PyPI.
- Install twine and add your credentials to
.pypirc
in your home directory. Example:[distutils] index-servers = pypi pypi-test [pypi] username = myusername password = mypassword [pypi-test] repository = https://test.pypi.org/legacy username = myusername password = mypassword
- Name your package appropriately. The package name is specified with the
name
argument insetup.py
. It is recommended that you prefix your package name withTrac
, for easy identification and to reduce the likelihood of a package name collision with an existing PyPI package. For example, FullBlogPlugin is given the nameTracFullBlog
, and TagsPlugin is given the nameTracTags
. - Add
Framework :: Trac
toclassifiers
insetup.py
. The classifiers are used as filters on PyPI. - Update dependencies in your environment:
$ pip install -U pip setuptools wheel
- From a checkout of your source code, run:
$ python setup.py sdist bdist_wheel
- Publish your packages:
$ pip install -U twine $ twine upload dist/*.whl dist/*.tar.gz
Some things to be aware of:
- Once a package is published to PyPI the package cannot be republished without changing the package version. You can simply bump the version or add a post release number.
- You may wish to first publish to
pypi-test
, particularly if you aren't familiar with the process. Once you establish that the package can be installed from pypi-test, you can publish to PyPI. Note that you have to register separately for pypi-test, and specify the server name intwine
commands:$ twine upload dist/*.whl dist/*.tar.gz pypi-test
- Once you take ownership of a package name on PyPI there is no process for transferring ownership of the package that can happen independent of you (see PEP:0541). This is a frequent cause of abandoned packages on PyPI, where the original owner is not reachable and a new maintainer of the package cannot update the published package. For that reason, please consider giving ownership of the package to other users in case you someday decide to no longer maintain the package. For example, you could give ownership to the TracHacks admins.
Further reading: