pax_global_header 0000666 0000000 0000000 00000000064 13135751141 0014513 g ustar 00root root 0000000 0000000 52 comment=d92262b9a50aaa0cd44211420651b8d15ed7d5b9
hiro-0.5/ 0000775 0000000 0000000 00000000000 13135751141 0012320 5 ustar 00root root 0000000 0000000 hiro-0.5/.codeclimate.yml 0000664 0000000 0000000 00000000033 13135751141 0015366 0 ustar 00root root 0000000 0000000 exclude_paths:
- tests/*
hiro-0.5/.coveragerc 0000664 0000000 0000000 00000000231 13135751141 0014435 0 ustar 00root root 0000000 0000000 [run]
include =
**/hiro/**
omit =
**/hiro/tests/**
**/setup.py
[report]
exclude_lines =
pragma: no cover
raise NotImplementedError
hiro-0.5/.gitignore 0000664 0000000 0000000 00000000241 13135751141 0014305 0 ustar 00root root 0000000 0000000 .installed.cfg
bin
develop-eggs
dist
eggs
parts
src/*.egg-info
lib
lib64
*.pyc
*.log
cover/*
build
.coverage*
.test_env
.python-version
.idea
htmlcov
*egg-info*
hiro-0.5/.travis.yml 0000664 0000000 0000000 00000000336 13135751141 0014433 0 ustar 00root root 0000000 0000000 language: python
python:
- "2.6"
- "2.7"
- "3.3"
- "3.4"
- "3.5"
- "pypy"
install:
- pip install -r requirements/ci.txt
script: nosetests --with-cov -v -w tests
after_success:
- coveralls
hiro-0.5/CLASSIFIERS 0000664 0000000 0000000 00000000754 13135751141 0014020 0 ustar 00root root 0000000 0000000 Development Status :: 4 - Beta
Environment :: Console
Intended Audience :: Developers
Intended Audience :: End Users/Desktop
License :: OSI Approved :: MIT License
Operating System :: OS Independent
Topic :: Software Development :: Testing
Topic :: Software Development :: Libraries
Programming Language :: Python :: 2.6
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3.2
Programming Language :: Python :: 3.3
Programming Language :: Python :: Implementation :: PyPy
hiro-0.5/LICENSE 0000664 0000000 0000000 00000002073 13135751141 0013327 0 ustar 00root root 0000000 0000000 The MIT License (MIT)
Copyright (c) 2013 Ali-Akber Saifee
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
hiro-0.5/MANIFEST.in 0000664 0000000 0000000 00000000134 13135751141 0014054 0 ustar 00root root 0000000 0000000 include README.rst
include LICENSE
include CLASSIFIERS
recursive-include requirements *.txt
hiro-0.5/README.rst 0000664 0000000 0000000 00000013322 13135751141 0014010 0 ustar 00root root 0000000 0000000 .. |travis| image:: https://img.shields.io/travis/alisaifee/hiro/master.svg?style=flat-square
:target: https://travis-ci.org/#!/alisaifee/hiro?branch=master
.. |coveralls| image:: https://img.shields.io/coveralls/alisaifee/hiro/master.svg?style=flat-square
:target: https://coveralls.io/r/alisaifee/hiro?branch=master
.. |license| image:: https://img.shields.io/pypi/l/hiro.svg?style=flat-square
:target: https://pypi.python.org/pypi/hiro
.. |pypi| image:: https://img.shields.io/pypi/v/hiro.svg?style=flat-square
:target: https://pypi.python.org/pypi/hiro
*********************************************
Hiro - time manipulation utilities for python
*********************************************
|travis| |coveralls| |pypi| |license|
Yatta!
-- Hiro Nakamura
====================
Hiro context manager
====================
Timeline context
================
The ``hiro.Timeline`` context manager hijacks a few commonly used time functions
to allow time manipulation within its context. Specifically ``time.sleep``, ``time.time``,
``time.gmtime``, ``datetime.now``, ``datetime.utcnow`` and ``datetime.today`` behave according the configuration of the context.
The context provides the following manipulation options:
* ``rewind``: accepts seconds as an integer or a ``timedelta`` object.
* ``forward``: accepts seconds as an integer or a ``timedelta`` object.
* ``freeze``: accepts a floating point time since epoch or ``datetime`` or ``date`` object to freeze the time at.
* ``unfreeze``: resumes time from the point it was frozen at.
* ``scale``: accepts a floating point to accelerate/decelerate time by. ``> 1 = acceleration, < 1 = deceleration``
* ``reset``: resets all time alterations.
.. code-block:: python
import hiro
from datetime import timedelta, datetime
import time
datetime.now().isoformat()
# OUT: '2013-12-01T06:55:41.706060'
with hiro.Timeline() as timeline:
# forward by an hour
timeline.forward(60*60)
datetime.now().isoformat()
# OUT: '2013-12-01T07:55:41.707383'
# jump forward by 10 minutes
timeline.forward(timedelta(minutes=10))
datetime.now().isoformat()
# OUT: '2013-12-01T08:05:41.707425'
# jump to yesterday and freeze
timeline.freeze(datetime.now() - timedelta(hours=24))
datetime.now().isoformat()
# OUT: '2013-11-30T09:15:41'
timeline.scale(5) # scale time by 5x
time.sleep(5) # this will effectively only sleep for 1 second
# since time is frozen the sleep has no effect
datetime.now().isoformat()
# OUT: '2013-11-30T09:15:41'
timeline.rewind(timedelta(days=365))
datetime.now().isoformat()
# OUT: '2012-11-30T09:15:41'
To reduce the amount of statements inside the context, certain timeline setup
tasks can be done via the constructor and/or by using the fluent interface.
.. code-block:: python
import hiro
import time
from datetime import timedelta, datetime
start_point = datetime(2012,12,12,0,0,0)
my_timeline = hiro.Timeline(scale=5).forward(60*60).freeze()
with my_timeline as timeline:
print datetime.now()
# OUT: '2012-12-12 01:00:00.000315'
time.sleep(5) # effectively 1 second
# no effect as time is frozen
datetime.now()
# OUT: '2012-12-12 01:00:00.000315'
timeline.unfreeze()
# back to starting point
datetime.now()
# OUT: '2012-12-12 01:00:00.000317'
time.sleep(5) # effectively 1 second
# takes effect (+5 seconds)
datetime.now()
# OUT: '2012-12-12 01:00:05.003100'
``Timeline`` can additionally be used as a decorator
.. code-block:: python
import hiro
import time, datetime
@hiro.Timeline(scale=50000)
def sleeper():
datetime.datetime.now()
# OUT: '2013-11-30 14:27:43.409291'
time.sleep(60*60) # effectively 72 ms
datetime.datetime.now()
# OUT: '2013-11-30 15:28:36.240675'
@hiro.Timeline()
def sleeper_aware(timeline):
datetime.datetime.now()
# OUT: '2013-11-30 14:27:43.409291'
timeline.forward(60*60)
datetime.datetime.now()
# OUT: '2013-11-30 15:28:36.240675'
==============
Hiro executors
==============
In order to execute certain callables within a ``Timeline`` context, two
shortcut functions are provided.
* ``run_sync(factor=1, callable, *args, **kwargs)``
* ``run_async(factor=1, callable, *args, **kwargs)``
Both functions return a ``ScaledRunner`` object which provides the following methods
* ``get_execution_time``: The actual execution time of the ``callable``
* ``get_response`` (will either return the actual return value of ``callable`` or raise the exception that was thrown)
``run_async`` returns a derived class of ``ScaledRunner`` that additionally provides the following methods
* ``is_running``: ``True/False`` depending on whether the callable has completed execution
* ``join``: blocks until the ``callable`` completes execution
Example
=======
.. code-block:: python
import hiro
import time
def _slow_function(n):
time.sleep(n)
if n > 10:
raise RuntimeError()
return n
runner = hiro.run_sync(10, _slow_function, 10)
runner.get_response()
# OUT: 10
# due to the scale factor 10 it only took 1s to execute
runner.get_execution_time()
# OUT: 1.1052658557891846
runner = hiro.run_async(10, _slow_function, 11)
runner.is_running()
# OUT: True
runner.join()
runner.get_execution_time()
# OUT: 1.1052658557891846
runner.get_response()
# OUT: Traceback (most recent call last):
# ....
# OUT: File "", line 4, in _slow_function
# OUT: RuntimeError
hiro-0.5/doc/ 0000775 0000000 0000000 00000000000 13135751141 0013065 5 ustar 00root root 0000000 0000000 hiro-0.5/doc/Makefile 0000664 0000000 0000000 00000012675 13135751141 0014540 0 ustar 00root root 0000000 0000000 # Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make ' where is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/hiro.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/hiro.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/hiro"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/hiro"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
hiro-0.5/doc/source/ 0000775 0000000 0000000 00000000000 13135751141 0014365 5 ustar 00root root 0000000 0000000 hiro-0.5/doc/source/api.rst 0000664 0000000 0000000 00000000476 13135751141 0015677 0 ustar 00root root 0000000 0000000 *****************
API Documentation
*****************
.. currentmodule:: hiro
.. autoclass:: Timeline
:members:
.. autofunction:: run_async
.. autofunction:: run_sync
.. currentmodule:: hiro.core
.. autoclass:: ScaledRunner
:members:
.. autoclass:: ScaledAsyncRunner
:members:
:inherited-members:
hiro-0.5/doc/source/conf.py 0000664 0000000 0000000 00000017666 13135751141 0015704 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
#
# hiro documentation build configuration file, created by
# sphinx-quickstart on Sun Dec 1 07:29:51 2013.
#
# 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
sys.path += ["../../"]
import hiro.version
# 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.insert(0, os.path.abspath('.'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.mathjax', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode']
# 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-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'hiro'
copyright = u'2013, Ali-Akber Saifee'
# 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 = hiro.version.__version__
# The full version, including alpha/beta/rc tags.
release = hiro.version.__version__
# 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 patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []
# 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. See the documentation for
# a list of builtin themes.
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_domain_indices = 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, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = 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 = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'hirodoc'
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'hiro.tex', u'hiro Documentation',
u'Ali-Akber Saifee', '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
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'hiro', u'hiro Documentation',
[u'Ali-Akber Saifee'], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'hiro', u'hiro Documentation',
u'Ali-Akber Saifee', 'hiro', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'http://docs.python.org/': None}
hiro-0.5/doc/source/index.rst 0000664 0000000 0000000 00000001043 13135751141 0016224 0 ustar 00root root 0000000 0000000
#############################################
Hiro - time manipulation utilities for python
#############################################
Often testing code that can be time dependent can become either fragile or
slow. Hiro provides context managers and utilities to either freeze, accelerate
or decelerate and jump between different points in time. Functions exposed by the
standard library's ``time``, ``datetime`` and ``date`` modules are patched within
the the contexts exposed.
.. toctree::
:maxdepth: 4
intro
api
project
hiro-0.5/doc/source/intro.rst 0000664 0000000 0000000 00000012271 13135751141 0016255 0 ustar 00root root 0000000 0000000 **********************************
Hiro context manager and utilities
**********************************
Timeline context
================
The :class:`hiro.Timeline` context manager hijacks a few commonly used time functions
to allow time manipulation within its context. Specifically :func:`time.sleep`, :func:`time.time`,
:func:`time.gmtime`, :meth:`datetime.datetime.now`, :meth:`datetime.datetime.utcnow` and :meth:`datetime.datetime.today`
behave according the configuration of the context.
The context provides the following manipulation options:
* ``rewind``: accepts seconds as an integer or a ``timedelta`` object.
* ``forward``: accepts seconds as an integer or a ``timedelta`` object.
* ``freeze``: accepts a floating point time since epoch or ``datetime`` or ``date`` object to freeze the time at.
* ``unfreeze``: resumes time from the point it was frozen at.
* ``scale``: accepts a floating point to accelerate/decelerate time by. ``> 1 = acceleration, < 1 = deceleration``
* ``reset``: resets all time alterations.
.. code-block:: python
import hiro
from datetime import timedelta, datetime
import time
datetime.now().isoformat()
# OUT: '2013-12-01T06:55:41.706060'
with hiro.Timeline() as timeline:
# forward by an hour
timeline.forward(60*60)
datetime.now().isoformat()
# OUT: '2013-12-01T07:55:41.707383'
# jump forward by 10 minutes
timeline.forward(timedelta(minutes=10))
datetime.now().isoformat()
# OUT: '2013-12-01T08:05:41.707425'
# jump to yesterday and freeze
timeline.freeze(datetime.now() - timedelta(hours=24))
datetime.now().isoformat()
# OUT: '2013-11-30T09:15:41'
timeline.scale(5) # scale time by 5x
time.sleep(5) # this will effectively only sleep for 1 second
# since time is frozen the sleep has no effect
datetime.now().isoformat()
# OUT: '2013-11-30T09:15:41'
timeline.rewind(timedelta(days=365))
datetime.now().isoformat()
# OUT: '2012-11-30T09:15:41'
To reduce the amount of statements inside the context, certain timeline setup
tasks can be done via the constructor and/or by using the fluent interface.
.. code-block:: python
import hiro
import time
from datetime import timedelta, datetime
start_point = datetime(2012,12,12,0,0,0)
my_timeline = hiro.Timeline(scale=5).forward(60*60).freeze()
with my_timeline as timeline:
print datetime.now()
# OUT: '2012-12-12 01:00:00.000315'
time.sleep(5) # effectively 1 second
# no effect as time is frozen
datetime.now()
# OUT: '2012-12-12 01:00:00.000315'
timeline.unfreeze()
# back to starting point
datetime.now()
# OUT: '2012-12-12 01:00:00.000317'
time.sleep(5) # effectively 1 second
# takes effect (+5 seconds)
datetime.now()
# OUT: '2012-12-12 01:00:05.003100'
:class:`hiro.Timeline` can additionally be used as a decorator. If the decorated
function expects has a ``timeline`` argument, the :class:`hiro.Timeline` will be
passed to it.
.. code-block:: python
import hiro
import time, datetime
@hiro.Timeline(scale=50000)
def sleeper():
datetime.datetime.now()
# OUT: '2013-11-30 14:27:43.409291'
time.sleep(60*60) # effectively 72 ms
datetime.datetime.now()
# OUT: '2013-11-30 15:28:36.240675'
@hiro.Timeline()
def sleeper_aware(timeline):
datetime.datetime.now()
# OUT: '2013-11-30 14:27:43.409291'
timeline.forward(60*60)
datetime.datetime.now()
# OUT: '2013-11-30 15:28:36.240675'
run_sync and run_async
======================
In order to execute certain callables within a :class:`hiro.ScaledTimeline` context, two
shortcut functions are provided.
* ``run_sync(factor=1, callable, *args, **kwargs)``
* ``run_async(factor=1, callable, *args, **kwargs)``
Both functions return a :class:`hiro.core.ScaledRunner` object which provides the following methods
* ``get_execution_time``: The actual execution time of the ``callable``
* ``get_response`` (will either return the actual return value of ``callable`` or raise the exception that was thrown)
``run_async`` returns a derived class of :class:`hiro.core.ScaledRunner` that additionally provides the following methods
* ``is_running``: ``True/False`` depending on whether the callable has completed execution
* ``join``: blocks until the ``callable`` completes execution
.. code-block:: python
import hiro
import time
def _slow_function(n):
time.sleep(n)
if n > 10:
raise RuntimeError()
return n
runner = hiro.run_sync(10, _slow_function, 10)
runner.get_response()
# OUT: 10
# due to the scale factor 10 it only took 1s to execute
runner.get_execution_time()
# OUT: 1.1052658557891846
runner = hiro.run_async(10, _slow_function, 11)
runner.is_running()
# OUT: True
runner.join()
runner.get_execution_time()
# OUT: 1.1052658557891846
runner.get_response()
# OUT: Traceback (most recent call last):
# ....
# OUT: File "", line 4, in _slow_function
# OUT: RuntimeError
hiro-0.5/doc/source/project.rst 0000664 0000000 0000000 00000002046 13135751141 0016567 0 ustar 00root root 0000000 0000000 *****************
Project resources
*****************
.. only:: html
- Source : `Github`_
- Continuous Integration: |travis|
- Test coverage: |coveralls|
- PyPi: |crate| / `pypi `_
.. only:: latex
- Source : `Github`_
- Continuous Integration: `Travis-CI `_
- Test coverage: `Coveralls `_
- PyPi: `pypi `_
.. _Github: http://github.com/alisaifee/hiro
.. |travis| image:: https://travis-ci.org/alisaifee/hiro.png?branch=master
:target: https://travis-ci.org/alisaifee/hiro
:alt: Travis-CI
.. |coveralls| image:: https://coveralls.io/repos/alisaifee/hiro/badge.png?branch=master
:target: https://coveralls.io/r/alisaifee/hiro?branch=master
:alt: Coveralls
.. |crate| image:: https://pypip.in/v/hiro/badge.png
:target: https://crate.io/packages/hiro/
:alt: pypi
.. note::
Hiro is tested on pythons version 2.6, 2.7, 3.2, 3.3, 3.4 and pypy >= 2.1
hiro-0.5/hiro/ 0000775 0000000 0000000 00000000000 13135751141 0013261 5 ustar 00root root 0000000 0000000 hiro-0.5/hiro/__init__.py 0000664 0000000 0000000 00000000243 13135751141 0015371 0 ustar 00root root 0000000 0000000 """
time manipulation utilities for python
"""
from .core import run_async, run_sync
from .core import Timeline
__all__ = ["run_async", "run_sync", "Timeline"]
hiro-0.5/hiro/core.py 0000664 0000000 0000000 00000034445 13135751141 0014575 0 ustar 00root root 0000000 0000000 """
timeline & runner implementation
"""
import copy
import inspect
import sys
import threading
import time
import datetime
from six import reraise
import mock
from functools import wraps
from .errors import SegmentNotComplete, TimeOutofBounds
from .utils import timedelta_to_seconds, chained, time_in_seconds
from .patches import Date, Datetime
BLACKLIST = set()
_NO_EXCEPTION = (None, None, None)
class Decorator(object):
def __call__(self, fn):
@wraps(fn)
def inner(*args, **kw):
self.__enter__()
exc = _NO_EXCEPTION
try:
if "timeline" in inspect.getargspec(fn).args:
result = fn(*args, timeline=self, **kw)
else:
result = fn(*args, **kw)
except Exception:
exc = sys.exc_info()
catch = self.__exit__(*exc)
if not catch and exc is not _NO_EXCEPTION:
reraise(*exc)
return result
return inner
class Segment(object):
"""
utility class to manager execution result and timings
for :class:`SyncRunner
"""
def __init__(self):
self.__error = False
self.__response = None
self.__start = time.time()
self.__end = None
def complete(self, response):
"""
called upon successful completion of the segment
"""
self.__response = response
def complete_with_error(self, exception):
"""
called if the segment errored during execution
"""
self.__error = exception
@property
def complete_time(self):
"""
returns the completion time
"""
return self.__end
@complete_time.setter
def complete_time(self, completion_time):
"""
sets the completion time
"""
self.__end = completion_time
@property
def start_time(self):
"""
returns the start time
"""
return self.__start
@start_time.setter
def start_time(self, start_time):
"""
sets the start time
"""
self.__start = start_time
@property
def runtime(self):
"""
returns the total execution time of the segment
"""
if self.__end:
return self.complete_time - self.start_time
else:
raise SegmentNotComplete
@property
def response(self):
"""
returns the return value captured in the segment or raises
the :exception:`exceptions.Exception` that was caught.
"""
if not self.__end:
raise SegmentNotComplete
else:
if self.__error:
reraise(self.__error[0], self.__error[1], self.__error[2])
return self.__response
class Timeline(Decorator):
"""
Timeline context manager. Within this context
the builtins :func:`time.time`, :func:`time.sleep`,
:meth:`datetime.datetime.now`, :meth:`datetime.date.today`,
:meth:`datetime.datetime.utcnow` and :func:`time.gmtime`
respect the alterations made to the timeline.
The class can be used either as a context manager or a decorator.
The following are all valid ways to use it.
.. code-block:: python
with Timeline(scale=10, start=datetime.datetime(2012,12,12)):
....
fast_timeline = Timeline(scale=10).forward(120)
with fast_timeline as timeline:
....
delta = datetime.date(2015,1,1) - datetime.date.today()
future_frozen_timeline = Timeline(scale=10000).freeze().forward(delta)
with future_frozen_timeline as timeline:
...
@Timeline(scale=100)
def slow():
time.sleep(120)
:param float scale: > 1 time will go faster and < 1 it will be slowed down.
:param start: if specified starts the timeline at the given value (either a
floating point representing seconds since epoch or a
:class:`datetime.datetime` object)
"""
class_mappings = {
"date": (datetime.date, Date),
"datetime": (datetime.datetime, Datetime)
}
def __init__(self, scale=1, start=None):
self.reference = time.time()
self.offset = time_in_seconds(start) - self.reference if start else 0.0
self.freeze_point = self.freeze_at = None
self.patchers = []
self.mock_mappings = {
"datetime.date": (datetime.date, Date),
"datetime.datetime": (datetime.datetime, Datetime),
"time.time": (time.time, self.__time_time),
"time.sleep": (time.sleep, self.__time_sleep),
"time.gmtime": (time.gmtime, self.__time_gmtime)
}
self.func_mappings = {
"time": (time.time, self.__time_time),
"sleep": (time.sleep, self.__time_sleep),
"gmtime": (time.gmtime, self.__time_gmtime)
}
self.factor = scale
def _get_original(self, fn_or_mod):
"""
returns the original moduel or function
"""
if fn_or_mod in self.mock_mappings:
return self.mock_mappings[fn_or_mod][0]
elif fn_or_mod in self.func_mappings:
return self.func_mappings[fn_or_mod][0]
else:
return self.class_mappings[fn_or_mod][0]
def _get_fake(self, fn_or_mod):
"""
returns the mocked/patched module or function
"""
if fn_or_mod in self.mock_mappings:
return self.mock_mappings[fn_or_mod][1]
elif fn_or_mod in self.func_mappings:
return self.func_mappings[fn_or_mod][1]
else:
return self.class_mappings[fn_or_mod][1]
def __compute_time(self, freeze_point, offset):
"""
computes the current_time after accounting for
any adjustments due to :attr:`factor` or invocations
of :meth:`freeze`, :meth:`rewind` or :meth:`forward`
"""
if not freeze_point is None:
return offset + freeze_point
else:
delta = self._get_original("time.time")() - self.reference
return self.reference + (delta * self.factor) + offset
def __check_out_of_bounds(self, offset=None, freeze_point=None):
"""
ensures that the time that would be calculated based on any
offset or freeze point would not result in jumping beyond the epoch
"""
next_time = self.__compute_time(freeze_point or self.freeze_point,
offset or self.offset
)
if next_time < 0:
raise TimeOutofBounds(next_time)
def __time_time(self):
"""
patched version of :func:`time.time`
"""
return self.__compute_time(self.freeze_point, self.offset)
def __time_gmtime(self, seconds=None):
"""
patched version of :func:`time.gmtime`
"""
return self._get_original("time.gmtime")(seconds or self.__time_time())
def __time_sleep(self, amount):
"""
patched version of :func:`time.sleep`
"""
self._get_original("time.sleep")(1.0 * amount / self.factor)
@chained
def forward(self, amount):
"""
forwards the timeline by the specified :attr:`amount`
:param amount: either an integer representing seconds or
a :class:`datetime.timedelta` object
"""
offset = self.offset
if isinstance(amount, datetime.timedelta):
offset += timedelta_to_seconds(amount)
else:
offset += amount
self.__check_out_of_bounds(offset=offset)
self.offset = offset
@chained
def rewind(self, amount):
"""
rewinds the timeline by the specified :attr:`amount`
:param amount: either an integer representing seconds or
a :class:`datetime.timedelta` object
"""
offset = self.offset
if isinstance(amount, datetime.timedelta):
offset -= timedelta_to_seconds(amount)
else:
offset -= amount
self.__check_out_of_bounds(offset=offset)
self.offset = offset
@chained
def freeze(self, target_time=None):
"""
freezes the timeline
:param target_time: the time to freeze at as either a float representing
seconds since the epoch or a :class:`datetime.datetime` object.
If not provided time will be frozen at the current time of the
enclosing :class:`Timeline`
"""
if target_time is None:
freeze_point = self._get_fake("time.time")()
else:
freeze_point = time_in_seconds(target_time)
self.__check_out_of_bounds(freeze_point=freeze_point)
self.freeze_point = freeze_point
self.offset = 0
@chained
def unfreeze(self):
"""
if a call to :meth:`freeze` was previously made, the timeline will be
unfrozen to the point which :meth:`freeze` was invoked.
.. warning::
Since unfreezing will reset the timeline back to the point in
when the :meth:`freeze` was invoked - the effect of previous
invocations of :meth:`forward` and :meth:`rewind` will
be lost. This is by design so that freeze/unfreeze can be used as
a checkpoint mechanism.
"""
if self.freeze_point is not None:
self.reference = self._get_original("time.time")()
self.offset = time_in_seconds(self.freeze_point) - self.reference
self.freeze_point = None
@chained
def scale(self, factor):
"""
changes the speed at which time elapses and how long sleeps last for.
:param float factor: > 1 time will go faster and < 1 it will be slowed
down.
"""
self.factor = factor
self.reference = self._get_original("time.time")()
@chained
def reset(self):
"""
resets the current timeline to the actual time now
with a scale factor 1
"""
self.factor = 1
self.freeze_point = None
self.reference = self._get_original("time.time")()
self.offset = 0
def __enter__(self):
for name in list(sys.modules.keys()):
module = sys.modules[name]
if module in BLACKLIST:
continue
mappings = copy.copy(self.class_mappings)
mappings.update(self.func_mappings)
for obj in mappings:
try:
if obj in dir(module) and getattr(module,
obj) == self._get_original(
obj):
path = "%s.%s" % (name, obj)
if not path in self.mock_mappings:
patcher = mock.patch(path, self._get_fake(obj))
patcher.start()
self.patchers.append(patcher)
# this is done for cases where invalid modules are on
# sys modules.
# pylint: disable=bare-except
except:
BLACKLIST.add(module)
for time_obj in self.mock_mappings:
patcher = mock.patch(time_obj, self._get_fake(time_obj))
patcher.start()
self.patchers.append(patcher)
return self
def __exit__(self, exc_type, exc_value, traceback):
for patcher in self.patchers:
patcher.stop()
self.patchers = []
class ScaledRunner(object):
"""
manages the execution of a callable within a :class:`hiro.Timeline`
context.
"""
def __init__(self, factor, func, *args, **kwargs):
self.func = func
self.func_args = args
self.func_kwargs = kwargs
self.segment = Segment()
self.factor = factor
self.__call__()
def _run(self):
"""
managed execution of :attr:`func`
"""
self.segment.start_time = time.time()
with Timeline(scale=self.factor):
try:
self.segment.complete(
self.func(*self.func_args, **self.func_kwargs))
# will be rethrown
# pylint: disable=bare-except
except:
self.segment.complete_with_error(sys.exc_info())
self.segment.complete_time = time.time()
def __call__(self):
self._run()
return self
def get_response(self):
"""
:returns: the return value from :attr:`func`
:raises: Exception if the :attr:`func` raised one during execution
"""
return self.segment.response
def get_execution_time(self):
"""
:returns: the real execution time of :attr:`func` in seconds
"""
return self.segment.runtime
class ScaledAsyncRunner(ScaledRunner):
"""
manages the asynchronous execution of a callable within a
:class:`hiro.Timeline` context.
"""
def __init__(self, *args, **kwargs):
self.thread_runner = threading.Thread(target=self._run)
super(ScaledAsyncRunner, self).__init__(*args, **kwargs)
def __call__(self):
self.thread_runner.start()
return self
def is_running(self):
"""
:rtype bool: whether the :attr:`func` is still running or not.
"""
return self.thread_runner.is_alive()
def join(self):
"""
waits for the :attr:`func` to complete execution.
"""
return self.thread_runner.join()
def run_sync(factor, func, *args, **kwargs):
"""
Executes a callable within a :class:`hiro.Timeline`
:param int factor: scale factor to use for the timeline during execution
:param function func: the function to invoke
:param args: the arguments to pass to the function
:param kwargs: the keyword arguments to pass to the function
:returns: an instance of :class:`hiro.core.ScaledRunner`
"""
return ScaledRunner(factor, func, *args, **kwargs)
def run_async(factor, func, *args, **kwargs):
"""
Asynchronously executes a callable within a :class:`hiro.Timeline`
:param int factor: scale factor to use for the timeline during execution
:param function func: the function to invoke
:param args: the arguments to pass to the function
:param kwargs: the keyword arguments to pass to the function
:returns: an instance of :class:`hiro.core.ScaledAsyncRunner`
"""
return ScaledAsyncRunner(factor, func, *args, **kwargs)
hiro-0.5/hiro/errors.py 0000664 0000000 0000000 00000001576 13135751141 0015160 0 ustar 00root root 0000000 0000000 """
exceptions used by hiro.
"""
class SegmentNotComplete(Exception):
"""
used to raise an exception if an async segment hasn't completed yet
"""
pass
class TimeOutofBounds(AttributeError):
"""
used to raise an exception when time is rewound beyond the epoch
"""
def __init__(self, oob_time):
message = ("you've frozen time at a point before the epoch (%d)."
"hiro only supports going back to 1970/01/01 07:30:00" % oob_time)
super(TimeOutofBounds, self).__init__(message)
class InvalidTypeError(TypeError):
"""
used to raise an exception when an invalid type is provided
for type operations
"""
def __init__(self, value):
message = ("%s provided when only float, int, datetime, or date objects"
"are supported" % type(value))
super(InvalidTypeError, self).__init__(message)
hiro-0.5/hiro/patches.py 0000664 0000000 0000000 00000002621 13135751141 0015263 0 ustar 00root root 0000000 0000000 """
patched builtin time classes for use by :class:`hiro.Timeline`
"""
import abc
from datetime import date as realdate
from datetime import datetime as realdatetime
import time
import six
class DatetimeMeta(abc.ABCMeta):
"""
meta class to allow interaction between :class:`datetime.datetime`
objects create inside the :class:`hiro.Timeline` with those created
outside it.
"""
def __instancecheck__(cls, instance):
return isinstance(instance, realdatetime)
class DateMeta(type):
"""
meta class to allow interaction between :class:`datetime.date`
objects create inside the :class:`hiro.Timeline` with those created
outside it.
"""
def __instancecheck__(cls, instance):
return isinstance(instance, realdate)
@six.add_metaclass(DatetimeMeta)
class Datetime(realdatetime):
"""
used to patch :class:`datetime.datetime` to follow the rules of the
parent :class:`hiro.Timeline`
"""
@classmethod
def now(cls, tz=None):
return cls.fromtimestamp(time.time(), tz)
@classmethod
def utcnow(cls):
return cls.utcfromtimestamp(time.time())
@six.add_metaclass(DateMeta)
class Date(realdate):
"""
used to patch :class:`datetime.date` to follow the rules of the
parent :class:`hiro.Timeline`
"""
__metaclass__ = DateMeta
@classmethod
def today(cls):
return cls.fromtimestamp(time.time())
hiro-0.5/hiro/utils.py 0000664 0000000 0000000 00000002103 13135751141 0014767 0 ustar 00root root 0000000 0000000 """
random utility functions
"""
import calendar
import datetime
import functools
from .errors import InvalidTypeError
def timedelta_to_seconds(delta):
"""
converts a timedelta object to seconds
"""
seconds = delta.microseconds
seconds += (delta.seconds + delta.days * 24 * 3600) * 10 ** 6
return float(seconds) / 10 ** 6
def time_in_seconds(value):
"""
normalized either a datetime.date, datetime.datetime or float
to a float
"""
if isinstance(value, (float, int)):
return value
elif isinstance(value, (datetime.date, datetime.datetime)):
return calendar.timegm(value.timetuple())
else:
raise InvalidTypeError(value)
#adopted from: http://www.snip2code.com/Snippet/2535/Fluent-interface-decorators
def chained(method):
"""
Method decorator to allow chaining.
"""
@functools.wraps(method)
def wrapper(self, *args, **kwargs):
"""
fluent wrapper
"""
result = method(self, *args, **kwargs)
return self if result is None else result
return wrapper
hiro-0.5/hiro/version.py 0000664 0000000 0000000 00000000076 13135751141 0015323 0 ustar 00root root 0000000 0000000 """
module version
"""
__version__ = "0.5" # pragma: no cover
hiro-0.5/requirements/ 0000775 0000000 0000000 00000000000 13135751141 0015043 5 ustar 00root root 0000000 0000000 hiro-0.5/requirements/ci.txt 0000664 0000000 0000000 00000000035 13135751141 0016175 0 ustar 00root root 0000000 0000000 -r test.txt
python-coveralls
hiro-0.5/requirements/main.txt 0000664 0000000 0000000 00000000021 13135751141 0016521 0 ustar 00root root 0000000 0000000 six>=1.4.1
mock
hiro-0.5/requirements/test.txt 0000664 0000000 0000000 00000000032 13135751141 0016556 0 ustar 00root root 0000000 0000000 -r main.txt
nose
nose-cov
hiro-0.5/setup.py 0000775 0000000 0000000 00000001646 13135751141 0014044 0 ustar 00root root 0000000 0000000 """
setup.py for hiro
"""
__author__ = "Ali-Akber Saifee"
__email__ = "ali@indydevs.org.com"
__copyright__ = "Copyright 2013, Ali-Akber Saifee"
from setuptools import setup
import os
import sys
import re
this_dir = os.path.abspath(os.path.dirname(__file__))
REQUIREMENTS = filter(None, open(os.path.join(this_dir, 'requirements/main.txt')).read().splitlines())
extra = {}
version = re.compile("__version__\s*=\s*\"(.*?)\"").findall(open("hiro/version.py").read())[0]
setup(
name='hiro',
author = __author__,
author_email = __email__,
license = "MIT",
url="http://hiro.readthedocs.org",
zip_safe = False,
include_package_data = True,
version = version,
install_requires = REQUIREMENTS,
classifiers=[k for k in open('CLASSIFIERS').read().split('\n') if k],
description='time manipulation utilities for python',
long_description=open('README.rst').read(),
packages = ["hiro"]
)
hiro-0.5/tag.sh 0000775 0000000 0000000 00000000714 13135751141 0013434 0 ustar 00root root 0000000 0000000 #!/bin/bash
echo current version:$(python -c "import hiro.version;print hiro.version.__version__")
read -p "new version:" new_version
sed -i -e "s/__version__.*/__version__ = \"${new_version}\" # pragma: no cover/g" hiro/version.py
echo "tagging $new_version"
git add hiro/version.py
git commit -m "updating version to ${new_version}"
git tag -s $(python setup.py --version) -m "tagging version ${new_version}"
python setup.py build sdist bdist_egg upload
hiro-0.5/tests/ 0000775 0000000 0000000 00000000000 13135751141 0013462 5 ustar 00root root 0000000 0000000 hiro-0.5/tests/__init__.py 0000664 0000000 0000000 00000000203 13135751141 0015566 0 ustar 00root root 0000000 0000000 # utter bullshit to work around pypy+six on a headless environment
from hiro import Timeline
with Timeline().freeze():
pass
hiro-0.5/tests/emulated_modules/ 0000775 0000000 0000000 00000000000 13135751141 0017012 5 ustar 00root root 0000000 0000000 hiro-0.5/tests/emulated_modules/__init__.py 0000664 0000000 0000000 00000000011 13135751141 0021113 0 ustar 00root root 0000000 0000000 """
"""
hiro-0.5/tests/emulated_modules/sample_1.py 0000664 0000000 0000000 00000000046 13135751141 0021065 0 ustar 00root root 0000000 0000000 """
"""
from . import sub_module_1
hiro-0.5/tests/emulated_modules/sample_2.py 0000664 0000000 0000000 00000000071 13135751141 0021064 0 ustar 00root root 0000000 0000000 import datetime
import time
from . import sub_module_2
hiro-0.5/tests/emulated_modules/sample_3.py 0000664 0000000 0000000 00000000121 13135751141 0021061 0 ustar 00root root 0000000 0000000 import datetime
from time import time, gmtime, sleep
from . import sub_module_3
hiro-0.5/tests/emulated_modules/sub_module_1/ 0000775 0000000 0000000 00000000000 13135751141 0021370 5 ustar 00root root 0000000 0000000 hiro-0.5/tests/emulated_modules/sub_module_1/__init__.py 0000664 0000000 0000000 00000000035 13135751141 0023477 0 ustar 00root root 0000000 0000000 from .sub_sample_1_1 import * hiro-0.5/tests/emulated_modules/sub_module_1/sub_sample_1_1.py 0000664 0000000 0000000 00000000432 13135751141 0024533 0 ustar 00root root 0000000 0000000 """
"""
import datetime
import time
def sub_sample_1_1_now():
return datetime.datetime.now()
def sub_sample_1_1_today():
return datetime.date.today()
def sub_sample_1_1_sleep(seconds):
datetime.time.sleep(seconds)
def sub_sample_1_1_time():
return time.time()
hiro-0.5/tests/emulated_modules/sub_module_2/ 0000775 0000000 0000000 00000000000 13135751141 0021371 5 ustar 00root root 0000000 0000000 hiro-0.5/tests/emulated_modules/sub_module_2/__init__.py 0000664 0000000 0000000 00000000036 13135751141 0023501 0 ustar 00root root 0000000 0000000 from .sub_sample_2_1 import *
hiro-0.5/tests/emulated_modules/sub_module_2/sub_sample_2_1.py 0000664 0000000 0000000 00000000423 13135751141 0024535 0 ustar 00root root 0000000 0000000 """
"""
from datetime import datetime, date
import time
def sub_sample_2_1_now():
return datetime.now()
def sub_sample_2_1_today():
return date.today()
def sub_sample_2_1_sleep(seconds):
time.sleep(seconds)
def sub_sample_2_1_time():
return time.time()
hiro-0.5/tests/emulated_modules/sub_module_3/ 0000775 0000000 0000000 00000000000 13135751141 0021372 5 ustar 00root root 0000000 0000000 hiro-0.5/tests/emulated_modules/sub_module_3/__init__.py 0000664 0000000 0000000 00000000036 13135751141 0023502 0 ustar 00root root 0000000 0000000 from .sub_sample_3_1 import *
hiro-0.5/tests/emulated_modules/sub_module_3/sub_sample_3_1.py 0000664 0000000 0000000 00000000524 13135751141 0024541 0 ustar 00root root 0000000 0000000 """
"""
from datetime import datetime, date
from time import time, sleep, gmtime
def sub_sample_3_1_now():
return datetime.now()
def sub_sample_3_1_today():
return date.today()
def sub_sample_3_1_sleep(seconds):
sleep(seconds)
def sub_sample_3_1_time():
return time()
def sub_sample_3_1_gmtime():
return gmtime()
hiro-0.5/tests/test_context_mgr.py 0000664 0000000 0000000 00000015436 13135751141 0017435 0 ustar 00root root 0000000 0000000 import os
import unittest
import time
from datetime import datetime, date, timedelta
import math
from hiro import Timeline
from hiro.utils import timedelta_to_seconds
from tests.emulated_modules import sample_1, sample_2, sample_3
class TestScaledContext(unittest.TestCase):
def test_accelerate(self):
s = time.time()
with Timeline(100):
time.sleep(10)
self.assertTrue(time.time() - s < 10)
def test_deccelerate(self):
s = time.time()
with Timeline(0.5):
time.sleep(0.25)
self.assertTrue(time.time() - s > 0.25)
def test_check_time(self):
start = time.time()
with Timeline(100):
last = time.time()
for i in range(0, 10):
time.sleep(1)
delta = time.time() - (start + (i+1)*100)
self.assertTrue(delta <= 1.5, delta)
def test_utc(self):
start_local = datetime.now()
start_utc = datetime.utcnow()
with Timeline(100000):
time.sleep(60*60)
self.assertEquals( math.ceil((datetime.now() - datetime.utcnow()).seconds / 60.0 / 60.0),
math.ceil((start_local - start_utc).seconds / 60.0 / 60.0))
time.sleep(60*60*23)
self.assertEquals( (datetime.now() - start_local).days, 1 )
self.assertEquals( (datetime.utcnow() - start_utc).days, 1 )
class TestTimelineContext(unittest.TestCase):
def test_forward(self):
original = date.today()
with Timeline() as timeline:
timeline.forward(86400)
self.assertEquals( (date.today() - original).days, 1)
def test_rewind(self):
original_day = date.today()
original_datetime = datetime.now()
with Timeline() as timeline:
timeline.rewind(86400)
self.assertEquals( (original_day - date.today()).days, 1)
timeline.forward(timedelta(days=1))
self.assertEquals( (original_day - date.today()).days, 0)
timeline.rewind(timedelta(days=1))
amount = (original_datetime - datetime.now())
amount_seconds = timedelta_to_seconds(amount)
self.assertTrue( int(amount_seconds) >= 86399, amount_seconds)
def test_freeze(self):
with Timeline() as timeline:
timeline.freeze()
originals = sample_1.sub_module_1.sub_sample_1_1_time(), sample_1.sub_module_1.sub_sample_1_1_now()
time.sleep(1)
self.assertEquals(time.time(), originals[0])
self.assertEquals(datetime.now(), originals[1])
with Timeline() as timeline:
timeline.freeze()
originals = sample_2.sub_module_2.sub_sample_2_1_time(), sample_2.sub_module_2.sub_sample_2_1_now()
time.sleep(1)
self.assertEquals(time.time(), originals[0])
self.assertEquals(datetime.now(), originals[1])
with Timeline() as timeline:
timeline.freeze()
originals = sample_3.sub_module_3.sub_sample_3_1_time(), sample_3.sub_module_3.sub_sample_3_1_now()
time.sleep(1)
self.assertEquals(time.time(), originals[0])
self.assertEquals(datetime.now(), originals[1])
def test_freeze_target(self):
with Timeline() as timeline:
timeline.freeze( datetime.fromtimestamp(0) )
self.assertAlmostEquals(time.time(), 0, 1)
with Timeline() as timeline:
timeline.freeze( 0 )
self.assertAlmostEquals(time.time(), 0, 1)
with Timeline() as timeline:
self.assertRaises(TypeError, timeline.freeze, "now")
with Timeline() as timeline:
self.assertRaises(AttributeError, timeline.freeze, -1)
self.assertRaises(AttributeError, timeline.freeze, date(1969,1,1))
def test_unfreeze(self):
real_day = date.today()
with Timeline() as timeline:
timeline.freeze(0)
self.assertAlmostEquals(time.time(), 0)
timeline.unfreeze()
timeline.scale(10)
time.sleep(1)
self.assertEqual(int(time.time()), 1)
timeline.forward(timedelta(minutes=2))
self.assertEqual(int(time.time()), 121)
timeline.reset()
self.assertTrue(int(time.time()) - int(os.popen("date +%s").read().strip()) <= 1)
timeline.forward(timedelta(days=1))
self.assertTrue((date.today() - real_day).days == 1)
def test_freeze_forward_unfreeze(self):
# start at 2012/12/12 0:0:0
test_timeline = Timeline(scale=100, start=datetime(2012,12,12,0,0,0))
# jump forward an hour and freeze
with test_timeline.forward(60*60).freeze():
self.assertAlmostEqual((datetime.now() - datetime(2012,12,12,1,0,0)).seconds, 0)
# sleep 10 seconds
time.sleep(10)
# assert no changes
self.assertAlmostEqual((datetime.now() - datetime(2012,12,12,1,0,0)).seconds, 0)
# unfreeze timeline
test_timeline.unfreeze()
# ensure unfreeze was at the original freeze point
self.assertAlmostEqual((datetime.now() - datetime(2012,12,12,1,0,0)).seconds, 0)
# sleep 10 seconds
time.sleep(10)
# ensure post unfreeze, time moves
self.assertAlmostEqual((datetime.now() - datetime(2012,12,12,1,0,0)).seconds, 10)
# ensure post unfreeze, forward operations work
test_timeline.forward(timedelta(hours=2))
self.assertAlmostEqual(int(timedelta_to_seconds(datetime.now() - datetime(2012,12,12,1,0,0))), 60*60*2 + 10)
# reset everything
test_timeline.reset()
self.assertEquals(int(time.time()), int(os.popen("date +%s").read().strip()))
def test_fluent(self):
start = datetime.now()
with Timeline().scale(10).forward(120):
self.assertTrue(int(timedelta_to_seconds(datetime.now() - start)) >= 120)
time.sleep(10)
self.assertTrue(int(timedelta_to_seconds(datetime.now() - start)) >= 130)
self.assertTrue((datetime.now() - start).seconds < 10)
def test_decorated(self):
start = datetime(2013,1,1,0,0,0)
real_start = datetime.now()
@Timeline(scale=10, start=start)
def _decorated():
time.sleep(10)
self.assertTrue(int(timedelta_to_seconds(datetime.now() - start)) >= 10)
_decorated()
self.assertTrue(((datetime.now() - real_start).seconds < 10))
def test_decorated_with_argument(self):
@Timeline()
def _decorated(timeline):
self.assertTrue(isinstance(timeline, Timeline))
_decorated()
@Timeline()
def _decorated(timeline=None):
self.assertTrue(isinstance(timeline, Timeline))
_decorated()
hiro-0.5/tests/test_runners.py 0000664 0000000 0000000 00000003323 13135751141 0016570 0 ustar 00root root 0000000 0000000 """
"""
import unittest
import time
import hiro
from hiro.errors import SegmentNotComplete
class TestSyncRunner(unittest.TestCase):
def test_scale_up_runner(self):
def _slow_func():
time.sleep(1)
return 1
f = hiro.run_sync(4, _slow_func)
self.assertTrue(f.get_execution_time() < 1)
self.assertEquals(f.get_response(), 1)
def test_scale_up_runner_fail(self):
def _slow_func():
time.sleep(1)
raise Exception("foo")
f = hiro.run_sync(4, _slow_func)
self.assertRaises(Exception, f.get_response)
self.assertTrue(f.get_execution_time() < 1)
class TestASyncRunner(unittest.TestCase):
def test_scale_up_runner(self):
def _slow_func():
time.sleep(1)
f = hiro.run_async(4, _slow_func)
self.assertTrue(f.is_running())
f.join()
self.assertTrue(f.get_execution_time() < 1)
def test_scale_up_runner_fail(self):
def _slow_func():
time.sleep(1)
raise Exception("foo")
f = hiro.run_async(4, _slow_func)
self.assertTrue(f.is_running())
f.join()
self.assertRaises(Exception, f.get_response)
self.assertTrue(f.get_execution_time() < 1)
def test_segment_not_complete_error(self):
def _slow_func():
time.sleep(1)
raise Exception("foo")
f = hiro.run_async(4, _slow_func)
self.assertRaises(SegmentNotComplete, f.get_execution_time)
self.assertRaises(SegmentNotComplete, f.get_response)
self.assertTrue(f.is_running())
f.join()
self.assertRaises(Exception, f.get_response)
self.assertTrue(f.get_execution_time() < 1 )
hiro-0.5/tests/test_utils.py 0000664 0000000 0000000 00000003105 13135751141 0016232 0 ustar 00root root 0000000 0000000 import datetime
import unittest
import hiro
from hiro.errors import InvalidTypeError
from hiro.utils import timedelta_to_seconds, time_in_seconds, chained
class TestTimeDeltaToSeconds(unittest.TestCase):
def test_fractional(self):
delta = datetime.timedelta(seconds=1, microseconds=1000)
self.assertAlmostEqual(timedelta_to_seconds(delta), 1.001)
def test_days(self):
delta = datetime.timedelta(days=10)
self.assertEqual(timedelta_to_seconds(delta),
delta.days * 24 * 60 * 60)
class TestTimeInSeconds(unittest.TestCase):
def test_passthrough(self):
self.assertEqual(time_in_seconds(1), 1)
self.assertEqual(time_in_seconds(1.0), 1.0)
def test_date(self):
d = datetime.date(1970, 1, 1)
self.assertEqual(time_in_seconds(d), 0)
def test_datetime(self):
d = datetime.datetime(1970, 1, 1, 0, 0, 0)
self.assertEqual(time_in_seconds(d), 0)
def test_invalid_type(self):
self.assertRaises(
InvalidTypeError, time_in_seconds, "this is a string")
class TestChained(unittest.TestCase):
class Foo(object):
@chained
def return_value(self, value=None):
return value
def setUp(self):
self.obj = self.Foo()
def test_no_return(self):
self.assertTrue(self.obj.return_value() is self.obj)
def test_with_return(self):
o = object()
self.assertTrue(self.obj.return_value(o) is o)
def test_kwargs(self):
o = object()
self.assertTrue(self.obj.return_value(value=o) is o)