Skip to content

Commit a5a6e2d

Browse files
authored
Merge pull request #1901 from sopel-irc/document-env-vars
doc: cover environment variable support
2 parents 73e7230 + 860dce5 commit a5a6e2d

File tree

4 files changed

+157
-0
lines changed

4 files changed

+157
-0
lines changed

docs/source/cli.rst

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ to, and channels to join. By default, it creates the file
1616
Once this is done, the ``start`` subcommand runs the bot, using this
1717
configuration file unless one is provided using the ``-c``/``--config`` option.
1818

19+
Certain command-line options can be passed via environment variables. Also see
20+
the :ref:`section on environment variables <Supported environment variables>`
21+
for more possibilities.
22+
1923
.. contents::
2024
:local:
2125
:depth: 1
@@ -67,3 +71,80 @@ The ``sopel-plugins`` command
6771

6872
.. autoprogram:: sopel.cli.plugins:build_parser()
6973
:prog: sopel-plugins
74+
75+
76+
Supported environment variables
77+
===============================
78+
79+
80+
``SOPEL_CONFIG``
81+
----------------
82+
83+
This environment variable replaces the built-in default config name (which is,
84+
confusingly, also "default") if set. It's interpreted in the same way as the
85+
``-c``/``--config`` option accepted by most CLI commands described above.
86+
87+
.. versionadded:: 7.0
88+
89+
90+
``SOPEL_CONFIG_DIR``
91+
--------------------
92+
93+
This environment variable replaces the default directory in which Sopel
94+
searches for config files. It's interpreted in the same way as the
95+
``--config-dir`` option accepted by most CLI commands described above.
96+
97+
.. versionadded:: 7.1
98+
99+
100+
Overriding individual settings
101+
------------------------------
102+
103+
Whenever a setting is accessed, Sopel looks for a matching environment
104+
variable. If found, the environment variable's value (even if it's empty)
105+
overrides the value from Sopel's config file.
106+
107+
The variable name Sopel looks for is structured as follows:
108+
109+
* ``SOPEL_`` prefix (to prevent collisions with other programs)
110+
* The section name in UPPERCASE, e.g. ``CORE`` or ``PLUGIN_NAME``
111+
* ``_`` as separator
112+
* The setting name in UPPERCASE, e.g. ``NICK`` or ``API_KEY``
113+
114+
For example, take this stripped-down config file:
115+
116+
.. code-block:: ini
117+
118+
[core]
119+
nick = ConfigFileNick
120+
host = chat.freenode.net
121+
122+
[plugin_name]
123+
api_key = abad1dea
124+
125+
Sopel would take the nickname ``ConfigFileNick`` when connecting to IRC at
126+
``chat.freenode.net``, and the ``plugin_name`` plugin would use the API key
127+
``abad1dea`` when communicating with its remote service.
128+
129+
However, by setting the environment variables:
130+
131+
.. code-block:: shell
132+
133+
SOPEL_CORE_NICK=EnvVarNick
134+
SOPEL_PLUGIN_NAME_API_KEY=1337c0ffee9001
135+
136+
Sopel would take the nickname ``EnvVarNick`` when connecting to IRC (still at
137+
``chat.freenode.net``; that value isn't overridden or lost), and the
138+
``plugin_name`` plugin would use the API key ``1337c0ffee9001``, instead.
139+
140+
.. versionadded:: 7.0
141+
142+
.. note::
143+
144+
Any ``_`` character in the section or setting name also appears in the
145+
environment variable name. It's therefore *theoretically* possible for two
146+
plugins to have section and setting name pairs that both resolve to the same
147+
environment variable name, but in practice this is highly unlikely.
148+
149+
However, should such a collision occur, please notify the main Sopel project
150+
*and* both plugin authors via any relevant communication channel(s).

docs/source/plugin.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,26 @@ A Sopel plugin consists of a Python module containing one or more
1313
``callable``\s. It may optionally also contain ``configure``, ``setup``, and
1414
``shutdown`` hooks.
1515

16+
Sopel plugins conventionally have all-lowercase names, usually one word.
17+
However, sometimes multiple words are needed for clarity or disambiguation;
18+
``snake_case`` is normally used for these.
19+
20+
.. note::
21+
22+
How Sopel determines a plugin's name depends on what kind of plugin it is:
23+
24+
Single file
25+
The file's basename (e.g. ``plugin`` in ``plugin.py``)
26+
27+
Folder
28+
The folder name (e.g. ``plugin`` in ``~/.sopel/plugins/plugin/__init__.py``)
29+
30+
Namespace package
31+
The submodule name (e.g. ``plugin`` in ``sopel_modules.plugin``)
32+
33+
Entry point
34+
The entry point name (e.g. ``plugin`` in ``plugin = my_plugin.module.path``)
35+
1636
.. py:function:: callable(bot, trigger)
1737
1838
:param bot: the bot's instance

sopel/config/__init__.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,20 @@ def add_section(self, name):
194194
:meth:`define_section` and a child class of
195195
:class:`~.types.StaticSection`.
196196
197+
.. important::
198+
199+
The section's ``name`` SHOULD follow *snake_case* naming rules:
200+
201+
* use only lowercase letters, digits, and underscore (``_``)
202+
* SHOULD NOT start with a digit
203+
204+
Deviations from *snake_case* can break the following operations:
205+
206+
* :ref:`accessing the section <sopel.config>` from Python code using
207+
the :class:`~.Config` object's attributes
208+
* :ref:`overriding the section's values <Overriding individual
209+
settings>` using environment variables
210+
197211
"""
198212
try:
199213
return self.parser.add_section(name)
@@ -216,6 +230,21 @@ def define_section(self, name, cls_, validate=True):
216230
raised if they are invalid. This is desirable in a plugin's
217231
:func:`setup` function, for example, but might not be in the
218232
:func:`configure` function.
233+
234+
.. important::
235+
236+
The section's ``name`` SHOULD follow *snake_case* naming rules:
237+
238+
* use only lowercase letters, digits, and underscore (``_``)
239+
* SHOULD NOT start with a digit
240+
241+
Deviations from *snake_case* can break the following operations:
242+
243+
* :ref:`accessing the section <sopel.config>` from Python code using
244+
the :class:`~.Config` object's attributes
245+
* :ref:`overriding the section's values <Overriding individual
246+
settings>` using environment variables
247+
219248
"""
220249
if not issubclass(cls_, types.StaticSection):
221250
raise ValueError("Class must be a subclass of StaticSection.")

sopel/config/types.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ class StaticSection(object):
4747
4848
This class is intended to be subclassed and customized with added
4949
attributes containing :class:`BaseValidated`-based objects.
50+
51+
.. note::
52+
53+
By convention, subclasses of ``StaticSection`` are named with the
54+
plugin's name in CamelCase, plus the suffix ``Section``. For example, a
55+
plugin named ``editor`` might name its subclass ``EditorSection``; a
56+
``do_stuff`` plugin might name its subclass ``DoStuffSection`` (its
57+
name converted from ``snake_case`` to ``CamelCase``).
58+
59+
However, this is *only* a convention. Any class name that is legal in
60+
Python will work just fine.
61+
5062
"""
5163
def __init__(self, config, section_name, validate=True):
5264
if not config.parser.has_section(section_name):
@@ -120,6 +132,21 @@ class BaseValidated(object):
120132
the value *must* be configured by the user (i.e. there is no suitable
121133
default value). Trying to read an empty ``NO_DEFAULT`` value will raise
122134
:class:`AttributeError`.
135+
136+
.. important::
137+
138+
Setting names SHOULD follow *snake_case* naming rules:
139+
140+
* use only lowercase letters, digits, and underscore (``_``)
141+
* SHOULD NOT start with a digit
142+
143+
Deviations from *snake_case* can break the following operations:
144+
145+
* :ref:`accessing the setting <sopel.config>` from Python code using
146+
the :class:`~.Config` object's attributes
147+
* :ref:`overriding the setting's value <Overriding individual
148+
settings>` using environment variables
149+
123150
"""
124151
def __init__(self, name, default=None, is_secret=False):
125152
self.name = name

0 commit comments

Comments
 (0)