Skip to content

Commit

Permalink
Improve docs and make Settings.save() a public method.
Browse files Browse the repository at this point in the history
  • Loading branch information
deanishe committed Aug 16, 2014
1 parent 6f99293 commit 5eb6468
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 18 deletions.
4 changes: 4 additions & 0 deletions TODO
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ General:
trying to achieve. They would just be a duplication of the comments in the
source code in any case.
- Add `setup.py` so `Alfred-Workflow` can be added to PyPi and installed with `pip`
- Add explicit `save()` method to `Settings`

background.py:
- Add `stop_process()` function.
Allow `killing` of running processes. Can be used for stopping servers.

web.py:

Expand Down
Binary file modified alfred-workflow.zip
Binary file not shown.
95 changes: 92 additions & 3 deletions doc/howto.rst
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ The data directory is intended for more permanent, user-generated data, or data
that cannot be otherwise easily recreated. See :ref:`Storing data <storing-data>`
for details.

It is easy to specify a custom file format for your stored data
via the ``serializer`` argument if you want your data to be readable by the user
or by other software. See :ref:`Serialization <serialization>` for more details.

There are also simliar methods related to the root directory of your Workflow
(where ``info.plist`` and your code are):

Expand All @@ -156,8 +160,10 @@ Caching data
------------

:class:`Workflow <workflow.workflow.Workflow>` provides a few methods to simplify
caching data that is slow to retrieve or expensive to generate. The main method
is :meth:`Workflow.cached_data() <workflow.workflow.Workflow.cached_data>`, which
caching data that is slow to retrieve or expensive to generate (e.g. downloaded
from a web API). These data are cached in your workflow's cache directory (see
:attr:`~workflow.workflow.Workflow.cachedir`). The main method is
:meth:`Workflow.cached_data() <workflow.workflow.Workflow.cached_data>`, which
takes a name under which the data should be cached, a callable to retrieve
the data if they aren't in the cache (or are too old), and a maximum age in seconds
for the cached data:
Expand Down Expand Up @@ -202,6 +208,9 @@ and retrieve permanent data:
:meth:`store_data() <workflow.workflow.Workflow.store_data>` and
:meth:`stored_data() <workflow.workflow.Workflow.stored_data>`.

These data are stored in your workflow's data directory
(see :attr:`~workflow.workflow.Workflow.datadir`).

.. code-block:: python
:linenos:
Expand Down Expand Up @@ -235,6 +244,20 @@ file in your Workflow's data directory when it is changed.
:class:`~workflow.workflow.Settings` can be used just like a normal :class:`dict`
with the caveat that all keys and values must be serializable to JSON.

**Note:** A :class:`~workflow.workflow.Settings` instance can only automatically
recognise when you directly alter the values of its own keys:

.. code-block:: python
:linenos:
wf = Workflow()
wf.settings['key'] = {'key2': 'value'} # will be automatically saved
wf.settings['key']['key2'] = 'value2' # will *not* be automatically saved
If you've altered a data structure stored within your workflow's
:attr:`Workflow.settings <workflow.workflow.Workflow.settings>`, you need to
explicitly call :meth:`Workflow.settings.save() <workflow.workflow.Settings.save>`.

If you need to store arbitrary data, you can use the :ref:`cached data API <caching-data>`.

If you need to store data securely (such as passwords and API keys),
Expand Down Expand Up @@ -469,6 +492,72 @@ or to :class:`Workflow <workflow.workflow.Workflow>` on instantiation and then
normalised.


.. _background-processes:

Background processes
====================

Many workflows provide a convenient interface to applications and/or web services.

For performance reasons, it's common for workflows to cache data locally, but
updating this cache typically takes a few seconds, making your workflow
unresponsive while an update is occurring, which is very un-Alfred-like.

To avoid such delays, **Alfred-Workflow** provides the :mod:`~workflow.background`
module to allow you to easily run scripts in the background.

There are two functions, :func:`~workflow.background.run_in_background` and
:func:`~workflow.background.is_running`, that provide the interface. The
processes started are proper background daemon processes, so you can start
proper servers as easily as simple scripts.

Here's an example of a common usage pattern (updating cached data in the
background). What we're doing is:

1. Check the age of the cached data and run the update script via
:func:`~workflow.background.run_in_background` if the cached data are too old
or don't exist.
2. (Optionally) inform the user that data are being updated.
3. Load the cached data regardless of age.
4. Display the cached data (if any).

.. code-block:: python
:linenos:
from workflow import Workflow, ICON_INFO
from workflow.background import run_in_background, is_running
def main(wf):
# Is cache over 6 hours old or non-existent?
if not wf.cached_data_fresh('exchange-rates', 3600):
run_in_background('update',
['/usr/bin/python',
wf.workflowfile('update_exchange_rates.py')])
# Add a notification if the script is running
if is_running('update'):
wf.add_item('Updating exchange rates...', icon=ICON_INFO)
exchange_rates = wf.cached_data('exchage-rates')
# Display (possibly stale) cache data
if exchange_rates:
for rate in exchange_rates:
wf.add_item(rate)
# Send results to Alfred
wf.send_feedback()
if __name__ == '__main__':
wf = Workflow()
wf.run(main)
For a working example, see :ref:`Part 2 of the Tutorial <background-updates>` or
the `source code <https://github.com/deanishe/alfred-repos/blob/master/src/repos.py>`_
of my `Git Repos <https://github.com/deanishe/alfred-repos>`_ workflow,
which is a bit smarter about showing the user update information.


