Skip to content

Commit

Permalink
Merge pull request #48 from wehlutyk/docs
Browse files Browse the repository at this point in the history
Docs
  • Loading branch information
wehlutyk committed Aug 22, 2016
2 parents 34cbc6d + 212f85b commit 8b077c8
Show file tree
Hide file tree
Showing 26 changed files with 2,343 additions and 342 deletions.
5 changes: 2 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ RUN set -x \
python3 \
python3-pip \
sudo \
texlive \
texlive-latex-extra \
unzip \
wget \
&& rm -rf /var/lib/apt/lists/*
Expand Down Expand Up @@ -85,6 +87,3 @@ RUN set -x \
&& cd /home/brainscopypaste \
&& sudo service postgresql restart \
&& py.test

# Use the CLI tool, for easier use.
CMD ["/home/brainscopypaste/dockerstart.sh"]
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ Software developed to build our "Brains Copy Paste" paper, analyzing mutation in

The latex source (along with compiled pdf) for the paper is at [wehlutyk/brainscopypaste-paper](https://github.com/wehlutyk/brainscopypaste-paper).

TODO/OUTDATED: See the [documentation](https://brainscopypaste.readthedocs.org/en/latest/) if you want to install, run, or understand any of this.
See the [documentation](https://brainscopypaste.readthedocs.org/en/latest/) if you want to install, run, or understand any of this. A substantial effort has gone into making this code readable and well-documented, so please feel free to use or review it if you need to or want to!

If you find a bug, a mistake, or anything you think needs changing, please [file an issue](https://github.com/wehlutyk/brainscopypaste/issues/new).

## License

Expand Down
16 changes: 16 additions & 0 deletions brainscopypaste/cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
"""CLI tool for stepping through the analysis.
Once you have the environment properly set up (see :ref:`setup`), invoke this
tool with ``brainscopypaste <command>``.
The documentation for this tool can be explored using ``brainscopypaste
--help`` or ``brainscopypaste <command> --help``. If you are viewing these docs
in the browser, you will only see docstrings for convenience functions in the
module. The other docstrings appear in the source code, but are best explored
by calling the tool with ``--help``.
"""


Expand Down Expand Up @@ -54,6 +63,9 @@ def drop():


def confirm(fillin):
"""Ask the user to confirm they want to drop the content described by
`fillin`."""

text = "About to drop {}. Are you sure? (type 'yes') ".format(fillin)
click.secho(text, nl=False, fg='red', bold=True)
answer = input()
Expand Down Expand Up @@ -121,6 +133,8 @@ def drop_substitutions(obj):


def _drop_features():
"""Drop computed features from the filesystem."""

logger.info('Dropping computed features from filesystem')
click.secho('Dropping computed features... ', nl=False)

Expand Down Expand Up @@ -283,6 +297,8 @@ def variant(time, source, past, durl, max_distance, notebook_path):


def cliobj():
"""Convenience function to launch the CLI tool, used by the `setup.py`
script."""
cli(obj={})


Expand Down
130 changes: 128 additions & 2 deletions brainscopypaste/conf.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
"""Manage settings from :mod:`.settings`, allowing overriding
of some values.
"""Manage settings from the :mod:`.settings` module, allowing overriding of
some values.
Use the :data:`settings` class instance from this module to access settings
from any other module: ``from brainscopypaste.conf import settings``. Note that
only uppercase variables from the :mod:`.settings` module are taken into
account, the rest is ignored.
"""

Expand All @@ -18,20 +23,77 @@

class Settings:

"""Hold all settings for the analysis, managing and proxying access to the
:mod:`.settings` module.
Only uppercase variables from the :mod:`.settings` module are taken into
account, the rest is ignored. This class also lets you override values with
a context manager to make testing easier. See the :meth:`override` and
:meth:`file_override` methods for more details.
Use the :data:`settings` instance of this class to access a singleton
version of the settings for the whole analysis. Overridden values then
appear overridden to all other modules (i.e. for all accesses) until the
context manager is closed.
"""

def __init__(self):
"""Import the :mod:`.settings` module and check for folders to
create."""

self.mod = importlib.import_module('brainscopypaste.settings')
self._setup()
for path in self.mod.paths_to_create:
logger.debug("Checking for path '%s' to create", path)
mkdirp(path)

def _setup(self):
"""Set uppercase variables from the :mod:`.settings` module as
attributes on this instance."""

for setting in dir(self.mod):
if setting.isupper():
setattr(self, setting, getattr(self.mod, setting))

@contextmanager
def override(self, *names_values):
"""Context manager that overrides setting values for the duration of
the context.
Use this method to override one or several setting values for a block
of code, then have those settings go back to their default value. Very
useful when writing tests.
Parameters
----------
names_values : list of tuples
List of `(name, value)` tuples defining which settings to override
with what value. Setting names must already exist (you can't use
this to create a new entry).
Raises
------
ValueError
If any of the `name` values in `names_values` is not an uppercase
string or is not a known setting name.
See Also
--------
file_override
Examples
--------
Override MemeTracker filter settings for the duration of a test:
>>> from brainscopypaste.conf import settings
>>> with settings.override(('MT_FILTER_MIN_TOKENS', 2),
... ('MT_FILTER_MAX_DAYS, 50)):
... # Here: some test code using the overridden settings.
>>> # `settings` is back to default here.
"""

for name, value in names_values:
self._override(name, value)
try:
Expand All @@ -41,6 +103,46 @@ def override(self, *names_values):

@contextmanager
def file_override(self, *names):
"""Context manager that overrides a file setting by pointing it to an
empty temporary file for the duration of the context.
Some values in the :mod:`.settings` module are file paths, and you
might want to easily override the `contents` of that file for a block
of code. This method lets you do just that: it will create a temporary
file for a setting you wish to override, point that setting to the new
empty file, and clean up once the context closes. This is a shortcut
for :meth:`override` when working on files whose contents you want to
override.
Parameters
----------
names : list of str
List of setting names you want to override with temporary files.
Raises
------
ValueError
If any member of `names` is not an uppercase string or is not a
known setting name.
See Also
--------
override
Examples
--------
Override the Age-of-Acquisition source file to e.g. test code that
imports it as a word feature:
>>> from brainscopypaste.conf import settings
>>> with settings.file_override('AOA'):
... with open(settings.AOA, 'w') as aoa:
... # Write test content to the temporary AOA file.
... # Test your code on the temporary AOA content.
>>> # `settings.AOA` is back to default here.
"""

filepaths = [mkstemp()[1] for name in names]
try:
with self.override(*zip(names, filepaths)):
Expand All @@ -50,11 +152,35 @@ def file_override(self, *names):
os.remove(filepath)

def _override(self, name, value):
"""Override `name` with `value`, after some checks.
The method checks that `name` is an uppercase string, and that it
exists in the known settings. Use this when writing a context manager
that wraps the operation in try/finally blocks, then restores the
default behaviour.
Parameters
----------
name : str
Uppercase string denoting a known setting to be overridden.
value : object
Value to replace the setting with.
Raises
------
ValueError
If `name` is not an uppercase string or is not a known setting
name.
"""

if not name.isupper():
raise ValueError('Setting names must be uppercase')
if name not in dir(self.mod):
raise ValueError('Unknown setting name')
setattr(self, name, value)


#: Instance of the :class:`Settings` class that should be used to access
#: settings. See that class's documentation for more information.
settings = Settings()
53 changes: 49 additions & 4 deletions brainscopypaste/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
"""Test fixtures.
"""Test fixtures used throughout the whole test suite.
Use any of these functions if you need a boilerplate database with clusters,
quotes, urls, and substitutions to test code on. Refer to `pytest's
documentation <http://docs.pytest.org/en/latest/>`_ for more information on
fixtures and how to use them in test code.
"""

Expand All @@ -15,9 +20,15 @@

@pytest.yield_fixture
def tmpdb():
engine = create_engine('postgresql+psycopg2://brainscopypaste:'
'@localhost:5432/brainscopypaste_test',
client_encoding='utf8')
"""Get a handle to an empty temporary database that is wiped on
teardown."""

from brainscopypaste.conf import settings
engine = create_engine(
'postgresql+psycopg2://{user}:{pw}@localhost:5432/{db}'
.format(user=settings.DB_USER, pw=settings.DB_PASSWORD,
db=settings.DB_NAME_TEST),
client_encoding='utf8')
Session.configure(bind=engine)
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
Expand All @@ -27,6 +38,13 @@ def tmpdb():

@pytest.fixture
def some_clusters(tmpdb):
"""Get a handle to a temporary database filled with a few empty clusters,
wiped on teardown.
See the source code if you need details on the clusters' exact attributes.
"""

sids = range(5)
with session_scope() as session:
session.add_all(Cluster(sid=i, source='test') for i in sids)
Expand All @@ -36,6 +54,14 @@ def some_clusters(tmpdb):

@pytest.fixture
def some_quotes(some_clusters):
"""Get a handle to a temporary database filled with a few clusters and
quotes, wiped on teardown.
See the source code if you need details on the clusters' and quotes' exact
attributes.
"""

sids = range(10)
with session_scope() as session:
clusters = session.query(Cluster)
Expand All @@ -50,6 +76,14 @@ def some_quotes(some_clusters):

@pytest.fixture
def some_urls(some_clusters, some_quotes):
"""Get a handle to a temporary database filled with a few clusters, quotes,
and urls, all wiped on teardown.
See the source code if you need details on the clusters', quotes', and
urls' exact attributes.
"""

ids = range(20)
with session_scope() as session:
quotes = session.query(Quote)
Expand All @@ -66,6 +100,17 @@ def some_urls(some_clusters, some_quotes):

@pytest.fixture
def some_substitutions(some_clusters, some_quotes, some_urls):
"""Get a handle to a temporary database filled with a few clusters, quotes,
urls, and substitutions all wiped on teardown.
The substitutions are assigned to two different substitution models
(although their actual occurrences don't fit with those models).
See the source code if you need details on the clusters', quotes', and
urls' exact attributes.
"""

model1 = Model(Time.discrete, Source.majority, Past.last_bin, Durl.all, 1)
model2 = Model(Time.discrete, Source.majority, Past.all, Durl.all, 1)
with session_scope() as session:
Expand Down

0 comments on commit 8b077c8

Please sign in to comment.