Compare commits
18 Commits
nilmdb-0.2
...
nilmdb-1.0
Author | SHA1 | Date | |
---|---|---|---|
1593e181a3 | |||
8e781506de | |||
f6a2c7620a | |||
6c30e5ab2f | |||
810eac4e61 | |||
d9bb3ab7ab | |||
21d0e90bd9 | |||
f071d749ce | |||
d95c354595 | |||
9bcd8183f6 | |||
5c531d8273 | |||
3fe3e2ca95 | |||
f01e781469 | |||
e6180a5a81 | |||
a9d31b46ed | |||
b01f23ed99 | |||
842bf21411 | |||
750d9e3c38 |
@@ -7,4 +7,4 @@
|
|||||||
exclude_lines =
|
exclude_lines =
|
||||||
pragma: no cover
|
pragma: no cover
|
||||||
if 0:
|
if 0:
|
||||||
omit = nilmdb/utils/datetime_tz*
|
omit = nilmdb/utils/datetime_tz*,nilmdb/scripts,nilmdb/_version.py
|
||||||
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
nilmdb/_version.py export-subst
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -18,6 +18,10 @@ nilmdb/server/rbtree.so
|
|||||||
dist/
|
dist/
|
||||||
nilmdb.egg-info/
|
nilmdb.egg-info/
|
||||||
|
|
||||||
|
# This gets generated as needed by setup.py
|
||||||
|
MANIFEST.in
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
timeit*out
|
timeit*out
|
||||||
|
|
||||||
|
250
.pylintrc
Normal file
250
.pylintrc
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
# -*- conf -*-
|
||||||
|
[MASTER]
|
||||||
|
|
||||||
|
# Specify a configuration file.
|
||||||
|
#rcfile=
|
||||||
|
|
||||||
|
# Python code to execute, usually for sys.path manipulation such as
|
||||||
|
# pygtk.require().
|
||||||
|
#init-hook=
|
||||||
|
|
||||||
|
# Profiled execution.
|
||||||
|
profile=no
|
||||||
|
|
||||||
|
# Add files or directories to the blacklist. They should be base names, not
|
||||||
|
# paths.
|
||||||
|
ignore=datetime_tz
|
||||||
|
|
||||||
|
# Pickle collected data for later comparisons.
|
||||||
|
persistent=no
|
||||||
|
|
||||||
|
# List of plugins (as comma separated values of python modules names) to load,
|
||||||
|
# usually to register additional checkers.
|
||||||
|
load-plugins=
|
||||||
|
|
||||||
|
|
||||||
|
[MESSAGES CONTROL]
|
||||||
|
|
||||||
|
# Enable the message, report, category or checker with the given id(s). You can
|
||||||
|
# either give multiple identifier separated by comma (,) or put this option
|
||||||
|
# multiple time.
|
||||||
|
#enable=
|
||||||
|
|
||||||
|
# Disable the message, report, category or checker with the given id(s). You
|
||||||
|
# can either give multiple identifier separated by comma (,) or put this option
|
||||||
|
# multiple time (only on the command line, not in the configuration file where
|
||||||
|
# it should appear only once).
|
||||||
|
disable=C0111,R0903,R0201,R0914,R0912,W0142,W0703,W0702
|
||||||
|
|
||||||
|
|
||||||
|
[REPORTS]
|
||||||
|
|
||||||
|
# Set the output format. Available formats are text, parseable, colorized, msvs
|
||||||
|
# (visual studio) and html
|
||||||
|
output-format=parseable
|
||||||
|
|
||||||
|
# Include message's id in output
|
||||||
|
include-ids=yes
|
||||||
|
|
||||||
|
# Put messages in a separate file for each module / package specified on the
|
||||||
|
# command line instead of printing them on stdout. Reports (if any) will be
|
||||||
|
# written in a file name "pylint_global.[txt|html]".
|
||||||
|
files-output=no
|
||||||
|
|
||||||
|
# Tells whether to display a full report or only the messages
|
||||||
|
reports=yes
|
||||||
|
|
||||||
|
# Python expression which should return a note less than 10 (10 is the highest
|
||||||
|
# note). You have access to the variables errors warning, statement which
|
||||||
|
# respectively contain the number of errors / warnings messages and the total
|
||||||
|
# number of statements analyzed. This is used by the global evaluation report
|
||||||
|
# (RP0004).
|
||||||
|
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
||||||
|
|
||||||
|
# Add a comment according to your evaluation note. This is used by the global
|
||||||
|
# evaluation report (RP0004).
|
||||||
|
comment=no
|
||||||
|
|
||||||
|
|
||||||
|
[SIMILARITIES]
|
||||||
|
|
||||||
|
# Minimum lines number of a similarity.
|
||||||
|
min-similarity-lines=4
|
||||||
|
|
||||||
|
# Ignore comments when computing similarities.
|
||||||
|
ignore-comments=yes
|
||||||
|
|
||||||
|
# Ignore docstrings when computing similarities.
|
||||||
|
ignore-docstrings=yes
|
||||||
|
|
||||||
|
|
||||||
|
[TYPECHECK]
|
||||||
|
|
||||||
|
# Tells whether missing members accessed in mixin class should be ignored. A
|
||||||
|
# mixin class is detected if its name ends with "mixin" (case insensitive).
|
||||||
|
ignore-mixin-members=yes
|
||||||
|
|
||||||
|
# List of classes names for which member attributes should not be checked
|
||||||
|
# (useful for classes with attributes dynamically set).
|
||||||
|
ignored-classes=SQLObject
|
||||||
|
|
||||||
|
# When zope mode is activated, add a predefined set of Zope acquired attributes
|
||||||
|
# to generated-members.
|
||||||
|
zope=no
|
||||||
|
|
||||||
|
# List of members which are set dynamically and missed by pylint inference
|
||||||
|
# system, and so shouldn't trigger E0201 when accessed. Python regular
|
||||||
|
# expressions are accepted.
|
||||||
|
generated-members=REQUEST,acl_users,aq_parent
|
||||||
|
|
||||||
|
|
||||||
|
[FORMAT]
|
||||||
|
|
||||||
|
# Maximum number of characters on a single line.
|
||||||
|
max-line-length=80
|
||||||
|
|
||||||
|
# Maximum number of lines in a module
|
||||||
|
max-module-lines=1000
|
||||||
|
|
||||||
|
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||||
|
# tab).
|
||||||
|
indent-string=' '
|
||||||
|
|
||||||
|
|
||||||
|
[MISCELLANEOUS]
|
||||||
|
|
||||||
|
# List of note tags to take in consideration, separated by a comma.
|
||||||
|
notes=FIXME,XXX,TODO
|
||||||
|
|
||||||
|
|
||||||
|
[VARIABLES]
|
||||||
|
|
||||||
|
# Tells whether we should check for unused import in __init__ files.
|
||||||
|
init-import=no
|
||||||
|
|
||||||
|
# A regular expression matching the beginning of the name of dummy variables
|
||||||
|
# (i.e. not used).
|
||||||
|
dummy-variables-rgx=_|dummy
|
||||||
|
|
||||||
|
# List of additional names supposed to be defined in builtins. Remember that
|
||||||
|
# you should avoid to define new builtins when possible.
|
||||||
|
additional-builtins=
|
||||||
|
|
||||||
|
|
||||||
|
[BASIC]
|
||||||
|
|
||||||
|
# Required attributes for module, separated by a comma
|
||||||
|
required-attributes=
|
||||||
|
|
||||||
|
# List of builtins function names that should not be used, separated by a comma
|
||||||
|
bad-functions=apply,input
|
||||||
|
|
||||||
|
# Regular expression which should only match correct module names
|
||||||
|
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
||||||
|
|
||||||
|
# Regular expression which should only match correct module level names
|
||||||
|
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__)|version)$
|
||||||
|
|
||||||
|
# Regular expression which should only match correct class names
|
||||||
|
class-rgx=[A-Z_][a-zA-Z0-9]+$
|
||||||
|
|
||||||
|
# Regular expression which should only match correct function names
|
||||||
|
function-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||||
|
|
||||||
|
# Regular expression which should only match correct method names
|
||||||
|
method-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||||
|
|
||||||
|
# Regular expression which should only match correct instance attribute names
|
||||||
|
attr-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||||
|
|
||||||
|
# Regular expression which should only match correct argument names
|
||||||
|
argument-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||||
|
|
||||||
|
# Regular expression which should only match correct variable names
|
||||||
|
variable-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||||
|
|
||||||
|
# Regular expression which should only match correct list comprehension /
|
||||||
|
# generator expression variable names
|
||||||
|
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
|
||||||
|
|
||||||
|
# Good variable names which should always be accepted, separated by a comma
|
||||||
|
good-names=i,j,k,ex,Run,_
|
||||||
|
|
||||||
|
# Bad variable names which should always be refused, separated by a comma
|
||||||
|
bad-names=foo,bar,baz,toto,tutu,tata
|
||||||
|
|
||||||
|
# Regular expression which should only match functions or classes name which do
|
||||||
|
# not require a docstring
|
||||||
|
no-docstring-rgx=__.*__
|
||||||
|
|
||||||
|
|
||||||
|
[CLASSES]
|
||||||
|
|
||||||
|
# List of interface methods to ignore, separated by a comma. This is used for
|
||||||
|
# instance to not check methods defines in Zope's Interface base class.
|
||||||
|
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
|
||||||
|
|
||||||
|
# List of method names used to declare (i.e. assign) instance attributes.
|
||||||
|
defining-attr-methods=__init__,__new__,setUp
|
||||||
|
|
||||||
|
# List of valid names for the first argument in a class method.
|
||||||
|
valid-classmethod-first-arg=cls
|
||||||
|
|
||||||
|
|
||||||
|
[DESIGN]
|
||||||
|
|
||||||
|
# Maximum number of arguments for function / method
|
||||||
|
max-args=5
|
||||||
|
|
||||||
|
# Argument names that match this expression will be ignored. Default to name
|
||||||
|
# with leading underscore
|
||||||
|
ignored-argument-names=_.*
|
||||||
|
|
||||||
|
# Maximum number of locals for function / method body
|
||||||
|
max-locals=15
|
||||||
|
|
||||||
|
# Maximum number of return / yield for function / method body
|
||||||
|
max-returns=6
|
||||||
|
|
||||||
|
# Maximum number of branch for function / method body
|
||||||
|
max-branchs=12
|
||||||
|
|
||||||
|
# Maximum number of statements in function / method body
|
||||||
|
max-statements=50
|
||||||
|
|
||||||
|
# Maximum number of parents for a class (see R0901).
|
||||||
|
max-parents=7
|
||||||
|
|
||||||
|
# Maximum number of attributes for a class (see R0902).
|
||||||
|
max-attributes=7
|
||||||
|
|
||||||
|
# Minimum number of public methods for a class (see R0903).
|
||||||
|
min-public-methods=2
|
||||||
|
|
||||||
|
# Maximum number of public methods for a class (see R0904).
|
||||||
|
max-public-methods=20
|
||||||
|
|
||||||
|
|
||||||
|
[IMPORTS]
|
||||||
|
|
||||||
|
# Deprecated modules which should not be used, separated by a comma
|
||||||
|
deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
|
||||||
|
|
||||||
|
# Create a graph of every (i.e. internal and external) dependencies in the
|
||||||
|
# given file (report RP0402 must not be disabled)
|
||||||
|
import-graph=
|
||||||
|
|
||||||
|
# Create a graph of external dependencies in the given file (report RP0402 must
|
||||||
|
# not be disabled)
|
||||||
|
ext-import-graph=
|
||||||
|
|
||||||
|
# Create a graph of internal dependencies in the given file (report RP0402 must
|
||||||
|
# not be disabled)
|
||||||
|
int-import-graph=
|
||||||
|
|
||||||
|
|
||||||
|
[EXCEPTIONS]
|
||||||
|
|
||||||
|
# Exceptions that will emit a warning when being caught. Defaults to
|
||||||
|
# "Exception"
|
||||||
|
overgeneral-exceptions=Exception
|
28
Makefile
28
Makefile
@@ -1,12 +1,36 @@
|
|||||||
|
# By default, run the tests.
|
||||||
all: test
|
all: test
|
||||||
|
|
||||||
|
version:
|
||||||
|
python setup.py version
|
||||||
|
|
||||||
|
build:
|
||||||
|
python setup.py build_ext --inplace
|
||||||
|
|
||||||
|
dist: sdist
|
||||||
|
sdist:
|
||||||
|
python setup.py sdist
|
||||||
|
|
||||||
|
install:
|
||||||
|
python setup.py install
|
||||||
|
|
||||||
|
docs:
|
||||||
|
make -C docs
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
pylint -f parseable nilmdb
|
pylint --rcfile=.pylintrc nilmdb
|
||||||
|
|
||||||
test:
|
test:
|
||||||
python runtests.py
|
python tests/runtests.py
|
||||||
|
|
||||||
clean::
|
clean::
|
||||||
find . -name '*pyc' | xargs rm -f
|
find . -name '*pyc' | xargs rm -f
|
||||||
rm -f .coverage
|
rm -f .coverage
|
||||||
rm -rf tests/*testdb*
|
rm -rf tests/*testdb*
|
||||||
|
rm -rf nilmdb.egg-info/ build/ nilmdb/server/*.so MANIFEST.in
|
||||||
|
make -C docs clean
|
||||||
|
|
||||||
|
gitclean::
|
||||||
|
git clean -dXf
|
||||||
|
|
||||||
|
.PHONY: all build dist sdist install docs lint test clean
|
||||||
|
14
README.txt
14
README.txt
@@ -3,8 +3,20 @@ by Jim Paris <jim@jtan.com>
|
|||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|
||||||
sudo apt-get install python2.7 python-cherrypy3 python-decorator python-nose python-coverage python-setuptools
|
# Runtime and build environments
|
||||||
|
sudo apt-get install python2.7 python2.7-dev python-setuptools cython
|
||||||
|
|
||||||
|
# Base NilmDB dependencies
|
||||||
|
sudo apt-get install python-cherrypy3 python-decorator python-simplejson python-pycurl python-dateutil python-tz
|
||||||
|
|
||||||
|
# Tools for running tests
|
||||||
|
sudo apt-get install python-nose python-coverage
|
||||||
|
|
||||||
Install:
|
Install:
|
||||||
|
|
||||||
python setup.py install
|
python setup.py install
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
nilmdb-server --help
|
||||||
|
nilmtool --help
|
||||||
|
@@ -1,4 +1,8 @@
|
|||||||
"""Main NilmDB import"""
|
"""Main NilmDB import"""
|
||||||
|
|
||||||
from server import NilmDB, Server
|
from nilmdb.server import NilmDB, Server
|
||||||
from client import Client
|
from nilmdb.client import Client
|
||||||
|
|
||||||
|
from nilmdb._version import get_versions
|
||||||
|
__version__ = get_versions()['version']
|
||||||
|
del get_versions
|
||||||
|
197
nilmdb/_version.py
Normal file
197
nilmdb/_version.py
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
|
||||||
|
IN_LONG_VERSION_PY = True
|
||||||
|
# This file helps to compute a version number in source trees obtained from
|
||||||
|
# git-archive tarball (such as those provided by githubs download-from-tag
|
||||||
|
# feature). Distribution tarballs (build by setup.py sdist) and build
|
||||||
|
# directories (produced by setup.py build) will contain a much shorter file
|
||||||
|
# that just contains the computed version number.
|
||||||
|
|
||||||
|
# This file is released into the public domain. Generated by
|
||||||
|
# versioneer-0.7+ (https://github.com/warner/python-versioneer)
|
||||||
|
|
||||||
|
# these strings will be replaced by git during git-archive
|
||||||
|
git_refnames = "$Format:%d$"
|
||||||
|
git_full = "$Format:%H$"
|
||||||
|
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def run_command(args, cwd=None, verbose=False):
|
||||||
|
try:
|
||||||
|
# remember shell=False, so use git.cmd on windows, not just git
|
||||||
|
p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd)
|
||||||
|
except EnvironmentError:
|
||||||
|
e = sys.exc_info()[1]
|
||||||
|
if verbose:
|
||||||
|
print("unable to run %s" % args[0])
|
||||||
|
print(e)
|
||||||
|
return None
|
||||||
|
stdout = p.communicate()[0].strip()
|
||||||
|
if sys.version >= '3':
|
||||||
|
stdout = stdout.decode()
|
||||||
|
if p.returncode != 0:
|
||||||
|
if verbose:
|
||||||
|
print("unable to run %s (error)" % args[0])
|
||||||
|
return None
|
||||||
|
return stdout
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
def get_expanded_variables(versionfile_source):
|
||||||
|
# the code embedded in _version.py can just fetch the value of these
|
||||||
|
# variables. When used from setup.py, we don't want to import
|
||||||
|
# _version.py, so we do it with a regexp instead. This function is not
|
||||||
|
# used from _version.py.
|
||||||
|
variables = {}
|
||||||
|
try:
|
||||||
|
for line in open(versionfile_source,"r").readlines():
|
||||||
|
if line.strip().startswith("git_refnames ="):
|
||||||
|
mo = re.search(r'=\s*"(.*)"', line)
|
||||||
|
if mo:
|
||||||
|
variables["refnames"] = mo.group(1)
|
||||||
|
if line.strip().startswith("git_full ="):
|
||||||
|
mo = re.search(r'=\s*"(.*)"', line)
|
||||||
|
if mo:
|
||||||
|
variables["full"] = mo.group(1)
|
||||||
|
except EnvironmentError:
|
||||||
|
pass
|
||||||
|
return variables
|
||||||
|
|
||||||
|
def versions_from_expanded_variables(variables, tag_prefix, verbose=False):
|
||||||
|
refnames = variables["refnames"].strip()
|
||||||
|
if refnames.startswith("$Format"):
|
||||||
|
if verbose:
|
||||||
|
print("variables are unexpanded, not using")
|
||||||
|
return {} # unexpanded, so not in an unpacked git-archive tarball
|
||||||
|
refs = set([r.strip() for r in refnames.strip("()").split(",")])
|
||||||
|
for ref in list(refs):
|
||||||
|
if not re.search(r'\d', ref):
|
||||||
|
if verbose:
|
||||||
|
print("discarding '%s', no digits" % ref)
|
||||||
|
refs.discard(ref)
|
||||||
|
# Assume all version tags have a digit. git's %d expansion
|
||||||
|
# behaves like git log --decorate=short and strips out the
|
||||||
|
# refs/heads/ and refs/tags/ prefixes that would let us
|
||||||
|
# distinguish between branches and tags. By ignoring refnames
|
||||||
|
# without digits, we filter out many common branch names like
|
||||||
|
# "release" and "stabilization", as well as "HEAD" and "master".
|
||||||
|
if verbose:
|
||||||
|
print("remaining refs: %s" % ",".join(sorted(refs)))
|
||||||
|
for ref in sorted(refs):
|
||||||
|
# sorting will prefer e.g. "2.0" over "2.0rc1"
|
||||||
|
if ref.startswith(tag_prefix):
|
||||||
|
r = ref[len(tag_prefix):]
|
||||||
|
if verbose:
|
||||||
|
print("picking %s" % r)
|
||||||
|
return { "version": r,
|
||||||
|
"full": variables["full"].strip() }
|
||||||
|
# no suitable tags, so we use the full revision id
|
||||||
|
if verbose:
|
||||||
|
print("no suitable tags, using full revision id")
|
||||||
|
return { "version": variables["full"].strip(),
|
||||||
|
"full": variables["full"].strip() }
|
||||||
|
|
||||||
|
def versions_from_vcs(tag_prefix, versionfile_source, verbose=False):
|
||||||
|
# this runs 'git' from the root of the source tree. That either means
|
||||||
|
# someone ran a setup.py command (and this code is in versioneer.py, so
|
||||||
|
# IN_LONG_VERSION_PY=False, thus the containing directory is the root of
|
||||||
|
# the source tree), or someone ran a project-specific entry point (and
|
||||||
|
# this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the
|
||||||
|
# containing directory is somewhere deeper in the source tree). This only
|
||||||
|
# gets called if the git-archive 'subst' variables were *not* expanded,
|
||||||
|
# and _version.py hasn't already been rewritten with a short version
|
||||||
|
# string, meaning we're inside a checked out source tree.
|
||||||
|
|
||||||
|
try:
|
||||||
|
here = os.path.abspath(__file__)
|
||||||
|
except NameError:
|
||||||
|
# some py2exe/bbfreeze/non-CPython implementations don't do __file__
|
||||||
|
return {} # not always correct
|
||||||
|
|
||||||
|
# versionfile_source is the relative path from the top of the source tree
|
||||||
|
# (where the .git directory might live) to this file. Invert this to find
|
||||||
|
# the root from __file__.
|
||||||
|
root = here
|
||||||
|
if IN_LONG_VERSION_PY:
|
||||||
|
for i in range(len(versionfile_source.split("/"))):
|
||||||
|
root = os.path.dirname(root)
|
||||||
|
else:
|
||||||
|
root = os.path.dirname(here)
|
||||||
|
if not os.path.exists(os.path.join(root, ".git")):
|
||||||
|
if verbose:
|
||||||
|
print("no .git in %s" % root)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
GIT = "git"
|
||||||
|
if sys.platform == "win32":
|
||||||
|
GIT = "git.cmd"
|
||||||
|
stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"],
|
||||||
|
cwd=root)
|
||||||
|
if stdout is None:
|
||||||
|
return {}
|
||||||
|
if not stdout.startswith(tag_prefix):
|
||||||
|
if verbose:
|
||||||
|
print("tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix))
|
||||||
|
return {}
|
||||||
|
tag = stdout[len(tag_prefix):]
|
||||||
|
stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
|
||||||
|
if stdout is None:
|
||||||
|
return {}
|
||||||
|
full = stdout.strip()
|
||||||
|
if tag.endswith("-dirty"):
|
||||||
|
full += "-dirty"
|
||||||
|
return {"version": tag, "full": full}
|
||||||
|
|
||||||
|
|
||||||
|
def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False):
|
||||||
|
if IN_LONG_VERSION_PY:
|
||||||
|
# We're running from _version.py. If it's from a source tree
|
||||||
|
# (execute-in-place), we can work upwards to find the root of the
|
||||||
|
# tree, and then check the parent directory for a version string. If
|
||||||
|
# it's in an installed application, there's no hope.
|
||||||
|
try:
|
||||||
|
here = os.path.abspath(__file__)
|
||||||
|
except NameError:
|
||||||
|
# py2exe/bbfreeze/non-CPython don't have __file__
|
||||||
|
return {} # without __file__, we have no hope
|
||||||
|
# versionfile_source is the relative path from the top of the source
|
||||||
|
# tree to _version.py. Invert this to find the root from __file__.
|
||||||
|
root = here
|
||||||
|
for i in range(len(versionfile_source.split("/"))):
|
||||||
|
root = os.path.dirname(root)
|
||||||
|
else:
|
||||||
|
# we're running from versioneer.py, which means we're running from
|
||||||
|
# the setup.py in a source tree. sys.argv[0] is setup.py in the root.
|
||||||
|
here = os.path.abspath(sys.argv[0])
|
||||||
|
root = os.path.dirname(here)
|
||||||
|
|
||||||
|
# Source tarballs conventionally unpack into a directory that includes
|
||||||
|
# both the project name and a version string.
|
||||||
|
dirname = os.path.basename(root)
|
||||||
|
if not dirname.startswith(parentdir_prefix):
|
||||||
|
if verbose:
|
||||||
|
print("guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" %
|
||||||
|
(root, dirname, parentdir_prefix))
|
||||||
|
return None
|
||||||
|
return {"version": dirname[len(parentdir_prefix):], "full": ""}
|
||||||
|
|
||||||
|
tag_prefix = "nilmdb-"
|
||||||
|
parentdir_prefix = "nilmdb-"
|
||||||
|
versionfile_source = "nilmdb/_version.py"
|
||||||
|
|
||||||
|
def get_versions(default={"version": "unknown", "full": ""}, verbose=False):
|
||||||
|
variables = { "refnames": git_refnames, "full": git_full }
|
||||||
|
ver = versions_from_expanded_variables(variables, tag_prefix, verbose)
|
||||||
|
if not ver:
|
||||||
|
ver = versions_from_vcs(tag_prefix, versionfile_source, verbose)
|
||||||
|
if not ver:
|
||||||
|
ver = versions_from_parentdir(parentdir_prefix, versionfile_source,
|
||||||
|
verbose)
|
||||||
|
if not ver:
|
||||||
|
ver = default
|
||||||
|
return ver
|
||||||
|
|
@@ -1,4 +1,4 @@
|
|||||||
"""nilmdb.client"""
|
"""nilmdb.client"""
|
||||||
|
|
||||||
from .client import Client
|
from nilmdb.client.client import Client
|
||||||
from .errors import *
|
from nilmdb.client.errors import ClientError, ServerError, Error
|
||||||
|
@@ -5,26 +5,17 @@
|
|||||||
import nilmdb
|
import nilmdb
|
||||||
import nilmdb.utils
|
import nilmdb.utils
|
||||||
import nilmdb.client.httpclient
|
import nilmdb.client.httpclient
|
||||||
from nilmdb.utils.printf import *
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
import os
|
|
||||||
import simplejson as json
|
import simplejson as json
|
||||||
import itertools
|
|
||||||
|
|
||||||
version = "1.0"
|
|
||||||
|
|
||||||
def float_to_string(f):
|
def float_to_string(f):
|
||||||
# Use repr to maintain full precision in the string output.
|
"""Use repr to maintain full precision in the string output."""
|
||||||
return repr(float(f))
|
return repr(float(f))
|
||||||
|
|
||||||
class Client(object):
|
class Client(object):
|
||||||
"""Main client interface to the Nilm database."""
|
"""Main client interface to the Nilm database."""
|
||||||
|
|
||||||
client_version = version
|
|
||||||
|
|
||||||
def __init__(self, url):
|
def __init__(self, url):
|
||||||
self.http = nilmdb.client.httpclient.HTTPClient(url)
|
self.http = nilmdb.client.httpclient.HTTPClient(url)
|
||||||
|
|
||||||
@@ -151,6 +142,8 @@ class Client(object):
|
|||||||
block_data = ""
|
block_data = ""
|
||||||
block_start = start
|
block_start = start
|
||||||
result = None
|
result = None
|
||||||
|
line = None
|
||||||
|
nextline = None
|
||||||
for (line, nextline) in nilmdb.utils.misc.pairwise(data):
|
for (line, nextline) in nilmdb.utils.misc.pairwise(data):
|
||||||
# If we don't have a starting time, extract it from the first line
|
# If we don't have a starting time, extract it from the first line
|
||||||
if block_start is None:
|
if block_start is None:
|
||||||
|
@@ -2,13 +2,8 @@
|
|||||||
|
|
||||||
import nilmdb
|
import nilmdb
|
||||||
import nilmdb.utils
|
import nilmdb.utils
|
||||||
from nilmdb.utils.printf import *
|
from nilmdb.client.errors import ClientError, ServerError, Error
|
||||||
from nilmdb.client.errors import *
|
|
||||||
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
import os
|
|
||||||
import simplejson as json
|
import simplejson as json
|
||||||
import urlparse
|
import urlparse
|
||||||
import pycurl
|
import pycurl
|
||||||
@@ -42,10 +37,12 @@ class HTTPClient(object):
|
|||||||
code = self.curl.getinfo(pycurl.RESPONSE_CODE)
|
code = self.curl.getinfo(pycurl.RESPONSE_CODE)
|
||||||
if code == 200:
|
if code == 200:
|
||||||
return
|
return
|
||||||
# Default variables for exception
|
# Default variables for exception. We use the entire body as
|
||||||
|
# the default message, in case we can't extract it from a JSON
|
||||||
|
# response.
|
||||||
args = { "url" : self.url,
|
args = { "url" : self.url,
|
||||||
"status" : str(code),
|
"status" : str(code),
|
||||||
"message" : None,
|
"message" : body,
|
||||||
"traceback" : None }
|
"traceback" : None }
|
||||||
try:
|
try:
|
||||||
# Fill with server-provided data if we can
|
# Fill with server-provided data if we can
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
"""nilmdb.cmdline"""
|
"""nilmdb.cmdline"""
|
||||||
|
|
||||||
from .cmdline import Cmdline
|
from nilmdb.cmdline.cmdline import Cmdline
|
||||||
|
@@ -4,21 +4,17 @@ import nilmdb
|
|||||||
from nilmdb.utils.printf import *
|
from nilmdb.utils.printf import *
|
||||||
from nilmdb.utils import datetime_tz
|
from nilmdb.utils import datetime_tz
|
||||||
|
|
||||||
import dateutil.parser
|
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
import argparse
|
import argparse
|
||||||
from argparse import ArgumentDefaultsHelpFormatter as def_form
|
from argparse import ArgumentDefaultsHelpFormatter as def_form
|
||||||
|
|
||||||
version = "1.0"
|
|
||||||
|
|
||||||
# Valid subcommands. Defined in separate files just to break
|
# Valid subcommands. Defined in separate files just to break
|
||||||
# things up -- they're still called with Cmdline as self.
|
# things up -- they're still called with Cmdline as self.
|
||||||
subcommands = [ "info", "create", "list", "metadata", "insert", "extract",
|
subcommands = [ "info", "create", "list", "metadata", "insert", "extract",
|
||||||
"remove", "destroy" ]
|
"remove", "destroy" ]
|
||||||
|
|
||||||
# Import the subcommand modules. Equivalent way of doing this would be
|
# Import the subcommand modules
|
||||||
# from . import info as cmd_info
|
|
||||||
subcmd_mods = {}
|
subcmd_mods = {}
|
||||||
for cmd in subcommands:
|
for cmd in subcommands:
|
||||||
subcmd_mods[cmd] = __import__("nilmdb.cmdline." + cmd, fromlist = [ cmd ])
|
subcmd_mods[cmd] = __import__("nilmdb.cmdline." + cmd, fromlist = [ cmd ])
|
||||||
@@ -30,8 +26,8 @@ class JimArgumentParser(argparse.ArgumentParser):
|
|||||||
|
|
||||||
class Cmdline(object):
|
class Cmdline(object):
|
||||||
|
|
||||||
def __init__(self, argv):
|
def __init__(self, argv = None):
|
||||||
self.argv = argv
|
self.argv = argv or sys.argv[1:]
|
||||||
self.client = None
|
self.client = None
|
||||||
|
|
||||||
def arg_time(self, toparse):
|
def arg_time(self, toparse):
|
||||||
@@ -95,9 +91,6 @@ class Cmdline(object):
|
|||||||
return dt.strftime("%a, %d %b %Y %H:%M:%S.%f %z")
|
return dt.strftime("%a, %d %b %Y %H:%M:%S.%f %z")
|
||||||
|
|
||||||
def parser_setup(self):
|
def parser_setup(self):
|
||||||
version_string = sprintf("nilmtool %s, client library %s",
|
|
||||||
version, nilmdb.Client.client_version)
|
|
||||||
|
|
||||||
self.parser = JimArgumentParser(add_help = False,
|
self.parser = JimArgumentParser(add_help = False,
|
||||||
formatter_class = def_form)
|
formatter_class = def_form)
|
||||||
|
|
||||||
@@ -105,7 +98,7 @@ class Cmdline(object):
|
|||||||
group.add_argument("-h", "--help", action='help',
|
group.add_argument("-h", "--help", action='help',
|
||||||
help='show this help message and exit')
|
help='show this help message and exit')
|
||||||
group.add_argument("-V", "--version", action="version",
|
group.add_argument("-V", "--version", action="version",
|
||||||
version=version_string)
|
version = nilmdb.__version__)
|
||||||
|
|
||||||
group = self.parser.add_argument_group("Server")
|
group = self.parser.add_argument_group("Server")
|
||||||
group.add_argument("-u", "--url", action="store",
|
group.add_argument("-u", "--url", action="store",
|
||||||
|
@@ -1,9 +1,7 @@
|
|||||||
from nilmdb.utils.printf import *
|
from nilmdb.utils.printf import *
|
||||||
import nilmdb
|
import nilmdb
|
||||||
import nilmdb.client
|
import nilmdb.client
|
||||||
import textwrap
|
|
||||||
|
|
||||||
from argparse import ArgumentDefaultsHelpFormatter as def_form
|
|
||||||
from argparse import RawDescriptionHelpFormatter as raw_form
|
from argparse import RawDescriptionHelpFormatter as raw_form
|
||||||
|
|
||||||
def setup(self, sub):
|
def setup(self, sub):
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from nilmdb.utils.printf import *
|
from nilmdb.utils.printf import *
|
||||||
import nilmdb
|
|
||||||
import nilmdb.client
|
import nilmdb.client
|
||||||
import sys
|
|
||||||
|
|
||||||
def setup(self, sub):
|
def setup(self, sub):
|
||||||
cmd = sub.add_parser("extract", help="Extract data",
|
cmd = sub.add_parser("extract", help="Extract data",
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import nilmdb
|
||||||
from nilmdb.utils.printf import *
|
from nilmdb.utils.printf import *
|
||||||
|
|
||||||
from argparse import ArgumentDefaultsHelpFormatter as def_form
|
from argparse import ArgumentDefaultsHelpFormatter as def_form
|
||||||
@@ -13,7 +14,7 @@ def setup(self, sub):
|
|||||||
|
|
||||||
def cmd_info(self):
|
def cmd_info(self):
|
||||||
"""Print info about the server"""
|
"""Print info about the server"""
|
||||||
printf("Client library version: %s\n", self.client.client_version)
|
printf("Client version: %s\n", nilmdb.__version__)
|
||||||
printf("Server version: %s\n", self.client.version())
|
printf("Server version: %s\n", self.client.version())
|
||||||
printf("Server URL: %s\n", self.client.geturl())
|
printf("Server URL: %s\n", self.client.geturl())
|
||||||
printf("Server database path: %s\n", self.client.dbpath())
|
printf("Server database path: %s\n", self.client.dbpath())
|
||||||
|
@@ -53,8 +53,6 @@ def cmd_insert(self):
|
|||||||
if len(streams) != 1:
|
if len(streams) != 1:
|
||||||
self.die("error getting stream info for path %s", self.args.path)
|
self.die("error getting stream info for path %s", self.args.path)
|
||||||
|
|
||||||
layout = streams[0][1]
|
|
||||||
|
|
||||||
if self.args.start and len(self.args.file) != 1:
|
if self.args.start and len(self.args.file) != 1:
|
||||||
self.die("error: --start can only be used with one input file")
|
self.die("error: --start can only be used with one input file")
|
||||||
|
|
||||||
@@ -93,7 +91,7 @@ def cmd_insert(self):
|
|||||||
|
|
||||||
# Insert the data
|
# Insert the data
|
||||||
try:
|
try:
|
||||||
result = self.client.stream_insert(self.args.path, ts)
|
self.client.stream_insert(self.args.path, ts)
|
||||||
except nilmdb.client.Error as e:
|
except nilmdb.client.Error as e:
|
||||||
# TODO: It would be nice to be able to offer better errors
|
# TODO: It would be nice to be able to offer better errors
|
||||||
# here, particularly in the case of overlap, which just shows
|
# here, particularly in the case of overlap, which just shows
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
from nilmdb.utils.printf import *
|
from nilmdb.utils.printf import *
|
||||||
import nilmdb
|
|
||||||
import nilmdb.client
|
|
||||||
|
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import argparse
|
import argparse
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
from nilmdb.utils.printf import *
|
from nilmdb.utils.printf import *
|
||||||
import nilmdb
|
import nilmdb
|
||||||
import nilmdb.client
|
import nilmdb.client
|
||||||
import sys
|
|
||||||
|
|
||||||
def setup(self, sub):
|
def setup(self, sub):
|
||||||
cmd = sub.add_parser("remove", help="Remove data",
|
cmd = sub.add_parser("remove", help="Remove data",
|
||||||
|
1
nilmdb/scripts/__init__.py
Normal file
1
nilmdb/scripts/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Command line scripts
|
81
nilmdb/scripts/nilmdb_server.py
Executable file
81
nilmdb/scripts/nilmdb_server.py
Executable file
@@ -0,0 +1,81 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
import nilmdb.server
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point for the 'nilmdb-server' command line script"""
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description = 'Run the NilmDB server',
|
||||||
|
formatter_class = argparse.ArgumentDefaultsHelpFormatter)
|
||||||
|
|
||||||
|
parser.add_argument("-V", "--version", action="version",
|
||||||
|
version = nilmdb.__version__)
|
||||||
|
|
||||||
|
group = parser.add_argument_group("Standard options")
|
||||||
|
group.add_argument('-a', '--address',
|
||||||
|
help = 'Only listen on the given address',
|
||||||
|
default = '0.0.0.0')
|
||||||
|
group.add_argument('-p', '--port', help = 'Listen on the given port',
|
||||||
|
type = int, default = 12380)
|
||||||
|
group.add_argument('-d', '--database', help = 'Database directory',
|
||||||
|
default = os.path.join(os.getcwd(), "db"))
|
||||||
|
group.add_argument('-q', '--quiet', help = 'Silence output',
|
||||||
|
action = 'store_true')
|
||||||
|
|
||||||
|
group = parser.add_argument_group("Debug options")
|
||||||
|
group.add_argument('-y', '--yappi', help = 'Run under yappi profiler and '
|
||||||
|
'invoke interactive shell afterwards',
|
||||||
|
action = 'store_true')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Create database object
|
||||||
|
db = nilmdb.server.NilmDB(args.database)
|
||||||
|
|
||||||
|
# Configure the server
|
||||||
|
if args.quiet:
|
||||||
|
embedded = True
|
||||||
|
else:
|
||||||
|
embedded = False
|
||||||
|
server = nilmdb.server.Server(db,
|
||||||
|
host = args.address,
|
||||||
|
port = args.port,
|
||||||
|
embedded = embedded)
|
||||||
|
|
||||||
|
# Print info
|
||||||
|
if not args.quiet:
|
||||||
|
print "Database: %s" % (os.path.realpath(args.database))
|
||||||
|
if args.address == '0.0.0.0' or args.address == '::':
|
||||||
|
host = socket.getfqdn()
|
||||||
|
else:
|
||||||
|
host = args.address
|
||||||
|
print "Server URL: http://%s:%d/" % ( host, args.port)
|
||||||
|
print "----"
|
||||||
|
|
||||||
|
# Run it
|
||||||
|
if args.yappi:
|
||||||
|
print "Running in yappi"
|
||||||
|
try:
|
||||||
|
import yappi
|
||||||
|
yappi.start()
|
||||||
|
server.start(blocking = True)
|
||||||
|
finally:
|
||||||
|
yappi.stop()
|
||||||
|
yappi.print_stats(sort_type = yappi.SORTTYPE_TTOT, limit = 50)
|
||||||
|
from IPython import embed
|
||||||
|
embed(header = "Use the yappi object to explore further, "
|
||||||
|
"quit to exit")
|
||||||
|
else:
|
||||||
|
server.start(blocking = True)
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
if not args.quiet:
|
||||||
|
print "Closing database"
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
10
nilmdb/scripts/nilmtool.py
Executable file
10
nilmdb/scripts/nilmtool.py
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
import nilmdb.cmdline
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point for the 'nilmtool' command line script"""
|
||||||
|
nilmdb.cmdline.Cmdline().run()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@@ -1,15 +1,22 @@
|
|||||||
"""nilmdb.server"""
|
"""nilmdb.server"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
# Try to set up pyximport to automatically rebuild Cython modules. If
|
# Try to set up pyximport to automatically rebuild Cython modules. If
|
||||||
# this doesn't work, it's OK, as long as the modules were built externally.
|
# this doesn't work, it's OK, as long as the modules were built externally.
|
||||||
# (e.g. python setup.py build_ext --inplace)
|
# (e.g. python setup.py build_ext --inplace)
|
||||||
try:
|
try:
|
||||||
|
import Cython
|
||||||
|
import distutils.version
|
||||||
|
if (distutils.version.LooseVersion(Cython.__version__) <
|
||||||
|
distutils.version.LooseVersion("0.16")): # pragma: no cover
|
||||||
|
raise ImportError("Cython version too old")
|
||||||
import pyximport
|
import pyximport
|
||||||
pyximport.install()
|
pyximport.install(inplace = True, build_in_temp = False)
|
||||||
import layout
|
except ImportError: # pragma: no cover
|
||||||
except: # pragma: no cover
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
from .nilmdb import NilmDB
|
import nilmdb.server.layout
|
||||||
from .server import Server
|
from nilmdb.server.nilmdb import NilmDB
|
||||||
from .errors import *
|
from nilmdb.server.server import Server
|
||||||
|
from nilmdb.server.errors import NilmDBError, StreamError, OverlapError
|
||||||
|
@@ -8,10 +8,8 @@ import nilmdb
|
|||||||
from nilmdb.utils.printf import *
|
from nilmdb.utils.printf import *
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
import struct
|
import struct
|
||||||
import fnmatch
|
|
||||||
import mmap
|
import mmap
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@@ -91,8 +89,7 @@ class BulkData(object):
|
|||||||
"float32": 'f',
|
"float32": 'f',
|
||||||
"float64": 'd',
|
"float64": 'd',
|
||||||
}
|
}
|
||||||
for n in range(layout.count):
|
struct_fmt += struct_mapping[layout.datatype] * layout.count
|
||||||
struct_fmt += struct_mapping[layout.datatype]
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError("no such layout, or bad data types")
|
raise ValueError("no such layout, or bad data types")
|
||||||
|
|
||||||
@@ -185,12 +182,12 @@ class Table(object):
|
|||||||
packer = struct.Struct(struct_fmt)
|
packer = struct.Struct(struct_fmt)
|
||||||
rows_per_file = max(file_size // packer.size, 1)
|
rows_per_file = max(file_size // packer.size, 1)
|
||||||
|
|
||||||
format = { "rows_per_file": rows_per_file,
|
fmt = { "rows_per_file": rows_per_file,
|
||||||
"files_per_dir": files_per_dir,
|
"files_per_dir": files_per_dir,
|
||||||
"struct_fmt": struct_fmt,
|
"struct_fmt": struct_fmt,
|
||||||
"version": 1 }
|
"version": 1 }
|
||||||
with open(os.path.join(root, "_format"), "wb") as f:
|
with open(os.path.join(root, "_format"), "wb") as f:
|
||||||
pickle.dump(format, f, 2)
|
pickle.dump(fmt, f, 2)
|
||||||
|
|
||||||
# Normal methods
|
# Normal methods
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
@@ -199,15 +196,15 @@ class Table(object):
|
|||||||
|
|
||||||
# Load the format and build packer
|
# Load the format and build packer
|
||||||
with open(os.path.join(self.root, "_format"), "rb") as f:
|
with open(os.path.join(self.root, "_format"), "rb") as f:
|
||||||
format = pickle.load(f)
|
fmt = pickle.load(f)
|
||||||
|
|
||||||
if format["version"] != 1: # pragma: no cover (just future proofing)
|
if fmt["version"] != 1: # pragma: no cover (just future proofing)
|
||||||
raise NotImplementedError("version " + format["version"] +
|
raise NotImplementedError("version " + fmt["version"] +
|
||||||
" bulk data store not supported")
|
" bulk data store not supported")
|
||||||
|
|
||||||
self.rows_per_file = format["rows_per_file"]
|
self.rows_per_file = fmt["rows_per_file"]
|
||||||
self.files_per_dir = format["files_per_dir"]
|
self.files_per_dir = fmt["files_per_dir"]
|
||||||
self.packer = struct.Struct(format["struct_fmt"])
|
self.packer = struct.Struct(fmt["struct_fmt"])
|
||||||
self.file_size = self.packer.size * self.rows_per_file
|
self.file_size = self.packer.size * self.rows_per_file
|
||||||
|
|
||||||
# Find nrows
|
# Find nrows
|
||||||
@@ -278,7 +275,7 @@ class Table(object):
|
|||||||
|
|
||||||
# Cache open files
|
# Cache open files
|
||||||
@nilmdb.utils.lru_cache(size = fd_cache_size,
|
@nilmdb.utils.lru_cache(size = fd_cache_size,
|
||||||
keys = slice(0,3), # exclude newsize
|
keys = slice(0, 3), # exclude newsize
|
||||||
onremove = lambda x: x.close())
|
onremove = lambda x: x.close())
|
||||||
def mmap_open(self, subdir, filename, newsize = None):
|
def mmap_open(self, subdir, filename, newsize = None):
|
||||||
"""Open and map a given 'subdir/filename' (relative to self.root).
|
"""Open and map a given 'subdir/filename' (relative to self.root).
|
||||||
|
@@ -15,11 +15,9 @@ from nilmdb.utils.printf import *
|
|||||||
from nilmdb.server.interval import (Interval, DBInterval,
|
from nilmdb.server.interval import (Interval, DBInterval,
|
||||||
IntervalSet, IntervalError)
|
IntervalSet, IntervalError)
|
||||||
from nilmdb.server import bulkdata
|
from nilmdb.server import bulkdata
|
||||||
from nilmdb.server.errors import *
|
from nilmdb.server.errors import NilmDBError, StreamError, OverlapError
|
||||||
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
import errno
|
import errno
|
||||||
import bisect
|
import bisect
|
||||||
@@ -80,7 +78,10 @@ class NilmDB(object):
|
|||||||
verbose = 0
|
verbose = 0
|
||||||
|
|
||||||
def __init__(self, basepath, sync=True, max_results=None,
|
def __init__(self, basepath, sync=True, max_results=None,
|
||||||
bulkdata_args={}):
|
bulkdata_args=None):
|
||||||
|
if bulkdata_args is None:
|
||||||
|
bulkdata_args = {}
|
||||||
|
|
||||||
# set up path
|
# set up path
|
||||||
self.basepath = os.path.abspath(basepath)
|
self.basepath = os.path.abspath(basepath)
|
||||||
|
|
||||||
@@ -156,7 +157,7 @@ class NilmDB(object):
|
|||||||
iset += DBInterval(start_time, end_time,
|
iset += DBInterval(start_time, end_time,
|
||||||
start_time, end_time,
|
start_time, end_time,
|
||||||
start_pos, end_pos)
|
start_pos, end_pos)
|
||||||
except IntervalError as e: # pragma: no cover
|
except IntervalError: # pragma: no cover
|
||||||
raise NilmDBError("unexpected overlap in ranges table!")
|
raise NilmDBError("unexpected overlap in ranges table!")
|
||||||
|
|
||||||
return iset
|
return iset
|
||||||
|
@@ -5,18 +5,16 @@
|
|||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
import nilmdb
|
import nilmdb
|
||||||
from nilmdb.utils.printf import *
|
from nilmdb.utils.printf import *
|
||||||
from nilmdb.server.errors import *
|
from nilmdb.server.errors import NilmDBError
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
import os
|
import os
|
||||||
import simplejson as json
|
import simplejson as json
|
||||||
import decorator
|
import decorator
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import cherrypy
|
|
||||||
cherrypy.tools.json_out
|
cherrypy.tools.json_out
|
||||||
except: # pragma: no cover
|
except: # pragma: no cover
|
||||||
sys.stderr.write("Cherrypy 3.2+ required\n")
|
sys.stderr.write("Cherrypy 3.2+ required\n")
|
||||||
@@ -26,8 +24,6 @@ class NilmApp(object):
|
|||||||
def __init__(self, db):
|
def __init__(self, db):
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
version = "1.2"
|
|
||||||
|
|
||||||
# Decorators
|
# Decorators
|
||||||
def chunked_response(func):
|
def chunked_response(func):
|
||||||
"""Decorator to enable chunked responses."""
|
"""Decorator to enable chunked responses."""
|
||||||
@@ -57,7 +53,7 @@ def workaround_cp_bug_1200(func, *args, **kwargs): # pragma: no cover
|
|||||||
try:
|
try:
|
||||||
for val in func(*args, **kwargs):
|
for val in func(*args, **kwargs):
|
||||||
yield val
|
yield val
|
||||||
except (LookupError, UnicodeError) as e:
|
except (LookupError, UnicodeError):
|
||||||
raise Exception("bug workaround; real exception is:\n" +
|
raise Exception("bug workaround; real exception is:\n" +
|
||||||
traceback.format_exc())
|
traceback.format_exc())
|
||||||
|
|
||||||
@@ -84,9 +80,8 @@ def exception_to_httperror(*expected):
|
|||||||
class Root(NilmApp):
|
class Root(NilmApp):
|
||||||
"""Root application for NILM database"""
|
"""Root application for NILM database"""
|
||||||
|
|
||||||
def __init__(self, db, version):
|
def __init__(self, db):
|
||||||
super(Root, self).__init__(db)
|
super(Root, self).__init__(db)
|
||||||
self.server_version = version
|
|
||||||
|
|
||||||
# /
|
# /
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@@ -102,7 +97,7 @@ class Root(NilmApp):
|
|||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
def version(self):
|
def version(self):
|
||||||
return self.server_version
|
return nilmdb.__version__
|
||||||
|
|
||||||
# /dbpath
|
# /dbpath
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@@ -247,7 +242,7 @@ class Stream(NilmApp):
|
|||||||
|
|
||||||
# Now do the nilmdb insert, passing it the parser full of data.
|
# Now do the nilmdb insert, passing it the parser full of data.
|
||||||
try:
|
try:
|
||||||
result = self.db.stream_insert(path, start, end, parser.data)
|
self.db.stream_insert(path, start, end, parser.data)
|
||||||
except NilmDBError as e:
|
except NilmDBError as e:
|
||||||
raise cherrypy.HTTPError("400 Bad Request", e.message)
|
raise cherrypy.HTTPError("400 Bad Request", e.message)
|
||||||
|
|
||||||
@@ -309,7 +304,7 @@ class Stream(NilmApp):
|
|||||||
def content(start, end):
|
def content(start, end):
|
||||||
# Note: disable chunked responses to see tracebacks from here.
|
# Note: disable chunked responses to see tracebacks from here.
|
||||||
while True:
|
while True:
|
||||||
(intervals, restart) = self.db.stream_intervals(path,start,end)
|
(intervals, restart) = self.db.stream_intervals(path, start, end)
|
||||||
response = ''.join([ json.dumps(i) + "\n" for i in intervals ])
|
response = ''.join([ json.dumps(i) + "\n" for i in intervals ])
|
||||||
yield response
|
yield response
|
||||||
if restart == 0:
|
if restart == 0:
|
||||||
@@ -387,31 +382,39 @@ class Server(object):
|
|||||||
fast_shutdown = False, # don't wait for clients to disconn.
|
fast_shutdown = False, # don't wait for clients to disconn.
|
||||||
force_traceback = False # include traceback in all errors
|
force_traceback = False # include traceback in all errors
|
||||||
):
|
):
|
||||||
self.version = version
|
# Save server version, just for verification during tests
|
||||||
|
self.version = nilmdb.__version__
|
||||||
|
|
||||||
# Need to wrap DB object in a serializer because we'll call
|
# Need to wrap DB object in a serializer because we'll call
|
||||||
# into it from separate threads.
|
# into it from separate threads.
|
||||||
self.embedded = embedded
|
self.embedded = embedded
|
||||||
self.db = nilmdb.utils.Serializer(db)
|
self.db = nilmdb.utils.Serializer(db)
|
||||||
|
|
||||||
|
# Build up global server configuration
|
||||||
cherrypy.config.update({
|
cherrypy.config.update({
|
||||||
'server.socket_host': host,
|
'server.socket_host': host,
|
||||||
'server.socket_port': port,
|
'server.socket_port': port,
|
||||||
'engine.autoreload_on': False,
|
'engine.autoreload_on': False,
|
||||||
'server.max_request_body_size': 4*1024*1024,
|
'server.max_request_body_size': 4*1024*1024,
|
||||||
'error_page.default': self.json_error_page,
|
|
||||||
})
|
})
|
||||||
if self.embedded:
|
if self.embedded:
|
||||||
cherrypy.config.update({ 'environment': 'embedded' })
|
cherrypy.config.update({ 'environment': 'embedded' })
|
||||||
|
|
||||||
|
# Build up application specific configuration
|
||||||
|
app_config = {}
|
||||||
|
app_config.update({
|
||||||
|
'error_page.default': self.json_error_page,
|
||||||
|
})
|
||||||
|
|
||||||
# Send a permissive Access-Control-Allow-Origin (CORS) header
|
# Send a permissive Access-Control-Allow-Origin (CORS) header
|
||||||
# with all responses so that browsers can send cross-domain
|
# with all responses so that browsers can send cross-domain
|
||||||
# requests to this server.
|
# requests to this server.
|
||||||
cherrypy.config.update({ 'response.headers.Access-Control-Allow-Origin':
|
app_config.update({ 'response.headers.Access-Control-Allow-Origin':
|
||||||
'*' })
|
'*' })
|
||||||
|
|
||||||
# Send tracebacks in error responses. They're hidden by the
|
# Send tracebacks in error responses. They're hidden by the
|
||||||
# error_page function for client errors (code 400-499).
|
# error_page function for client errors (code 400-499).
|
||||||
cherrypy.config.update({ 'request.show_tracebacks' : True })
|
app_config.update({ 'request.show_tracebacks' : True })
|
||||||
self.force_traceback = force_traceback
|
self.force_traceback = force_traceback
|
||||||
|
|
||||||
# Patch CherryPy error handler to never pad out error messages.
|
# Patch CherryPy error handler to never pad out error messages.
|
||||||
@@ -419,11 +422,13 @@ class Server(object):
|
|||||||
# error messages.
|
# error messages.
|
||||||
cherrypy._cperror._ie_friendly_error_sizes = {}
|
cherrypy._cperror._ie_friendly_error_sizes = {}
|
||||||
|
|
||||||
cherrypy.tree.apps = {}
|
# Build up the application and mount it
|
||||||
cherrypy.tree.mount(Root(self.db, self.version), "/")
|
root = Root(self.db)
|
||||||
cherrypy.tree.mount(Stream(self.db), "/stream")
|
root.stream = Stream(self.db)
|
||||||
if stoppable:
|
if stoppable:
|
||||||
cherrypy.tree.mount(Exiter(), "/exit")
|
root.exit = Exiter()
|
||||||
|
cherrypy.tree.apps = {}
|
||||||
|
cherrypy.tree.mount(root, "/", config = { "/" : app_config })
|
||||||
|
|
||||||
# Shutdowns normally wait for clients to disconnect. To speed
|
# Shutdowns normally wait for clients to disconnect. To speed
|
||||||
# up tests, set fast_shutdown = True
|
# up tests, set fast_shutdown = True
|
||||||
@@ -444,7 +449,7 @@ class Server(object):
|
|||||||
if not self.force_traceback:
|
if not self.force_traceback:
|
||||||
if code >= 400 and code <= 499:
|
if code >= 400 and code <= 499:
|
||||||
errordata["traceback"] = ""
|
errordata["traceback"] = ""
|
||||||
except Exception as e: # pragma: no cover
|
except Exception: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
# Override the response type, which was previously set to text/html
|
# Override the response type, which was previously set to text/html
|
||||||
cherrypy.serving.response.headers['Content-Type'] = (
|
cherrypy.serving.response.headers['Content-Type'] = (
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
"""NilmDB utilities"""
|
"""NilmDB utilities"""
|
||||||
|
|
||||||
from .timer import Timer
|
from nilmdb.utils.timer import Timer
|
||||||
from .iteratorizer import Iteratorizer
|
from nilmdb.utils.iteratorizer import Iteratorizer
|
||||||
from .serializer import Serializer
|
from nilmdb.utils.serializer import Serializer
|
||||||
from .lrucache import lru_cache
|
from nilmdb.utils.lrucache import lru_cache
|
||||||
from .diskusage import du
|
from nilmdb.utils.diskusage import du
|
||||||
from .mustclose import must_close
|
from nilmdb.utils.mustclose import must_close
|
||||||
from .urllib import urlencode
|
from nilmdb.utils.urllib import urlencode
|
||||||
from . import misc
|
from nilmdb.utils import misc
|
||||||
from . import atomic
|
from nilmdb.utils import atomic
|
||||||
|
@@ -19,8 +19,8 @@ def du_bytes(path):
|
|||||||
"""Like du -sb, returns total size of path in bytes."""
|
"""Like du -sb, returns total size of path in bytes."""
|
||||||
size = os.path.getsize(path)
|
size = os.path.getsize(path)
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
for file in os.listdir(path):
|
for thisfile in os.listdir(path):
|
||||||
filepath = os.path.join(path, file)
|
filepath = os.path.join(path, thisfile)
|
||||||
size += du_bytes(filepath)
|
size += du_bytes(filepath)
|
||||||
return size
|
return size
|
||||||
|
|
||||||
|
@@ -95,5 +95,5 @@ def Iteratorizer(function, curl_hack = False):
|
|||||||
while thread.isAlive():
|
while thread.isAlive():
|
||||||
try:
|
try:
|
||||||
queue.get(True, 0.01)
|
queue.get(True, 0.01)
|
||||||
except:
|
except: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
|
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
import collections
|
import collections
|
||||||
import decorator
|
import decorator
|
||||||
import warnings
|
|
||||||
|
|
||||||
def lru_cache(size = 10, onremove = None, keys = slice(None)):
|
def lru_cache(size = 10, onremove = None, keys = slice(None)):
|
||||||
"""Least-recently-used cache decorator.
|
"""Least-recently-used cache decorator.
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Simple timer to time a block of code, for optimization debugging
|
# Simple timer to time a block of code, for optimization debugging
|
||||||
# use like:
|
# use like:
|
||||||
# with nilmdb.Timer("flush"):
|
# with nilmdb.utils.Timer("flush"):
|
||||||
# foo.flush()
|
# foo.flush()
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
@@ -3,19 +3,16 @@
|
|||||||
from nilmdb.utils.printf import *
|
from nilmdb.utils.printf import *
|
||||||
from nilmdb.utils import datetime_tz
|
from nilmdb.utils import datetime_tz
|
||||||
|
|
||||||
import time
|
|
||||||
import os
|
|
||||||
|
|
||||||
class Timestamper(object):
|
class Timestamper(object):
|
||||||
"""A file-like object that adds timestamps to lines of an input file."""
|
"""A file-like object that adds timestamps to lines of an input file."""
|
||||||
def __init__(self, file, ts_iter):
|
def __init__(self, infile, ts_iter):
|
||||||
"""file: filename, or another file-like object
|
"""file: filename, or another file-like object
|
||||||
ts_iter: iterator that returns a timestamp string for
|
ts_iter: iterator that returns a timestamp string for
|
||||||
each line of the file"""
|
each line of the file"""
|
||||||
if isinstance(file, basestring):
|
if isinstance(infile, basestring):
|
||||||
self.file = open(file, "r")
|
self.file = open(infile, "r")
|
||||||
else:
|
else:
|
||||||
self.file = file
|
self.file = infile
|
||||||
self.ts_iter = ts_iter
|
self.ts_iter = ts_iter
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
@@ -54,7 +51,7 @@ class Timestamper(object):
|
|||||||
|
|
||||||
class TimestamperRate(Timestamper):
|
class TimestamperRate(Timestamper):
|
||||||
"""Timestamper that uses a start time and a fixed rate"""
|
"""Timestamper that uses a start time and a fixed rate"""
|
||||||
def __init__(self, file, start, rate, end = None):
|
def __init__(self, infile, start, rate, end = None):
|
||||||
"""
|
"""
|
||||||
file: file name or object
|
file: file name or object
|
||||||
|
|
||||||
@@ -76,7 +73,7 @@ class TimestamperRate(Timestamper):
|
|||||||
# Handle case where we're passed a datetime or datetime_tz object
|
# Handle case where we're passed a datetime or datetime_tz object
|
||||||
if "totimestamp" in dir(start):
|
if "totimestamp" in dir(start):
|
||||||
start = start.totimestamp()
|
start = start.totimestamp()
|
||||||
Timestamper.__init__(self, file, iterator(start, rate, end))
|
Timestamper.__init__(self, infile, iterator(start, rate, end))
|
||||||
self.start = start
|
self.start = start
|
||||||
self.rate = rate
|
self.rate = rate
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -87,21 +84,21 @@ class TimestamperRate(Timestamper):
|
|||||||
|
|
||||||
class TimestamperNow(Timestamper):
|
class TimestamperNow(Timestamper):
|
||||||
"""Timestamper that uses current time"""
|
"""Timestamper that uses current time"""
|
||||||
def __init__(self, file):
|
def __init__(self, infile):
|
||||||
def iterator():
|
def iterator():
|
||||||
while True:
|
while True:
|
||||||
now = datetime_tz.datetime_tz.utcnow().totimestamp()
|
now = datetime_tz.datetime_tz.utcnow().totimestamp()
|
||||||
yield sprintf("%.6f ", now)
|
yield sprintf("%.6f ", now)
|
||||||
Timestamper.__init__(self, file, iterator())
|
Timestamper.__init__(self, infile, iterator())
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "TimestamperNow(...)"
|
return "TimestamperNow(...)"
|
||||||
|
|
||||||
class TimestamperNull(Timestamper):
|
class TimestamperNull(Timestamper):
|
||||||
"""Timestamper that adds nothing to each line"""
|
"""Timestamper that adds nothing to each line"""
|
||||||
def __init__(self, file):
|
def __init__(self, infile):
|
||||||
def iterator():
|
def iterator():
|
||||||
while True:
|
while True:
|
||||||
yield ""
|
yield ""
|
||||||
Timestamper.__init__(self, file, iterator())
|
Timestamper.__init__(self, infile, iterator())
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "TimestamperNull(...)"
|
return "TimestamperNull(...)"
|
||||||
|
@@ -1,6 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
import nilmdb
|
|
||||||
import sys
|
|
||||||
|
|
||||||
nilmdb.cmdline.Cmdline(sys.argv[1:]).run()
|
|
35
runserver.py
35
runserver.py
@@ -1,35 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
import nilmdb
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
formatter = argparse.ArgumentDefaultsHelpFormatter
|
|
||||||
parser = argparse.ArgumentParser(description='Run the NILM server',
|
|
||||||
formatter_class = formatter)
|
|
||||||
parser.add_argument('-p', '--port', help='Port number', type=int, default=12380)
|
|
||||||
parser.add_argument('-d', '--database', help='Database directory', default="db")
|
|
||||||
parser.add_argument('-y', '--yappi', help='Run with yappi profiler',
|
|
||||||
action='store_true')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# Start web app on a custom port
|
|
||||||
db = nilmdb.NilmDB(args.database)
|
|
||||||
server = nilmdb.Server(db, host = "127.0.0.1",
|
|
||||||
port = args.port,
|
|
||||||
embedded = False)
|
|
||||||
|
|
||||||
|
|
||||||
if args.yappi:
|
|
||||||
print "Running in yappi"
|
|
||||||
try:
|
|
||||||
import yappi
|
|
||||||
yappi.start()
|
|
||||||
server.start(blocking = True)
|
|
||||||
finally:
|
|
||||||
yappi.stop()
|
|
||||||
print "Try: yappi.print_stats(sort_type=yappi.SORTTYPE_TTOT,limit=50)"
|
|
||||||
from IPython import embed
|
|
||||||
embed()
|
|
||||||
else:
|
|
||||||
server.start(blocking = True)
|
|
||||||
db.close()
|
|
108
setup.py
108
setup.py
@@ -1,5 +1,11 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
# To release a new version, tag it:
|
||||||
|
# git tag -a nilmdb-1.1 -m "Version 1.1"
|
||||||
|
# git push --tags
|
||||||
|
# Then just package it up:
|
||||||
|
# python setup.py sdist
|
||||||
|
|
||||||
# This is supposed to be using Distribute:
|
# This is supposed to be using Distribute:
|
||||||
#
|
#
|
||||||
# distutils provides a "setup" method.
|
# distutils provides a "setup" method.
|
||||||
@@ -9,32 +15,105 @@
|
|||||||
# So we don't really know if this is using the old setuptools or the
|
# So we don't really know if this is using the old setuptools or the
|
||||||
# Distribute-provided version of setuptools.
|
# Distribute-provided version of setuptools.
|
||||||
|
|
||||||
from setuptools import setup, find_packages
|
import traceback
|
||||||
from distutils.extension import Extension
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
from Cython.Build import cythonize
|
try:
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
from distutils.extension import Extension
|
||||||
|
import distutils.version
|
||||||
|
except ImportError:
|
||||||
|
traceback.print_exc()
|
||||||
|
print "Please install the prerequisites listed in README.txt"
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Versioneer manages version numbers from git tags.
|
||||||
|
# https://github.com/warner/python-versioneer
|
||||||
|
import versioneer
|
||||||
|
versioneer.versionfile_source = 'nilmdb/_version.py'
|
||||||
|
versioneer.versionfile_build = 'nilmdb/_version.py'
|
||||||
|
versioneer.tag_prefix = 'nilmdb-'
|
||||||
|
versioneer.parentdir_prefix = 'nilmdb-'
|
||||||
|
|
||||||
# Hack to workaround logging/multiprocessing issue:
|
# Hack to workaround logging/multiprocessing issue:
|
||||||
# https://groups.google.com/d/msg/nose-users/fnJ-kAUbYHQ/_UsLN786ygcJ
|
# https://groups.google.com/d/msg/nose-users/fnJ-kAUbYHQ/_UsLN786ygcJ
|
||||||
try: import multiprocessing
|
try: import multiprocessing
|
||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
# Build cython modules.
|
# Use Cython if it's new enough, otherwise use preexisting C files.
|
||||||
cython_modules = cythonize("**/*.pyx")
|
cython_modules = [ 'nilmdb.server.interval',
|
||||||
|
'nilmdb.server.layout',
|
||||||
|
'nilmdb.server.rbtree' ]
|
||||||
|
try:
|
||||||
|
import Cython
|
||||||
|
from Cython.Build import cythonize
|
||||||
|
if (distutils.version.LooseVersion(Cython.__version__) <
|
||||||
|
distutils.version.LooseVersion("0.16")):
|
||||||
|
print "Cython version", Cython.__version__, "is too old; not using it."
|
||||||
|
raise ImportError()
|
||||||
|
use_cython = True
|
||||||
|
except ImportError:
|
||||||
|
use_cython = False
|
||||||
|
|
||||||
|
ext_modules = []
|
||||||
|
for modulename in cython_modules:
|
||||||
|
filename = modulename.replace('.','/')
|
||||||
|
if use_cython:
|
||||||
|
ext_modules.extend(cythonize(filename + ".pyx"))
|
||||||
|
else:
|
||||||
|
cfile = filename + ".c"
|
||||||
|
if not os.path.exists(cfile):
|
||||||
|
raise Exception("Missing source file " + cfile + ". "
|
||||||
|
"Try installing cython >= 0.16.")
|
||||||
|
ext_modules.append(Extension(modulename, [ cfile ]))
|
||||||
|
|
||||||
|
# We need a MANIFEST.in. Generate it here rather than polluting the
|
||||||
|
# repository with yet another setup-related file.
|
||||||
|
with open("MANIFEST.in", "w") as m:
|
||||||
|
m.write("""
|
||||||
|
# Root
|
||||||
|
include README.txt
|
||||||
|
include setup.cfg
|
||||||
|
include setup.py
|
||||||
|
include versioneer.py
|
||||||
|
include Makefile
|
||||||
|
include .coveragerc
|
||||||
|
include .pylintrc
|
||||||
|
|
||||||
|
# Cython files -- include source.
|
||||||
|
recursive-include nilmdb/server *.pyx *.pyxdep *.pxd
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
recursive-include tests *.py
|
||||||
|
recursive-include tests/data *
|
||||||
|
include tests/test.order
|
||||||
|
|
||||||
|
# Docs
|
||||||
|
recursive-include docs Makefile *.md
|
||||||
|
""")
|
||||||
|
|
||||||
# Run setup
|
# Run setup
|
||||||
setup(name='nilmdb',
|
setup(name='nilmdb',
|
||||||
version = '1.0',
|
version = versioneer.get_version(),
|
||||||
|
cmdclass = versioneer.get_cmdclass(),
|
||||||
url = 'https://git.jim.sh/jim/lees/nilmdb.git',
|
url = 'https://git.jim.sh/jim/lees/nilmdb.git',
|
||||||
author = 'Jim Paris',
|
author = 'Jim Paris',
|
||||||
|
description = "NILM Database",
|
||||||
|
long_description = "NILM Database",
|
||||||
|
license = "Proprietary",
|
||||||
author_email = 'jim@jtan.com',
|
author_email = 'jim@jtan.com',
|
||||||
tests_require = [ 'nose',
|
tests_require = [ 'nose',
|
||||||
'coverage',
|
'coverage',
|
||||||
],
|
],
|
||||||
setup_requires = [ 'cython',
|
setup_requires = [ 'distribute',
|
||||||
],
|
],
|
||||||
install_requires = [ 'distribute',
|
install_requires = [ 'decorator',
|
||||||
'decorator',
|
'cherrypy >= 3.2',
|
||||||
|
'simplejson',
|
||||||
|
'pycurl',
|
||||||
|
'python-dateutil',
|
||||||
|
'pytz',
|
||||||
],
|
],
|
||||||
packages = [ 'nilmdb',
|
packages = [ 'nilmdb',
|
||||||
'nilmdb.utils',
|
'nilmdb.utils',
|
||||||
@@ -42,7 +121,14 @@ setup(name='nilmdb',
|
|||||||
'nilmdb.server',
|
'nilmdb.server',
|
||||||
'nilmdb.client',
|
'nilmdb.client',
|
||||||
'nilmdb.cmdline',
|
'nilmdb.cmdline',
|
||||||
|
'nilmdb.scripts',
|
||||||
],
|
],
|
||||||
ext_modules = cython_modules,
|
entry_points = {
|
||||||
|
'console_scripts': [
|
||||||
|
'nilmtool = nilmdb.scripts.nilmtool:main',
|
||||||
|
'nilmdb-server = nilmdb.scripts.nilmdb_server:main',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
ext_modules = ext_modules,
|
||||||
zip_safe = False,
|
zip_safe = False,
|
||||||
)
|
)
|
||||||
|
@@ -6,6 +6,9 @@ import sys
|
|||||||
import glob
|
import glob
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
# Change into parent dir
|
||||||
|
os.chdir(os.path.dirname(os.path.realpath(__file__)) + "/..")
|
||||||
|
|
||||||
class JimOrderPlugin(nose.plugins.Plugin):
|
class JimOrderPlugin(nose.plugins.Plugin):
|
||||||
"""When searching for tests and encountering a directory that
|
"""When searching for tests and encountering a directory that
|
||||||
contains a 'test.order' file, run tests listed in that file, in the
|
contains a 'test.order' file, run tests listed in that file, in the
|
@@ -67,8 +67,8 @@ class TestClient(object):
|
|||||||
# Now use the real URL
|
# Now use the real URL
|
||||||
client = nilmdb.Client(url = "http://localhost:12380/")
|
client = nilmdb.Client(url = "http://localhost:12380/")
|
||||||
version = client.version()
|
version = client.version()
|
||||||
eq_(distutils.version.StrictVersion(version),
|
eq_(distutils.version.LooseVersion(version),
|
||||||
distutils.version.StrictVersion(test_server.version))
|
distutils.version.LooseVersion(test_server.version))
|
||||||
|
|
||||||
# Bad URLs should give 404, not 500
|
# Bad URLs should give 404, not 500
|
||||||
with assert_raises(ClientError):
|
with assert_raises(ClientError):
|
||||||
|
@@ -151,8 +151,8 @@ class TestServer(object):
|
|||||||
eq_(e.exception.code, 404)
|
eq_(e.exception.code, 404)
|
||||||
|
|
||||||
# Check version
|
# Check version
|
||||||
eq_(distutils.version.StrictVersion(getjson("/version")),
|
eq_(distutils.version.LooseVersion(getjson("/version")),
|
||||||
distutils.version.StrictVersion(self.server.version))
|
distutils.version.LooseVersion(nilmdb.__version__))
|
||||||
|
|
||||||
def test_stream_list(self):
|
def test_stream_list(self):
|
||||||
# Known streams that got populated by an earlier test (test_nilmdb)
|
# Known streams that got populated by an earlier test (test_nilmdb)
|
||||||
|
656
versioneer.py
Normal file
656
versioneer.py
Normal file
@@ -0,0 +1,656 @@
|
|||||||
|
#! /usr/bin/python
|
||||||
|
|
||||||
|
"""versioneer.py
|
||||||
|
|
||||||
|
(like a rocketeer, but for versions)
|
||||||
|
|
||||||
|
* https://github.com/warner/python-versioneer
|
||||||
|
* Brian Warner
|
||||||
|
* License: Public Domain
|
||||||
|
* Version: 0.7+
|
||||||
|
|
||||||
|
This file helps distutils-based projects manage their version number by just
|
||||||
|
creating version-control tags.
|
||||||
|
|
||||||
|
For developers who work from a VCS-generated tree (e.g. 'git clone' etc),
|
||||||
|
each 'setup.py version', 'setup.py build', 'setup.py sdist' will compute a
|
||||||
|
version number by asking your version-control tool about the current
|
||||||
|
checkout. The version number will be written into a generated _version.py
|
||||||
|
file of your choosing, where it can be included by your __init__.py
|
||||||
|
|
||||||
|
For users who work from a VCS-generated tarball (e.g. 'git archive'), it will
|
||||||
|
compute a version number by looking at the name of the directory created when
|
||||||
|
te tarball is unpacked. This conventionally includes both the name of the
|
||||||
|
project and a version number.
|
||||||
|
|
||||||
|
For users who work from a tarball built by 'setup.py sdist', it will get a
|
||||||
|
version number from a previously-generated _version.py file.
|
||||||
|
|
||||||
|
As a result, loading code directly from the source tree will not result in a
|
||||||
|
real version. If you want real versions from VCS trees (where you frequently
|
||||||
|
update from the upstream repository, or do new development), you will need to
|
||||||
|
do a 'setup.py version' after each update, and load code from the build/
|
||||||
|
directory.
|
||||||
|
|
||||||
|
You need to provide this code with a few configuration values:
|
||||||
|
|
||||||
|
versionfile_source:
|
||||||
|
A project-relative pathname into which the generated version strings
|
||||||
|
should be written. This is usually a _version.py next to your project's
|
||||||
|
main __init__.py file. If your project uses src/myproject/__init__.py,
|
||||||
|
this should be 'src/myproject/_version.py'. This file should be checked
|
||||||
|
in to your VCS as usual: the copy created below by 'setup.py
|
||||||
|
update_files' will include code that parses expanded VCS keywords in
|
||||||
|
generated tarballs. The 'build' and 'sdist' commands will replace it with
|
||||||
|
a copy that has just the calculated version string.
|
||||||
|
|
||||||
|
versionfile_build:
|
||||||
|
Like versionfile_source, but relative to the build directory instead of
|
||||||
|
the source directory. These will differ when your setup.py uses
|
||||||
|
'package_dir='. If you have package_dir={'myproject': 'src/myproject'},
|
||||||
|
then you will probably have versionfile_build='myproject/_version.py' and
|
||||||
|
versionfile_source='src/myproject/_version.py'.
|
||||||
|
|
||||||
|
tag_prefix: a string, like 'PROJECTNAME-', which appears at the start of all
|
||||||
|
VCS tags. If your tags look like 'myproject-1.2.0', then you
|
||||||
|
should use tag_prefix='myproject-'. If you use unprefixed tags
|
||||||
|
like '1.2.0', this should be an empty string.
|
||||||
|
|
||||||
|
parentdir_prefix: a string, frequently the same as tag_prefix, which
|
||||||
|
appears at the start of all unpacked tarball filenames. If
|
||||||
|
your tarball unpacks into 'myproject-1.2.0', this should
|
||||||
|
be 'myproject-'.
|
||||||
|
|
||||||
|
To use it:
|
||||||
|
|
||||||
|
1: include this file in the top level of your project
|
||||||
|
2: make the following changes to the top of your setup.py:
|
||||||
|
import versioneer
|
||||||
|
versioneer.versionfile_source = 'src/myproject/_version.py'
|
||||||
|
versioneer.versionfile_build = 'myproject/_version.py'
|
||||||
|
versioneer.tag_prefix = '' # tags are like 1.2.0
|
||||||
|
versioneer.parentdir_prefix = 'myproject-' # dirname like 'myproject-1.2.0'
|
||||||
|
3: add the following arguments to the setup() call in your setup.py:
|
||||||
|
version=versioneer.get_version(),
|
||||||
|
cmdclass=versioneer.get_cmdclass(),
|
||||||
|
4: run 'setup.py update_files', which will create _version.py, and will
|
||||||
|
append the following to your __init__.py:
|
||||||
|
from _version import __version__
|
||||||
|
5: modify your MANIFEST.in to include versioneer.py
|
||||||
|
6: add both versioneer.py and the generated _version.py to your VCS
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os, sys, re
|
||||||
|
from distutils.core import Command
|
||||||
|
from distutils.command.sdist import sdist as _sdist
|
||||||
|
from distutils.command.build import build as _build
|
||||||
|
|
||||||
|
versionfile_source = None
|
||||||
|
versionfile_build = None
|
||||||
|
tag_prefix = None
|
||||||
|
parentdir_prefix = None
|
||||||
|
|
||||||
|
VCS = "git"
|
||||||
|
IN_LONG_VERSION_PY = False
|
||||||
|
|
||||||
|
|
||||||
|
LONG_VERSION_PY = '''
|
||||||
|
IN_LONG_VERSION_PY = True
|
||||||
|
# This file helps to compute a version number in source trees obtained from
|
||||||
|
# git-archive tarball (such as those provided by githubs download-from-tag
|
||||||
|
# feature). Distribution tarballs (build by setup.py sdist) and build
|
||||||
|
# directories (produced by setup.py build) will contain a much shorter file
|
||||||
|
# that just contains the computed version number.
|
||||||
|
|
||||||
|
# This file is released into the public domain. Generated by
|
||||||
|
# versioneer-0.7+ (https://github.com/warner/python-versioneer)
|
||||||
|
|
||||||
|
# these strings will be replaced by git during git-archive
|
||||||
|
git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s"
|
||||||
|
git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s"
|
||||||
|
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def run_command(args, cwd=None, verbose=False):
|
||||||
|
try:
|
||||||
|
# remember shell=False, so use git.cmd on windows, not just git
|
||||||
|
p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd)
|
||||||
|
except EnvironmentError:
|
||||||
|
e = sys.exc_info()[1]
|
||||||
|
if verbose:
|
||||||
|
print("unable to run %%s" %% args[0])
|
||||||
|
print(e)
|
||||||
|
return None
|
||||||
|
stdout = p.communicate()[0].strip()
|
||||||
|
if sys.version >= '3':
|
||||||
|
stdout = stdout.decode()
|
||||||
|
if p.returncode != 0:
|
||||||
|
if verbose:
|
||||||
|
print("unable to run %%s (error)" %% args[0])
|
||||||
|
return None
|
||||||
|
return stdout
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
def get_expanded_variables(versionfile_source):
|
||||||
|
# the code embedded in _version.py can just fetch the value of these
|
||||||
|
# variables. When used from setup.py, we don't want to import
|
||||||
|
# _version.py, so we do it with a regexp instead. This function is not
|
||||||
|
# used from _version.py.
|
||||||
|
variables = {}
|
||||||
|
try:
|
||||||
|
for line in open(versionfile_source,"r").readlines():
|
||||||
|
if line.strip().startswith("git_refnames ="):
|
||||||
|
mo = re.search(r'=\s*"(.*)"', line)
|
||||||
|
if mo:
|
||||||
|
variables["refnames"] = mo.group(1)
|
||||||
|
if line.strip().startswith("git_full ="):
|
||||||
|
mo = re.search(r'=\s*"(.*)"', line)
|
||||||
|
if mo:
|
||||||
|
variables["full"] = mo.group(1)
|
||||||
|
except EnvironmentError:
|
||||||
|
pass
|
||||||
|
return variables
|
||||||
|
|
||||||
|
def versions_from_expanded_variables(variables, tag_prefix, verbose=False):
|
||||||
|
refnames = variables["refnames"].strip()
|
||||||
|
if refnames.startswith("$Format"):
|
||||||
|
if verbose:
|
||||||
|
print("variables are unexpanded, not using")
|
||||||
|
return {} # unexpanded, so not in an unpacked git-archive tarball
|
||||||
|
refs = set([r.strip() for r in refnames.strip("()").split(",")])
|
||||||
|
for ref in list(refs):
|
||||||
|
if not re.search(r'\d', ref):
|
||||||
|
if verbose:
|
||||||
|
print("discarding '%%s', no digits" %% ref)
|
||||||
|
refs.discard(ref)
|
||||||
|
# Assume all version tags have a digit. git's %%d expansion
|
||||||
|
# behaves like git log --decorate=short and strips out the
|
||||||
|
# refs/heads/ and refs/tags/ prefixes that would let us
|
||||||
|
# distinguish between branches and tags. By ignoring refnames
|
||||||
|
# without digits, we filter out many common branch names like
|
||||||
|
# "release" and "stabilization", as well as "HEAD" and "master".
|
||||||
|
if verbose:
|
||||||
|
print("remaining refs: %%s" %% ",".join(sorted(refs)))
|
||||||
|
for ref in sorted(refs):
|
||||||
|
# sorting will prefer e.g. "2.0" over "2.0rc1"
|
||||||
|
if ref.startswith(tag_prefix):
|
||||||
|
r = ref[len(tag_prefix):]
|
||||||
|
if verbose:
|
||||||
|
print("picking %%s" %% r)
|
||||||
|
return { "version": r,
|
||||||
|
"full": variables["full"].strip() }
|
||||||
|
# no suitable tags, so we use the full revision id
|
||||||
|
if verbose:
|
||||||
|
print("no suitable tags, using full revision id")
|
||||||
|
return { "version": variables["full"].strip(),
|
||||||
|
"full": variables["full"].strip() }
|
||||||
|
|
||||||
|
def versions_from_vcs(tag_prefix, versionfile_source, verbose=False):
|
||||||
|
# this runs 'git' from the root of the source tree. That either means
|
||||||
|
# someone ran a setup.py command (and this code is in versioneer.py, so
|
||||||
|
# IN_LONG_VERSION_PY=False, thus the containing directory is the root of
|
||||||
|
# the source tree), or someone ran a project-specific entry point (and
|
||||||
|
# this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the
|
||||||
|
# containing directory is somewhere deeper in the source tree). This only
|
||||||
|
# gets called if the git-archive 'subst' variables were *not* expanded,
|
||||||
|
# and _version.py hasn't already been rewritten with a short version
|
||||||
|
# string, meaning we're inside a checked out source tree.
|
||||||
|
|
||||||
|
try:
|
||||||
|
here = os.path.abspath(__file__)
|
||||||
|
except NameError:
|
||||||
|
# some py2exe/bbfreeze/non-CPython implementations don't do __file__
|
||||||
|
return {} # not always correct
|
||||||
|
|
||||||
|
# versionfile_source is the relative path from the top of the source tree
|
||||||
|
# (where the .git directory might live) to this file. Invert this to find
|
||||||
|
# the root from __file__.
|
||||||
|
root = here
|
||||||
|
if IN_LONG_VERSION_PY:
|
||||||
|
for i in range(len(versionfile_source.split("/"))):
|
||||||
|
root = os.path.dirname(root)
|
||||||
|
else:
|
||||||
|
root = os.path.dirname(here)
|
||||||
|
if not os.path.exists(os.path.join(root, ".git")):
|
||||||
|
if verbose:
|
||||||
|
print("no .git in %%s" %% root)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
GIT = "git"
|
||||||
|
if sys.platform == "win32":
|
||||||
|
GIT = "git.cmd"
|
||||||
|
stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"],
|
||||||
|
cwd=root)
|
||||||
|
if stdout is None:
|
||||||
|
return {}
|
||||||
|
if not stdout.startswith(tag_prefix):
|
||||||
|
if verbose:
|
||||||
|
print("tag '%%s' doesn't start with prefix '%%s'" %% (stdout, tag_prefix))
|
||||||
|
return {}
|
||||||
|
tag = stdout[len(tag_prefix):]
|
||||||
|
stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
|
||||||
|
if stdout is None:
|
||||||
|
return {}
|
||||||
|
full = stdout.strip()
|
||||||
|
if tag.endswith("-dirty"):
|
||||||
|
full += "-dirty"
|
||||||
|
return {"version": tag, "full": full}
|
||||||
|
|
||||||
|
|
||||||
|
def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False):
|
||||||
|
if IN_LONG_VERSION_PY:
|
||||||
|
# We're running from _version.py. If it's from a source tree
|
||||||
|
# (execute-in-place), we can work upwards to find the root of the
|
||||||
|
# tree, and then check the parent directory for a version string. If
|
||||||
|
# it's in an installed application, there's no hope.
|
||||||
|
try:
|
||||||
|
here = os.path.abspath(__file__)
|
||||||
|
except NameError:
|
||||||
|
# py2exe/bbfreeze/non-CPython don't have __file__
|
||||||
|
return {} # without __file__, we have no hope
|
||||||
|
# versionfile_source is the relative path from the top of the source
|
||||||
|
# tree to _version.py. Invert this to find the root from __file__.
|
||||||
|
root = here
|
||||||
|
for i in range(len(versionfile_source.split("/"))):
|
||||||
|
root = os.path.dirname(root)
|
||||||
|
else:
|
||||||
|
# we're running from versioneer.py, which means we're running from
|
||||||
|
# the setup.py in a source tree. sys.argv[0] is setup.py in the root.
|
||||||
|
here = os.path.abspath(sys.argv[0])
|
||||||
|
root = os.path.dirname(here)
|
||||||
|
|
||||||
|
# Source tarballs conventionally unpack into a directory that includes
|
||||||
|
# both the project name and a version string.
|
||||||
|
dirname = os.path.basename(root)
|
||||||
|
if not dirname.startswith(parentdir_prefix):
|
||||||
|
if verbose:
|
||||||
|
print("guessing rootdir is '%%s', but '%%s' doesn't start with prefix '%%s'" %%
|
||||||
|
(root, dirname, parentdir_prefix))
|
||||||
|
return None
|
||||||
|
return {"version": dirname[len(parentdir_prefix):], "full": ""}
|
||||||
|
|
||||||
|
tag_prefix = "%(TAG_PREFIX)s"
|
||||||
|
parentdir_prefix = "%(PARENTDIR_PREFIX)s"
|
||||||
|
versionfile_source = "%(VERSIONFILE_SOURCE)s"
|
||||||
|
|
||||||
|
def get_versions(default={"version": "unknown", "full": ""}, verbose=False):
|
||||||
|
variables = { "refnames": git_refnames, "full": git_full }
|
||||||
|
ver = versions_from_expanded_variables(variables, tag_prefix, verbose)
|
||||||
|
if not ver:
|
||||||
|
ver = versions_from_vcs(tag_prefix, versionfile_source, verbose)
|
||||||
|
if not ver:
|
||||||
|
ver = versions_from_parentdir(parentdir_prefix, versionfile_source,
|
||||||
|
verbose)
|
||||||
|
if not ver:
|
||||||
|
ver = default
|
||||||
|
return ver
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def run_command(args, cwd=None, verbose=False):
|
||||||
|
try:
|
||||||
|
# remember shell=False, so use git.cmd on windows, not just git
|
||||||
|
p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd)
|
||||||
|
except EnvironmentError:
|
||||||
|
e = sys.exc_info()[1]
|
||||||
|
if verbose:
|
||||||
|
print("unable to run %s" % args[0])
|
||||||
|
print(e)
|
||||||
|
return None
|
||||||
|
stdout = p.communicate()[0].strip()
|
||||||
|
if sys.version >= '3':
|
||||||
|
stdout = stdout.decode()
|
||||||
|
if p.returncode != 0:
|
||||||
|
if verbose:
|
||||||
|
print("unable to run %s (error)" % args[0])
|
||||||
|
return None
|
||||||
|
return stdout
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
def get_expanded_variables(versionfile_source):
|
||||||
|
# the code embedded in _version.py can just fetch the value of these
|
||||||
|
# variables. When used from setup.py, we don't want to import
|
||||||
|
# _version.py, so we do it with a regexp instead. This function is not
|
||||||
|
# used from _version.py.
|
||||||
|
variables = {}
|
||||||
|
try:
|
||||||
|
for line in open(versionfile_source,"r").readlines():
|
||||||
|
if line.strip().startswith("git_refnames ="):
|
||||||
|
mo = re.search(r'=\s*"(.*)"', line)
|
||||||
|
if mo:
|
||||||
|
variables["refnames"] = mo.group(1)
|
||||||
|
if line.strip().startswith("git_full ="):
|
||||||
|
mo = re.search(r'=\s*"(.*)"', line)
|
||||||
|
if mo:
|
||||||
|
variables["full"] = mo.group(1)
|
||||||
|
except EnvironmentError:
|
||||||
|
pass
|
||||||
|
return variables
|
||||||
|
|
||||||
|
def versions_from_expanded_variables(variables, tag_prefix, verbose=False):
|
||||||
|
refnames = variables["refnames"].strip()
|
||||||
|
if refnames.startswith("$Format"):
|
||||||
|
if verbose:
|
||||||
|
print("variables are unexpanded, not using")
|
||||||
|
return {} # unexpanded, so not in an unpacked git-archive tarball
|
||||||
|
refs = set([r.strip() for r in refnames.strip("()").split(",")])
|
||||||
|
for ref in list(refs):
|
||||||
|
if not re.search(r'\d', ref):
|
||||||
|
if verbose:
|
||||||
|
print("discarding '%s', no digits" % ref)
|
||||||
|
refs.discard(ref)
|
||||||
|
# Assume all version tags have a digit. git's %d expansion
|
||||||
|
# behaves like git log --decorate=short and strips out the
|
||||||
|
# refs/heads/ and refs/tags/ prefixes that would let us
|
||||||
|
# distinguish between branches and tags. By ignoring refnames
|
||||||
|
# without digits, we filter out many common branch names like
|
||||||
|
# "release" and "stabilization", as well as "HEAD" and "master".
|
||||||
|
if verbose:
|
||||||
|
print("remaining refs: %s" % ",".join(sorted(refs)))
|
||||||
|
for ref in sorted(refs):
|
||||||
|
# sorting will prefer e.g. "2.0" over "2.0rc1"
|
||||||
|
if ref.startswith(tag_prefix):
|
||||||
|
r = ref[len(tag_prefix):]
|
||||||
|
if verbose:
|
||||||
|
print("picking %s" % r)
|
||||||
|
return { "version": r,
|
||||||
|
"full": variables["full"].strip() }
|
||||||
|
# no suitable tags, so we use the full revision id
|
||||||
|
if verbose:
|
||||||
|
print("no suitable tags, using full revision id")
|
||||||
|
return { "version": variables["full"].strip(),
|
||||||
|
"full": variables["full"].strip() }
|
||||||
|
|
||||||
|
def versions_from_vcs(tag_prefix, versionfile_source, verbose=False):
|
||||||
|
# this runs 'git' from the root of the source tree. That either means
|
||||||
|
# someone ran a setup.py command (and this code is in versioneer.py, so
|
||||||
|
# IN_LONG_VERSION_PY=False, thus the containing directory is the root of
|
||||||
|
# the source tree), or someone ran a project-specific entry point (and
|
||||||
|
# this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the
|
||||||
|
# containing directory is somewhere deeper in the source tree). This only
|
||||||
|
# gets called if the git-archive 'subst' variables were *not* expanded,
|
||||||
|
# and _version.py hasn't already been rewritten with a short version
|
||||||
|
# string, meaning we're inside a checked out source tree.
|
||||||
|
|
||||||
|
try:
|
||||||
|
here = os.path.abspath(__file__)
|
||||||
|
except NameError:
|
||||||
|
# some py2exe/bbfreeze/non-CPython implementations don't do __file__
|
||||||
|
return {} # not always correct
|
||||||
|
|
||||||
|
# versionfile_source is the relative path from the top of the source tree
|
||||||
|
# (where the .git directory might live) to this file. Invert this to find
|
||||||
|
# the root from __file__.
|
||||||
|
root = here
|
||||||
|
if IN_LONG_VERSION_PY:
|
||||||
|
for i in range(len(versionfile_source.split("/"))):
|
||||||
|
root = os.path.dirname(root)
|
||||||
|
else:
|
||||||
|
root = os.path.dirname(here)
|
||||||
|
if not os.path.exists(os.path.join(root, ".git")):
|
||||||
|
if verbose:
|
||||||
|
print("no .git in %s" % root)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
GIT = "git"
|
||||||
|
if sys.platform == "win32":
|
||||||
|
GIT = "git.cmd"
|
||||||
|
stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"],
|
||||||
|
cwd=root)
|
||||||
|
if stdout is None:
|
||||||
|
return {}
|
||||||
|
if not stdout.startswith(tag_prefix):
|
||||||
|
if verbose:
|
||||||
|
print("tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix))
|
||||||
|
return {}
|
||||||
|
tag = stdout[len(tag_prefix):]
|
||||||
|
stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
|
||||||
|
if stdout is None:
|
||||||
|
return {}
|
||||||
|
full = stdout.strip()
|
||||||
|
if tag.endswith("-dirty"):
|
||||||
|
full += "-dirty"
|
||||||
|
return {"version": tag, "full": full}
|
||||||
|
|
||||||
|
|
||||||
|
def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False):
|
||||||
|
if IN_LONG_VERSION_PY:
|
||||||
|
# We're running from _version.py. If it's from a source tree
|
||||||
|
# (execute-in-place), we can work upwards to find the root of the
|
||||||
|
# tree, and then check the parent directory for a version string. If
|
||||||
|
# it's in an installed application, there's no hope.
|
||||||
|
try:
|
||||||
|
here = os.path.abspath(__file__)
|
||||||
|
except NameError:
|
||||||
|
# py2exe/bbfreeze/non-CPython don't have __file__
|
||||||
|
return {} # without __file__, we have no hope
|
||||||
|
# versionfile_source is the relative path from the top of the source
|
||||||
|
# tree to _version.py. Invert this to find the root from __file__.
|
||||||
|
root = here
|
||||||
|
for i in range(len(versionfile_source.split("/"))):
|
||||||
|
root = os.path.dirname(root)
|
||||||
|
else:
|
||||||
|
# we're running from versioneer.py, which means we're running from
|
||||||
|
# the setup.py in a source tree. sys.argv[0] is setup.py in the root.
|
||||||
|
here = os.path.abspath(sys.argv[0])
|
||||||
|
root = os.path.dirname(here)
|
||||||
|
|
||||||
|
# Source tarballs conventionally unpack into a directory that includes
|
||||||
|
# both the project name and a version string.
|
||||||
|
dirname = os.path.basename(root)
|
||||||
|
if not dirname.startswith(parentdir_prefix):
|
||||||
|
if verbose:
|
||||||
|
print("guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" %
|
||||||
|
(root, dirname, parentdir_prefix))
|
||||||
|
return None
|
||||||
|
return {"version": dirname[len(parentdir_prefix):], "full": ""}
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def do_vcs_install(versionfile_source, ipy):
|
||||||
|
GIT = "git"
|
||||||
|
if sys.platform == "win32":
|
||||||
|
GIT = "git.cmd"
|
||||||
|
run_command([GIT, "add", "versioneer.py"])
|
||||||
|
run_command([GIT, "add", versionfile_source])
|
||||||
|
run_command([GIT, "add", ipy])
|
||||||
|
present = False
|
||||||
|
try:
|
||||||
|
f = open(".gitattributes", "r")
|
||||||
|
for line in f.readlines():
|
||||||
|
if line.strip().startswith(versionfile_source):
|
||||||
|
if "export-subst" in line.strip().split()[1:]:
|
||||||
|
present = True
|
||||||
|
f.close()
|
||||||
|
except EnvironmentError:
|
||||||
|
pass
|
||||||
|
if not present:
|
||||||
|
f = open(".gitattributes", "a+")
|
||||||
|
f.write("%s export-subst\n" % versionfile_source)
|
||||||
|
f.close()
|
||||||
|
run_command([GIT, "add", ".gitattributes"])
|
||||||
|
|
||||||
|
|
||||||
|
SHORT_VERSION_PY = """
|
||||||
|
# This file was generated by 'versioneer.py' (0.7+) from
|
||||||
|
# revision-control system data, or from the parent directory name of an
|
||||||
|
# unpacked source archive. Distribution tarballs contain a pre-generated copy
|
||||||
|
# of this file.
|
||||||
|
|
||||||
|
version_version = '%(version)s'
|
||||||
|
version_full = '%(full)s'
|
||||||
|
def get_versions(default={}, verbose=False):
|
||||||
|
return {'version': version_version, 'full': version_full}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
DEFAULT = {"version": "unknown", "full": "unknown"}
|
||||||
|
|
||||||
|
def versions_from_file(filename):
|
||||||
|
versions = {}
|
||||||
|
try:
|
||||||
|
f = open(filename)
|
||||||
|
except EnvironmentError:
|
||||||
|
return versions
|
||||||
|
for line in f.readlines():
|
||||||
|
mo = re.match("version_version = '([^']+)'", line)
|
||||||
|
if mo:
|
||||||
|
versions["version"] = mo.group(1)
|
||||||
|
mo = re.match("version_full = '([^']+)'", line)
|
||||||
|
if mo:
|
||||||
|
versions["full"] = mo.group(1)
|
||||||
|
return versions
|
||||||
|
|
||||||
|
def write_to_version_file(filename, versions):
|
||||||
|
f = open(filename, "w")
|
||||||
|
f.write(SHORT_VERSION_PY % versions)
|
||||||
|
f.close()
|
||||||
|
print("set %s to '%s'" % (filename, versions["version"]))
|
||||||
|
|
||||||
|
|
||||||
|
def get_best_versions(versionfile, tag_prefix, parentdir_prefix,
|
||||||
|
default=DEFAULT, verbose=False):
|
||||||
|
# returns dict with two keys: 'version' and 'full'
|
||||||
|
#
|
||||||
|
# extract version from first of _version.py, 'git describe', parentdir.
|
||||||
|
# This is meant to work for developers using a source checkout, for users
|
||||||
|
# of a tarball created by 'setup.py sdist', and for users of a
|
||||||
|
# tarball/zipball created by 'git archive' or github's download-from-tag
|
||||||
|
# feature.
|
||||||
|
|
||||||
|
variables = get_expanded_variables(versionfile_source)
|
||||||
|
if variables:
|
||||||
|
ver = versions_from_expanded_variables(variables, tag_prefix)
|
||||||
|
if ver:
|
||||||
|
if verbose: print("got version from expanded variable %s" % ver)
|
||||||
|
return ver
|
||||||
|
|
||||||
|
ver = versions_from_file(versionfile)
|
||||||
|
if ver:
|
||||||
|
if verbose: print("got version from file %s %s" % (versionfile, ver))
|
||||||
|
return ver
|
||||||
|
|
||||||
|
ver = versions_from_vcs(tag_prefix, versionfile_source, verbose)
|
||||||
|
if ver:
|
||||||
|
if verbose: print("got version from git %s" % ver)
|
||||||
|
return ver
|
||||||
|
|
||||||
|
ver = versions_from_parentdir(parentdir_prefix, versionfile_source, verbose)
|
||||||
|
if ver:
|
||||||
|
if verbose: print("got version from parentdir %s" % ver)
|
||||||
|
return ver
|
||||||
|
|
||||||
|
if verbose: print("got version from default %s" % ver)
|
||||||
|
return default
|
||||||
|
|
||||||
|
def get_versions(default=DEFAULT, verbose=False):
|
||||||
|
assert versionfile_source is not None, "please set versioneer.versionfile_source"
|
||||||
|
assert tag_prefix is not None, "please set versioneer.tag_prefix"
|
||||||
|
assert parentdir_prefix is not None, "please set versioneer.parentdir_prefix"
|
||||||
|
return get_best_versions(versionfile_source, tag_prefix, parentdir_prefix,
|
||||||
|
default=default, verbose=verbose)
|
||||||
|
def get_version(verbose=False):
|
||||||
|
return get_versions(verbose=verbose)["version"]
|
||||||
|
|
||||||
|
class cmd_version(Command):
|
||||||
|
description = "report generated version string"
|
||||||
|
user_options = []
|
||||||
|
boolean_options = []
|
||||||
|
def initialize_options(self):
|
||||||
|
pass
|
||||||
|
def finalize_options(self):
|
||||||
|
pass
|
||||||
|
def run(self):
|
||||||
|
ver = get_version(verbose=True)
|
||||||
|
print("Version is currently: %s" % ver)
|
||||||
|
|
||||||
|
|
||||||
|
class cmd_build(_build):
|
||||||
|
def run(self):
|
||||||
|
versions = get_versions(verbose=True)
|
||||||
|
_build.run(self)
|
||||||
|
# now locate _version.py in the new build/ directory and replace it
|
||||||
|
# with an updated value
|
||||||
|
target_versionfile = os.path.join(self.build_lib, versionfile_build)
|
||||||
|
print("UPDATING %s" % target_versionfile)
|
||||||
|
os.unlink(target_versionfile)
|
||||||
|
f = open(target_versionfile, "w")
|
||||||
|
f.write(SHORT_VERSION_PY % versions)
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
class cmd_sdist(_sdist):
|
||||||
|
def run(self):
|
||||||
|
versions = get_versions(verbose=True)
|
||||||
|
self._versioneer_generated_versions = versions
|
||||||
|
# unless we update this, the command will keep using the old version
|
||||||
|
self.distribution.metadata.version = versions["version"]
|
||||||
|
return _sdist.run(self)
|
||||||
|
|
||||||
|
def make_release_tree(self, base_dir, files):
|
||||||
|
_sdist.make_release_tree(self, base_dir, files)
|
||||||
|
# now locate _version.py in the new base_dir directory (remembering
|
||||||
|
# that it may be a hardlink) and replace it with an updated value
|
||||||
|
target_versionfile = os.path.join(base_dir, versionfile_source)
|
||||||
|
print("UPDATING %s" % target_versionfile)
|
||||||
|
os.unlink(target_versionfile)
|
||||||
|
f = open(target_versionfile, "w")
|
||||||
|
f.write(SHORT_VERSION_PY % self._versioneer_generated_versions)
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
INIT_PY_SNIPPET = """
|
||||||
|
from ._version import get_versions
|
||||||
|
__version__ = get_versions()['version']
|
||||||
|
del get_versions
|
||||||
|
"""
|
||||||
|
|
||||||
|
class cmd_update_files(Command):
|
||||||
|
description = "modify __init__.py and create _version.py"
|
||||||
|
user_options = []
|
||||||
|
boolean_options = []
|
||||||
|
def initialize_options(self):
|
||||||
|
pass
|
||||||
|
def finalize_options(self):
|
||||||
|
pass
|
||||||
|
def run(self):
|
||||||
|
ipy = os.path.join(os.path.dirname(versionfile_source), "__init__.py")
|
||||||
|
print(" creating %s" % versionfile_source)
|
||||||
|
f = open(versionfile_source, "w")
|
||||||
|
f.write(LONG_VERSION_PY % {"DOLLAR": "$",
|
||||||
|
"TAG_PREFIX": tag_prefix,
|
||||||
|
"PARENTDIR_PREFIX": parentdir_prefix,
|
||||||
|
"VERSIONFILE_SOURCE": versionfile_source,
|
||||||
|
})
|
||||||
|
f.close()
|
||||||
|
try:
|
||||||
|
old = open(ipy, "r").read()
|
||||||
|
except EnvironmentError:
|
||||||
|
old = ""
|
||||||
|
if INIT_PY_SNIPPET not in old:
|
||||||
|
print(" appending to %s" % ipy)
|
||||||
|
f = open(ipy, "a")
|
||||||
|
f.write(INIT_PY_SNIPPET)
|
||||||
|
f.close()
|
||||||
|
else:
|
||||||
|
print(" %s unmodified" % ipy)
|
||||||
|
do_vcs_install(versionfile_source, ipy)
|
||||||
|
|
||||||
|
def get_cmdclass():
|
||||||
|
return {'version': cmd_version,
|
||||||
|
'update_files': cmd_update_files,
|
||||||
|
'build': cmd_build,
|
||||||
|
'sdist': cmd_sdist,
|
||||||
|
}
|
Reference in New Issue
Block a user