.. _serialization:

Serialization
Expand Down Expand Up @@ -539,7 +628,7 @@ of the stored file.

The :meth:`stored_data() <workflow.workflow.Workflow.stored_data>` method can
automatically determine the serialization of the stored data, provided the
relevant serializer is registered. If it isn't, an exception will be raised.
corresponding serializer is registered. If it isn't, an exception will be raised.


Built-in icons
Expand Down
4 changes: 2 additions & 2 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ tricks that might also be of interest to intermediate Pythonistas.
tutorial2


Alfred-Workflow howto
=====================
Howto
=====

If you know your way around Python and Alfred, here's an overview of what
**Alfred-Workflow** does and how to do it.
Expand Down
10 changes: 6 additions & 4 deletions workflow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@
- Parsing script arguments.
- Text decoding/normalisation.
- Caching data and settings.
- Secure storage (and sync) of passwords (using OS X Keychain).
- Storing data and settings.
- Caching data from, e.g., web services with a simple API for updating expired data.
- Securely storing (and syncing) passwords using OS X Keychain.
- Generating XML output for Alfred.
- Including external libraries (adding directories to ``sys.path``).
- Filtering results using an Alfred-like algorithm.
- Filtering results using an Alfred-like, fuzzy search algorithm.
- Generating log output for debugging.
- Running background processes to keep your workflow responsive.
- Capturing errors, so the workflow doesn't fail silently.
Quick Example
Expand Down Expand Up @@ -108,7 +110,7 @@ def main(wf):
"""

__version__ = '1.8'
__version__ = '1.8.1'


from .workflow import Workflow, PasswordNotFound, KeychainError
Expand Down
35 changes: 26 additions & 9 deletions workflow/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ def register(self, name, serializer):
:param name: Name to register ``serializer`` under
:type name: ``unicode`` or ``str``
:param serilializer: object with ``load()`` and ``dump()``
:param serializer: object with ``load()`` and ``dump()``
methods
"""
Expand Down Expand Up @@ -744,7 +744,7 @@ def __init__(self, filepath, defaults=None):
elif defaults:
for key, val in defaults.items():
self[key] = val
self._save() # save default settings
self.save() # save default settings

def _load(self):
"""Load cached settings from JSON file `self._filepath`"""
Expand All @@ -755,8 +755,13 @@ def _load(self):
self[key] = value
self._nosave = False

def _save(self):
"""Save settings to JSON file `self._filepath`"""
def save(self):
"""Save settings to JSON file specified in ``self._filepath``
If you're using this class via :attr:`Workflow.settings`, which
you probably are, ``self._filepath`` will be ``settings.json``
in your workflow's data directory (see :attr:`~Workflow.datadir`).
"""
if self._nosave:
return
data = {}
Expand All @@ -768,17 +773,17 @@ def _save(self):
# dict methods
def __setitem__(self, key, value):
super(Settings, self).__setitem__(key, value)
self._save()
self.save()

def update(self, *args, **kwargs):
"""Override :class:`dict` method to save on update."""
super(Settings, self).update(*args, **kwargs)
self._save()
self.save()

def setdefault(self, key, value=None):
"""Override :class:`dict` method to save on update."""
ret = super(Settings, self).setdefault(key, value)
self._save()
self.save()
return ret


Expand Down Expand Up @@ -1030,6 +1035,11 @@ def args(self):
def cachedir(self):
"""Path to workflow's cache directory.
The cache directory is a subdirectory of Alfred's own cache directory in
``~/Library/Caches``. The full path is:
``~/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/<bundle id>``
:returns: full path to workflow's cache directory
:rtype: ``unicode``
Expand All @@ -1050,6 +1060,11 @@ def cachedir(self):
def datadir(self):
"""Path to workflow's data directory.
The data directory is a subdirectory of Alfred's own data directory in
``~/Library/Application Support``. The full path is:
``~/Library/Application Support/Alfred 2/Workflow Data/<bundle id>``
:returns: full path to workflow data directory
:rtype: ``unicode``
Expand Down Expand Up @@ -1088,7 +1103,8 @@ def workflowdir(self):
return self._workflowdir

def cachefile(self, filename):
"""Return full path to ``filename`` within workflow's cache dir.
"""Return full path to ``filename`` within your workflow's
:attr:`cache directory <Workflow.cachedir>`.
:param filename: basename of file
:type filename: ``unicode``
Expand All @@ -1100,7 +1116,8 @@ def cachefile(self, filename):
return os.path.join(self.cachedir, filename)

def datafile(self, filename):
"""Return full path to ``filename`` within workflow's data dir.
"""Return full path to ``filename`` within your workflow's
:attr:`data directory <Workflow.datadir>`.
:param filename: basename of file
:type filename: ``unicode``
Expand Down

0 comments on commit 5eb6468

Please sign in to comment.