diff --git a/help/en/developer/core/index.rst b/help/en/developer/core/index.rst new file mode 100644 index 00000000..4e18ee06 --- /dev/null +++ b/help/en/developer/core/index.rst @@ -0,0 +1,12 @@ +core +==== + +.. toctree:: + :maxdepth: 2 + + app + fs + engine + directories + results + gui diff --git a/help/en/developer/hscommon/build.rst b/help/en/developer/hscommon/build.rst new file mode 100644 index 00000000..b3b106bd --- /dev/null +++ b/help/en/developer/hscommon/build.rst @@ -0,0 +1,5 @@ +hscommon.build +============== + +.. automodule:: hscommon.build + :members: diff --git a/help/en/developer/hscommon/conflict.rst b/help/en/developer/hscommon/conflict.rst new file mode 100644 index 00000000..34724d10 --- /dev/null +++ b/help/en/developer/hscommon/conflict.rst @@ -0,0 +1,5 @@ +hscommon.conflict +================= + +.. automodule:: hscommon.conflict + :members: diff --git a/help/en/developer/hscommon/desktop.rst b/help/en/developer/hscommon/desktop.rst new file mode 100644 index 00000000..e30ccafd --- /dev/null +++ b/help/en/developer/hscommon/desktop.rst @@ -0,0 +1,5 @@ +hscommon.desktop +================ + +.. automodule:: hscommon.desktop + :members: diff --git a/help/en/developer/hscommon/index.rst b/help/en/developer/hscommon/index.rst new file mode 100644 index 00000000..c70b8295 --- /dev/null +++ b/help/en/developer/hscommon/index.rst @@ -0,0 +1,12 @@ +hscommon +======== + +.. toctree:: + :maxdepth: 2 + + build + conflict + desktop + notify + path + util diff --git a/help/en/developer/hscommon/notify.rst b/help/en/developer/hscommon/notify.rst new file mode 100644 index 00000000..4d61d584 --- /dev/null +++ b/help/en/developer/hscommon/notify.rst @@ -0,0 +1,5 @@ +hscommon.notify +=============== + +.. automodule:: hscommon.notify + :members: diff --git a/help/en/developer/hscommon/path.rst b/help/en/developer/hscommon/path.rst new file mode 100644 index 00000000..e2bcab3d --- /dev/null +++ b/help/en/developer/hscommon/path.rst @@ -0,0 +1,5 @@ +hscommon.path +============= + +.. automodule:: hscommon.path + :members: diff --git a/help/en/developer/hscommon/util.rst b/help/en/developer/hscommon/util.rst new file mode 100644 index 00000000..3dc3f539 --- /dev/null +++ b/help/en/developer/hscommon/util.rst @@ -0,0 +1,5 @@ +hscommon.util +============= + +.. automodule:: hscommon.util + :members: diff --git a/help/en/developer/index.rst b/help/en/developer/index.rst index f242b49c..098d973c 100644 --- a/help/en/developer/index.rst +++ b/help/en/developer/index.rst @@ -53,9 +53,5 @@ API .. toctree:: :maxdepth: 2 - core/app - core/fs - core/engine - core/directories - core/results - core/gui + core/index + hscommon/index diff --git a/hscommon/README b/hscommon/README index efee44a7..acc2ccbc 100644 --- a/hscommon/README +++ b/hscommon/README @@ -1,9 +1,3 @@ -The documentation has to be built with Sphinx. You can get Sphinx at http://sphinx.pocoo.org/ - -Once you installed it, you can build the documentation with: - -cd docs -sphinx-build . ../docs_html - -The reason why you have to move in 'docs' is because hscommon.io conflicts with the builtin 'io' -module. The documentation is also available online at http://www.hardcoded.net/docs/hscommon \ No newline at end of file +This module is common code used in all Hardcoded Software applications. It has no stable API so +it is not recommended to actually depend on it. But if you want to copy bits and pieces for your own +apps, be my guest. diff --git a/hscommon/build.py b/hscommon/build.py index dcd7ece0..15a81128 100644 --- a/hscommon/build.py +++ b/hscommon/build.py @@ -6,6 +6,9 @@ # which should be included with this package. The terms are also available at # http://www.hardcoded.net/licenses/bsd_license +"""This module is a collection of function to help in HS apps build process. +""" + import os import sys import os.path as op @@ -26,6 +29,8 @@ from .plat import ISWINDOWS from .util import modified_after, find_in_path, ensure_folder, delete_files_with_pattern def print_and_do(cmd): + """Prints ``cmd`` and executes it in the shell. + """ print(cmd) p = Popen(cmd, shell=True) return p.wait() @@ -125,6 +130,10 @@ def package_cocoa_app_in_dmg(app_path, destfolder, args): build_dmg(app_path, destfolder) def build_dmg(app_path, destfolder): + """Builds a DMG volume with application at ``app_path`` and puts it in ``dest_path``. + + The name of the resulting DMG volume is determined by the app's name and version. + """ print(repr(op.join(app_path, 'Contents', 'Info.plist'))) plist = plistlib.readPlist(op.join(app_path, 'Contents', 'Info.plist')) workpath = tempfile.mkdtemp() @@ -153,7 +162,7 @@ sysconfig.get_config_h_filename = lambda: op.join(op.dirname(__file__), 'pyconfi """) def add_to_pythonpath(path): - """Adds `path` to both PYTHONPATH env and sys.path. + """Adds ``path`` to both ``PYTHONPATH`` env and ``sys.path``. """ abspath = op.abspath(path) pythonpath = os.environ.get('PYTHONPATH', '') @@ -166,6 +175,12 @@ def add_to_pythonpath(path): # in setuptools. We copy the packages *without data* in a build folder and then build the plugin # from there. def copy_packages(packages_names, dest, create_links=False, extra_ignores=None): + """Copy python packages ``packages_names`` to ``dest``, spurious data. + + Copy will happen without tests, testdata, mercurial data or C extension module source with it. + ``py2app`` include and exclude rules are **quite** funky, and doing this is the only reliable + way to make sure we don't end up with useless stuff in our app. + """ if ISWINDOWS: create_links = False if not extra_ignores: diff --git a/hscommon/conflict.py b/hscommon/conflict.py index 019f0c81..3e556345 100644 --- a/hscommon/conflict.py +++ b/hscommon/conflict.py @@ -6,6 +6,10 @@ # which should be included with this package. The terms are also available at # http://www.hardcoded.net/licenses/bsd_license +"""When you have to deal with names that have to be unique and can conflict together, you can use +this module that deals with conflicts by prepending unique numbers in ``[]`` brackets to the name. +""" + import re import os import shutil @@ -18,7 +22,7 @@ from .path import Path, pathify re_conflict = re.compile(r'^\[\d{3}\d*\] ') def get_conflicted_name(other_names, name): - """Returns name with a [000] number in front of it. + """Returns name with a ``[000]`` number in front of it. The number between brackets depends on how many conlicted filenames there already are in other_names. @@ -34,16 +38,21 @@ def get_conflicted_name(other_names, name): i += 1 def get_unconflicted_name(name): + """Returns ``name`` without ``[]`` brackets. + + Brackets which, of course, might have been added by func:`get_conflicted_name`. + """ return re_conflict.sub('',name,1) def is_conflicted(name): + """Returns whether ``name`` is prepended with a bracketed number. + """ return re_conflict.match(name) is not None @pathify def _smart_move_or_copy(operation, source_path: Path, dest_path: Path): - ''' Use move() or copy() to move and copy file with the conflict management, but without the - slowness of the fs system. - ''' + """Use move() or copy() to move and copy file with the conflict management. + """ if dest_path.isdir() and not source_path.isdir(): dest_path = dest_path[source_path.name] if dest_path.exists(): @@ -54,9 +63,13 @@ def _smart_move_or_copy(operation, source_path: Path, dest_path: Path): operation(str(source_path), str(dest_path)) def smart_move(source_path, dest_path): + """Same as :func:`smart_copy`, but it moves files instead. + """ _smart_move_or_copy(shutil.move, source_path, dest_path) def smart_copy(source_path, dest_path): + """Copies ``source_path`` to ``dest_path``, recursively and with conflict resolution. + """ try: _smart_move_or_copy(shutil.copy, source_path, dest_path) except IOError as e: diff --git a/hscommon/currency.py b/hscommon/currency.py index a4644505..d73ea5c1 100644 --- a/hscommon/currency.py +++ b/hscommon/currency.py @@ -6,6 +6,10 @@ # which should be included with this package. The terms are also available at # http://www.hardcoded.net/licenses/bsd_license +"""This module facilitates currencies management. It exposes :class:`Currency` which lets you +easily figure out their exchange value. +""" + import os from datetime import datetime, date, timedelta import logging @@ -17,6 +21,21 @@ from .path import Path from .util import iterdaterange class Currency: + """Represents a currency and allow easy exchange rate lookups. + + A ``Currency`` instance is created with either a 3-letter ISO code or with a full name. If it's + present in the database, an instance will be returned. If not, ``ValueError`` is raised. The + easiest way to access a currency instance, however, if by using module-level constants. For + example:: + + >>> from hscommon.currency import USD, EUR + >>> from datetime import date + >>> USD.value_in(EUR, date.today()) + 0.6339119851386843 + + Unless a :class:`RatesDB` global instance is set through :meth:`Currency.set_rate_db` however, + only fallback values will be used as exchange rates. + """ all = [] by_code = {} by_name = {} @@ -68,12 +87,16 @@ class Currency: @staticmethod def set_rates_db(db): + """Sets a new currency ``RatesDB`` instance to be used with all ``Currency`` instances. + """ Currency.rates_db = db @staticmethod def get_rates_db(): + """Returns the current ``RatesDB`` instance. + """ if Currency.rates_db is None: - Currency.rates_db = RatesDB() # Make sure we always have some db to work with + Currency.rates_db = RatesDB() # Make sure we always have some db to work with return Currency.rates_db def rates_date_range(self): @@ -372,7 +395,12 @@ class RatesDB: self._cache = {} def date_range(self, currency_code): - """Returns (start, end) of the cached rates for currency""" + """Returns (start, end) of the cached rates for currency. + + Returns a tuple ``(start_date, end_date)`` representing dates covered in the database for + currency ``currency_code``. If there are gaps, they are not accounted for (subclasses that + automatically update themselves are not supposed to introduce gaps in the db). + """ sql = "select min(date), max(date) from rates where currency = '%s'" % currency_code cur = self._execute(sql) start, end = cur.fetchone() diff --git a/hscommon/docs/build.rst b/hscommon/docs/build.rst deleted file mode 100644 index 4db5316d..00000000 --- a/hscommon/docs/build.rst +++ /dev/null @@ -1,25 +0,0 @@ -========================================== -:mod:`build` - Build utilities for HS apps -========================================== - -This module is a collection of function to help in HS apps build process. - -.. function:: print_and_do(cmd) - - Prints ``cmd`` and executes it in the shell. - -.. function:: build_all_qt_ui(base_dir='.') - - Calls Qt's ``pyuic4`` for each file in ``base_dir`` with a ".ui" extension. The resulting file is saved under ``{base_name}_ui.py``. - -.. function:: build_dmg(app_path, dest_path) - - Builds a DMG volume with application at ``app_path`` and puts it in ``dest_path``. The name of the resulting DMG volume is determined by the app's name and version. - -.. function:: add_to_pythonpath(path) - - Adds ``path`` to both ``PYTHONPATH`` env variable and ``sys.path``. - -.. function:: copy_packages(packages_names, dest) - - Copy python packages ``packages_names`` to ``dest``, but without tests, testdata, mercurial data or C extension module source with it. ``py2app`` include and exclude rules are **quite** funky, and doing this is the only reliable way to make sure we don;t end up with useless stuff in our app. \ No newline at end of file diff --git a/hscommon/docs/conf.py b/hscommon/docs/conf.py deleted file mode 100644 index 9ec64693..00000000 --- a/hscommon/docs/conf.py +++ /dev/null @@ -1,194 +0,0 @@ -# -*- coding: utf-8 -*- -# -# hscommon documentation build configuration file, created by -# sphinx-quickstart on Fri Mar 12 16:00:37 2010. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.append(os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = 'hscommon' -copyright = '2011, Hardcoded Software' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '1.0.0' -# The full version, including alpha/beta/rc tags. -release = '1.0.0' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of documents that shouldn't be included in the build. -#unused_docs = [] - -# List of directories, relative to source directory, that shouldn't be searched -# for source files. -exclude_trees = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_use_modindex = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'hscommondoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'hscommon.tex', 'hscommon Documentation', - 'Hardcoded Software', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_use_modindex = True diff --git a/hscommon/docs/conflict.rst b/hscommon/docs/conflict.rst deleted file mode 100644 index c186d809..00000000 --- a/hscommon/docs/conflict.rst +++ /dev/null @@ -1,27 +0,0 @@ -=================================================== -:mod:`conflict` - Detect and resolve name conflicts -=================================================== - -.. module:: conflict - -When you have to deal with names that have to be unique and can conflict together, you can use this module that deals with conflicts by prepending unique numbers in ``[]`` brackets to the name. - -.. function:: get_conflicted_name(other_names, name) - - Returns a name based on ``name`` that is guaranteed not to be in ``other_names``. Name conflicts are resolved by prepending numbers in ``[]`` brackets to the name. - -.. function:: get_unconflicted_name(name) - - Returns ``name`` without ``[]`` brackets. - -.. function:: is_conflicted(name) - - Returns whether ``name`` is prepended with a bracketed number. - -.. function:: smart_copy(source_path, dest_path) - - Copies ``source_path`` to ``dest_path``, recursively. However, it does conflict resolution using functions in this module. - -.. function:: smart_move(source_path, dest_path) - - Same as :func:`smart_copy`, but it moves files instead. diff --git a/hscommon/docs/currency.rst b/hscommon/docs/currency.rst deleted file mode 100644 index 13d2992f..00000000 --- a/hscommon/docs/currency.rst +++ /dev/null @@ -1,62 +0,0 @@ -=================================== -:mod:`currency` - Manage currencies -=================================== - -This module facilitates currencies management. It exposes :class:`Currency` which lets you easily figure out their exchange value. - -The ``Currency`` class -====================== - -.. class:: Currency(code=None, name=None) - - A ``Currency`` instance is created with either a 3-letter ISO code or with a full name. If it's present in the database, an instance will be returned. If not, ``ValueError`` is raised. The easiest way to access a currency instance, however, if by using module-level constants. For example:: - - >>> from hscommon.currency import USD, EUR - >>> from datetime import date - >>> USD.value_in(EUR, date.today()) - 0.6339119851386843 - - Unless a :class:`currency.RatesDB` global instance is set through :meth:`Currency.set_rate_db` however, only fallback values will be used as exchange rates. - - .. staticmethod:: Currency.register(code, name, exponent=2, fallback_rate=1) - - Register a new currency in the currency list. - - .. staticmethod:: Currency.set_rates_db(db) - - Sets a new currency ``RatesDB`` instance to be used with all ``Currency`` instances. - - .. staticmethod:: Currency.set_rates_db() - - Returns the current ``RatesDB`` instance. - - .. method:: Currency.rates_date_range() - - Returns the range of date for which rates are available for this currency. - - .. method:: Currency.value_in(currency, date) - - Returns the value of this currency in terms of the other currency on the given date. - - .. method:: Currency.set_CAD_value(value, date) - - Sets currency's value in CAD on the given date. - -The ``RatesDB`` class -===================== - -.. class:: RatesDB(db_or_path=':memory:') - - A sqlite database that stores currency/date/value pairs, "value" being the value of the currency in CAD at the given date. Currencies are referenced by their 3-letter ISO code in the database and it its arguments (so ``currency_code`` arguments must be 3-letter strings). - - .. method:: RatesDB.date_range(currency_code) - - Returns a tuple ``(start_date, end_date)`` representing dates covered in the database for currency ``currency_code``. If there are gaps, they are not accounted for (subclasses that automatically update themselves are not supposed to introduce gaps in the db). - - .. method:: RatesDB.get_rate(date, currency1_code, currency2_code) - - Returns the exchange rate between currency1 and currency2 for date. The rate returned means '1 unit of currency1 is worth X units of currency2'. The rate of the nearest date that is smaller than 'date' is returned. If there is none, a seek for a rate with a higher date will be made. - - .. method:: RatesDB.set_CAD_value(date, currency_code, value) - - Sets the CAD value of ``currency_code`` at ``date`` to ``value`` in the database. diff --git a/hscommon/docs/index.rst b/hscommon/docs/index.rst deleted file mode 100644 index ce4cd37b..00000000 --- a/hscommon/docs/index.rst +++ /dev/null @@ -1,32 +0,0 @@ -============================================== -hscommon - Common code used throughout HS apps -============================================== - -:Author: `Hardcoded Software `_ -:Dev website: http://hg.hardcoded.net/hscommon -:License: BSD License - -Introduction -============ - -``hscommon`` is a collection of tools used throughout HS apps. - -Dependencies -============ - -Python 3.1 is required. `py.test `_ is required to run the tests. - -API Documentation -================= - -.. toctree:: - :maxdepth: 2 - - build - conflict - currency - notify - path - reg - sqlite - util diff --git a/hscommon/docs/notify.rst b/hscommon/docs/notify.rst deleted file mode 100644 index af7dbdfe..00000000 --- a/hscommon/docs/notify.rst +++ /dev/null @@ -1,26 +0,0 @@ -========================================== -:mod:`notify` - Simple notification system -========================================== - -.. module:: notify - -This module is a brain-dead simple notification system involving a :class:`Broadcaster` and a :class:`Listener`. A listener can only listen to one broadcaster. A broadcaster can have multiple listeners. If the listener is connected, whenever the broadcaster calls :meth:`~Broadcaster.notify`, the method with the same name as the broadcasted message is called on the listener. - -.. class:: Broadcaster - - .. method:: notify(msg) - - Notify all connected listeners of ``msg``. That means that each listeners will have their method with the same name as ``msg`` called. - -.. class:: Listener(broadcaster) - - A listener is initialized with the broadcaster it's going to listen to. Initially, it is not connected. - - .. method:: connect() - - Connects the listener to its broadcaster. - - .. method:: disconnect() - - Disconnects the listener from its broadcaster. - diff --git a/hscommon/docs/path.rst b/hscommon/docs/path.rst deleted file mode 100644 index 8c594fae..00000000 --- a/hscommon/docs/path.rst +++ /dev/null @@ -1,13 +0,0 @@ -======================================== -:mod:`path` - Work with paths -======================================== - -.. module:: path - -.. class:: Path(value, separator=None) - - ``Path`` instances can be created from strings, other ``Path`` instances or tuples. If ``separator`` is not specified, the one from the OS is used. Once created, paths can be manipulated like a tuple, each element being an element of the path. It makes a few common operations easy, such as getting the filename (``path[-1]``) or the directory name or parent (``path[:-1]``). - - HS apps pretty much always refer to ``Path`` instances when a variable name ends with ``path``. If a variable is of another type, that type is usually explicited in the name. - - To make common operations (from ``os.path``, ``os`` and ``shutil``) convenient, the :mod:`io` module wraps these functions and converts paths to strings. diff --git a/hscommon/docs/reg.rst b/hscommon/docs/reg.rst deleted file mode 100644 index 0d074ebc..00000000 --- a/hscommon/docs/reg.rst +++ /dev/null @@ -1,25 +0,0 @@ -======================================== -:mod:`reg` - Manage app registration -======================================== - -.. module:: reg - -.. class:: RegistrableApplication - - HS main application classes subclass this. It provides an easy interface for managing whether the app should be in demo mode or not. - - .. method:: _setup_as_registered() - - Virtual. This is called whenever the app is unlocked. This is the one place to put code that changes to UI to indicate that the app is unlocked. - - .. method:: validate_code(code, email) - - Validates ``code`` with email. If it's valid, it does nothing. Otherwise, it raises ``InvalidCodeError`` with a message indicating why it's invalid (wrong product, wrong code format, fields swapped). - - .. method:: set_registration(code, email) - - If ``code`` and ``email`` are valid, sets ``registered`` to True as well as ``registration_code`` and ``registration_email`` and then calls :meth:`_setup_as_registered`. - -.. exception:: InvalidCodeError - - Raised during :meth:`RegistrableApplication.validate_code`. diff --git a/hscommon/docs/sqlite.rst b/hscommon/docs/sqlite.rst deleted file mode 100644 index 1faa8cdf..00000000 --- a/hscommon/docs/sqlite.rst +++ /dev/null @@ -1,9 +0,0 @@ -========================================== -:mod:`sqlite` - Threaded sqlite connection -========================================== - -.. module:: sqlite - -.. class:: ThreadedConn(dbname, autocommit) - - ``sqlite`` connections can't be used across threads. ``TheadedConn`` opens a sqlite connection in its own thread and sends it queries through a queue, making it suitable in multi-threaded environment. \ No newline at end of file diff --git a/hscommon/docs/util.rst b/hscommon/docs/util.rst deleted file mode 100644 index ac5d54f1..00000000 --- a/hscommon/docs/util.rst +++ /dev/null @@ -1,88 +0,0 @@ -======================================== -:mod:`util` - Miscellaneous utilities -======================================== - -.. module:: misc - -.. function:: nonone(value, replace_value) - - Returns ``value`` if value is not None. Returns ``replace_value`` otherwise. - -.. function:: dedupe(iterable) - - Returns a list of elements in ``iterable`` with all dupes removed. The order of the elements is preserved. - -.. function:: flatten(iterables, start_with=None) - - Takes the list of iterable ``iterables`` and returns a list containing elements of every iterable. - - If ``start_with`` is not None, the result will start with ``start_with`` items, exactly as if ``start_with`` would be the first item of lists. - -.. function:: first(iterable) - - Returns the first item of ``iterable`` or ``None`` if empty. - -.. function:: tryint(value, default=0) - - Tries to convert ``value`` to in ``int`` and returns ``default`` if it fails. - -.. function:: escape(s, to_escape, escape_with='\\') - - Returns ``s`` with characters in ``to_escape`` all prepended with ``escape_with``. - -.. function:: format_size(size, decimal=0, forcepower=-1, showdesc=True) - - Transform a byte count ``size`` in a formatted string (KB, MB etc..). ``decimal`` is the number digits after the dot. ``forcepower`` is the desired suffix. 0 is B, 1 is KB, 2 is MB etc.. if kept at -1, the suffix will be automatically chosen (so the resulting number is always below 1024). If ``showdesc`` is True, the suffix will be shown after the number. Usage example:: - - >>> format_size(1234, decimal=2, showdesc=True) - '1.21 KB' - -.. function:: format_time(seconds, with_hours=True) - - Transforms seconds in a hh:mm:ss string. - - If `with_hours` if false, the format is mm:ss. - -.. function:: format_time_decimal(seconds) - - Transforms seconds in a strings like '3.4 minutes'. - -.. function:: get_file_ext(filename) - - Returns the lowercase extension part of ``filename``, without the dot. - -.. function:: pluralize(number, word, decimals=0, plural_word=None) - - Returns a string with ``number`` in front of ``word``, and adds a 's' to ``word`` if ``number`` > 1. If ``plural_word`` is defined, it will replace ``word`` in plural cases instead of appending a 's'. - -.. function:: rem_file_ext(filename) - - Returns ``filename`` without extension. - -.. function:: multi_replace(s, replace_from, replace_to='') - - A function like str.replace() with multiple replacements. ``replace_from`` is a list of things you want to replace (Ex: ``['a','bc','d']``). ``replace_to`` is a list of what you want to replace to. If ``replace_to`` is a list and has the same length as ``replace_from``, ``replace_from`` items will be translated to corresponding ``replace_to``. A ``replace_to`` list must have the same length as ``replace_from``. If ``replace_to`` is a string, all ``replace_from`` occurences will be replaced by that string. ``replace_from`` can also be a string. If it is, every char in it will be translated as if ``replace_from`` would be a list of chars. If ``replace_to`` is a string and has the same length as ``replace_from``, it will be transformed into a list. - -.. function:: open_if_filename(infile, mode='rb') - - If ``infile`` is a string, it opens and returns it. If it's already a file object, it simply returns it. This function returns ``(file, should_close_flag)``. The should_close_flag is True is a file has effectively been opened (if we already pass a file object, we assume that the responsibility for closing the file has already been taken). Example usage:: - - fp, shouldclose = open_if_filename(infile) - dostuff() - if shouldclose: - fp.close() - -.. class:: FileOrPath(file_or_path, mode='rb') - - Does the same as :func:`open_if_filename`, but it can be used with a ``with`` statement. Example:: - - with FileOrPath(infile): - dostuff() - -.. function:: delete_if_empty(path, files_to_delete=[]) - - Same as with :func:`clean_empty_dirs`, but not recursive. - -.. function:: modified_after(first_path, second_path) - - Returns True if ``first_path``'s mtime is higher than ``second_path``'s mtime. \ No newline at end of file diff --git a/hscommon/notify.py b/hscommon/notify.py index 13421f01..b61e5f8c 100644 --- a/hscommon/notify.py +++ b/hscommon/notify.py @@ -4,9 +4,19 @@ # which should be included with this package. The terms are also available at # http://www.hardcoded.net/licenses/bsd_license +"""Very simple inter-object notification system. + +This module is a brain-dead simple notification system involving a :class:`Broadcaster` and a +:class:`Listener`. A listener can only listen to one broadcaster. A broadcaster can have multiple +listeners. If the listener is connected, whenever the broadcaster calls :meth:`~Broadcaster.notify`, +the method with the same name as the broadcasted message is called on the listener. +""" + from collections import defaultdict class Broadcaster: + """Broadcasts messages that are received by all listeners. + """ def __init__(self): self.listeners = set() @@ -14,6 +24,10 @@ class Broadcaster: self.listeners.add(listener) def notify(self, msg): + """Notify all connected listeners of ``msg``. + + That means that each listeners will have their method with the same name as ``msg`` called. + """ for listener in self.listeners.copy(): # listeners can change during iteration if listener in self.listeners: # disconnected during notification listener.dispatch(msg) @@ -23,6 +37,8 @@ class Broadcaster: class Listener: + """A listener is initialized with the broadcaster it's going to listen to. Initially, it is not connected. + """ def __init__(self, broadcaster): self.broadcaster = broadcaster self._bound_notifications = defaultdict(list) @@ -38,9 +54,13 @@ class Listener: self._bound_notifications[message].append(func) def connect(self): + """Connects the listener to its broadcaster. + """ self.broadcaster.add_listener(self) def disconnect(self): + """Disconnects the listener from its broadcaster. + """ self.broadcaster.remove_listener(self) def dispatch(self, msg): diff --git a/hscommon/path.py b/hscommon/path.py index 9ac8d197..36abda08 100755 --- a/hscommon/path.py +++ b/hscommon/path.py @@ -18,20 +18,12 @@ from inspect import signature class Path(tuple): """A handy class to work with paths. - path[index] returns a string - path[start:stop] returns a Path - start and stop can be int, but the can also be path instances. When start - or stop are Path like in refpath[p1:p2], it is the same thing as typing - refpath[len(p1):-len(p2)], except that it will only slice out stuff that are - equal. For example, 'a/b/c/d'['a/z':'z/d'] returns 'b/c', not ''. - See the test units for more details. + We subclass ``tuple``, each element of the tuple represents an element of the path. - You can use the + operator, which is the same thing as with tuples, but - returns a Path. - - In HS applications, all paths variable should be Path instances. These Path instances should - be converted to str only at the last moment (when it is needed in an external function, such - as os.rename) + * ``Path('/foo/bar/baz')[1]`` --> ``'bar'`` + * ``Path('/foo/bar/baz')[1:2]`` --> ``Path('bar/baz')`` + * ``Path('/foo/bar')['baz']`` --> ``Path('/foo/bar/baz')`` + * ``str(Path('/foo/bar/baz'))`` --> ``'/foo/bar/baz'`` """ # Saves a little bit of memory usage __slots__ = () @@ -135,10 +127,18 @@ class Path(tuple): return str(self).encode(sys.getfilesystemencoding()) def parent(self): + """Returns the parent path. + + ``Path('/foo/bar/baz').parent()`` --> ``Path('/foo/bar')`` + """ return self[:-1] @property def name(self): + """Last element of the path (filename), with extension. + + ``Path('/foo/bar/baz').name`` --> ``'baz'`` + """ return self[-1] # OS method wrappers diff --git a/hscommon/sqlite.py b/hscommon/sqlite.py index f61314d8..f4978237 100644 --- a/hscommon/sqlite.py +++ b/hscommon/sqlite.py @@ -114,6 +114,10 @@ class _ActualThread(threading.Thread): class ThreadedConn: + """``sqlite`` connections can't be used across threads. ``TheadedConn`` opens a sqlite + connection in its own thread and sends it queries through a queue, making it suitable in + multi-threaded environment. + """ def __init__(self, dbname, autocommit): self._t = _ActualThread(dbname, autocommit) self.lastrowid = -1 diff --git a/hscommon/util.py b/hscommon/util.py index 790d352c..48011540 100644 --- a/hscommon/util.py +++ b/hscommon/util.py @@ -18,14 +18,16 @@ from datetime import timedelta from .path import Path, pathify, log_io_error def nonone(value, replace_value): - ''' Returns value if value is not None. Returns replace_value otherwise. - ''' + """Returns ``value`` if ``value`` is not ``None``. Returns ``replace_value`` otherwise. + """ if value is None: return replace_value else: return value def tryint(value, default=0): + """Tries to convert ``value`` to in ``int`` and returns ``default`` if it fails. + """ try: return int(value) except (TypeError, ValueError): @@ -39,8 +41,10 @@ def minmax(value, min_value, max_value): #--- Sequence related def dedupe(iterable): - '''Returns a list of elements in iterable with all dupes removed. - ''' + """Returns a list of elements in ``iterable`` with all dupes removed. + + The order of the elements is preserved. + """ result = [] seen = {} for item in iterable: @@ -51,11 +55,11 @@ def dedupe(iterable): return result def flatten(iterables, start_with=None): - '''Takes a list of lists 'lists' and returns a list containing elements of every list. + """Takes a list of lists ``iterables`` and returns a list containing elements of every list. - If start_with is not None, the result will start with start_with items, exactly as if - start_with would be the first item of lists. - ''' + If ``start_with`` is not ``None``, the result will start with ``start_with`` items, exactly as + if ``start_with`` would be the first item of lists. + """ result = [] if start_with: result.extend(start_with) @@ -64,7 +68,7 @@ def flatten(iterables, start_with=None): return result def first(iterable): - """Returns the first item of 'iterable' + """Returns the first item of ``iterable``. """ try: return next(iter(iterable)) @@ -116,10 +120,13 @@ def trailiter(iterable, skipfirst=False): #--- String related def escape(s, to_escape, escape_with='\\'): + """Returns ``s`` with characters in ``to_escape`` all prepended with ``escape_with``. + """ return ''.join((escape_with + c if c in to_escape else c) for c in s) def get_file_ext(filename): - """Returns the lowercase extension part of filename, without the dot.""" + """Returns the lowercase extension part of filename, without the dot. + """ pos = filename.rfind('.') if pos > -1: return filename[pos + 1:].lower() @@ -127,7 +134,8 @@ def get_file_ext(filename): return '' def rem_file_ext(filename): - """Returns the filename without extension.""" + """Returns the filename without extension. + """ pos = filename.rfind('.') if pos > -1: return filename[:pos] @@ -135,12 +143,13 @@ def rem_file_ext(filename): return filename def pluralize(number, word, decimals=0, plural_word=None): - """Returns a string with number in front of s, and adds a 's' to s if number > 1 + """Returns a pluralized string with ``number`` in front of ``word``. - number: The number to go in front of s - word: The word to go after number - decimals: The number of digits after the dot - plural_word: If the plural rule for word is more complex than adding a 's', specify a plural + Adds a 's' to s if ``number`` > 1. + ``number``: The number to go in front of s + ``word``: The word to go after number + ``decimals``: The number of digits after the dot + ``plural_word``: If the plural rule for word is more complex than adding a 's', specify a plural """ number = round(number, decimals) format = "%%1.%df %%s" % decimals @@ -154,7 +163,7 @@ def pluralize(number, word, decimals=0, plural_word=None): def format_time(seconds, with_hours=True): """Transforms seconds in a hh:mm:ss string. - If `with_hours` if false, the format is mm:ss. + If ``with_hours`` if false, the format is mm:ss. """ minus = seconds < 0 if minus: @@ -171,7 +180,8 @@ def format_time(seconds, with_hours=True): return r def format_time_decimal(seconds): - """Transforms seconds in a strings like '3.4 minutes'""" + """Transforms seconds in a strings like '3.4 minutes'. + """ minus = seconds < 0 if minus: seconds *= -1 @@ -193,11 +203,15 @@ SIZE_VALS = tuple(1024 ** i for i in range(1,9)) def format_size(size, decimal=0, forcepower=-1, showdesc=True): """Transform a byte count in a formatted string (KB, MB etc..). - size is the number of bytes to format. - decimal is the number digits after the dot. - forcepower is the desired suffix. 0 is B, 1 is KB, 2 is MB etc.. if kept at -1, the suffix will - be automatically chosen (so the resulting number is always below 1024). - if showdesc is True, the suffix will be shown after the number. + ``size`` is the number of bytes to format. + ``decimal`` is the number digits after the dot. + ``forcepower`` is the desired suffix. 0 is B, 1 is KB, 2 is MB etc.. if kept at -1, the suffix + will be automatically chosen (so the resulting number is always below 1024). + if ``showdesc`` is ``True``, the suffix will be shown after the number. + Usage example:: + + >>> format_size(1234, decimal=2, showdesc=True) + '1.21 KB' """ if forcepower < 0: i = 0 @@ -234,16 +248,16 @@ def remove_invalid_xml(s, replace_with=' '): def multi_replace(s, replace_from, replace_to=''): """A function like str.replace() with multiple replacements. - replace_from is a list of things you want to replace. Ex: ['a','bc','d'] - replace_to is a list of what you want to replace to. - If replace_to is a list and has the same length as replace_from, replace_from - items will be translated to corresponding replace_to. A replace_to list must - have the same length as replace_from - If replace_to is a basestring, all replace_from occurence will be replaced + ``replace_from`` is a list of things you want to replace. Ex: ['a','bc','d'] + ``replace_to`` is a list of what you want to replace to. + If ``replace_to`` is a list and has the same length as ``replace_from``, ``replace_from`` + items will be translated to corresponding ``replace_to``. A ``replace_to`` list must + have the same length as ``replace_from`` + If ``replace_to`` is a string, all ``replace_from`` occurence will be replaced by that string. - replace_from can also be a str. If it is, every char in it will be translated - as if replace_from would be a list of chars. If replace_to is a str and has - the same length as replace_from, it will be transformed into a list. + ``replace_from`` can also be a str. If it is, every char in it will be translated + as if ``replace_from`` would be a list of chars. If ``replace_to`` is a str and has + the same length as ``replace_from``, it will be transformed into a list. """ if isinstance(replace_to, str) and (len(replace_from) != len(replace_to)): replace_to = [replace_to for r in replace_from] @@ -298,8 +312,8 @@ def find_in_path(name, paths=None): @log_io_error @pathify def delete_if_empty(path: Path, files_to_delete=[]): - ''' Deletes the directory at 'path' if it is empty or if it only contains files_to_delete. - ''' + """Deletes the directory at 'path' if it is empty or if it only contains files_to_delete. + """ if not path.exists() or not path.isdir(): return contents = path.listdir() @@ -311,10 +325,16 @@ def delete_if_empty(path: Path, files_to_delete=[]): return True def open_if_filename(infile, mode='rb'): - """ - infile can be either a string or a file-like object. - if it is a string, a file will be opened with mode. - Returns a tuple (shouldbeclosed,infile) infile is a file object + """If ``infile`` is a string, it opens and returns it. If it's already a file object, it simply returns it. + + This function returns ``(file, should_close_flag)``. The should_close_flag is True is a file has + effectively been opened (if we already pass a file object, we assume that the responsibility for + closing the file has already been taken). Example usage:: + + fp, shouldclose = open_if_filename(infile) + dostuff() + if shouldclose: + fp.close() """ if isinstance(infile, Path): return (infile.open(mode), True) @@ -349,6 +369,13 @@ def delete_files_with_pattern(folder_path, pattern, recursive=True): delete_files_with_pattern(p, pattern, True) class FileOrPath: + """Does the same as :func:`open_if_filename`, but it can be used with a ``with`` statement. + + Example:: + + with FileOrPath(infile): + dostuff() + """ def __init__(self, file_or_path, mode='rb'): self.file_or_path = file_or_path self.mode = mode