Compare commits

...

18 Commits

Author SHA1 Message Date
1593e181a3 Switch to versioneer-provided versions everywhere 2013-02-05 19:07:38 -05:00
8e781506de Incorporate versioneer for versioning 2013-02-05 18:49:07 -05:00
f6a2c7620a Restructure cherrypy application more correctly
Specifically, switch from using global configuration and several apps,
to using application-specific configuration with a single app.  This
should hopefully make it easier to plug this into another
WSGI-compliant server someday, and also silences some startup warnings
about missing application configs.
2013-02-04 22:38:49 -05:00
6c30e5ab2f Add gitclean target to Makefile 2013-02-04 22:15:12 -05:00
810eac4e61 Flesh out the list of dependencies in setup.py 2013-02-04 22:14:09 -05:00
d9bb3ab7ab Fix iteratorizer coverage issue with thread timing 2013-02-04 22:14:01 -05:00
21d0e90bd9 Rework Cython and external module support.
Now we build Cython modules only if cython >= 0.16 is present.
Tarballs made by "make sdist" include the Cython-generated *.c files,
and so Cython isn't required on the end user machine at all.
2013-02-04 22:12:52 -05:00
f071d749ce Generate a MANIFEST.in from setup.py; more setup.py and Makefile updates 2013-02-04 18:14:44 -05:00
d95c354595 Print a warning in setup.py if basic dependencies aren't present 2013-02-01 17:44:54 -05:00
9bcd8183f6 Add cython dependency 2013-02-01 17:44:27 -05:00
5c531d8273 Convert runserver.py into a generated nilmdb-server script 2013-02-01 17:43:41 -05:00
3fe3e2ca95 Move nilmtool into a dedicated nilmdb.scripts module 2013-02-01 17:42:09 -05:00
f01e781469 Convert nilmtool.py into a setuptools-generated script
At install time, the script "/usr/bin/nilmtool" will be created.
2013-02-01 16:25:12 -05:00
e6180a5a81 Remove all relative imports 2013-02-01 16:02:01 -05:00
a9d31b46ed More files in clean target 2013-02-01 15:48:55 -05:00
b01f23ed99 Move runtests.py script into test directory 2013-02-01 15:47:47 -05:00
842bf21411 Include the full server response if we can't parse errors out of it.
This makes things easier to debug if there's an error in
e.g. json_error_handler(), or if we're trying to poke a server that's
not even ours.
2013-02-01 15:47:47 -05:00
750d9e3c38 Clean up some pylint warnings and potential errors 2013-02-01 15:29:24 -05:00
39 changed files with 1449 additions and 180 deletions

View File

@@ -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
View File

@@ -0,0 +1 @@
nilmdb/_version.py export-subst

4
.gitignore vendored
View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -1,3 +1,3 @@
"""nilmdb.cmdline""" """nilmdb.cmdline"""
from .cmdline import Cmdline from nilmdb.cmdline.cmdline import Cmdline

View File

@@ -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",

View File

@@ -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):

View File

@@ -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",

View File

@@ -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())

View File

@@ -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

View File

@@ -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

View File

@@ -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",

View File

@@ -0,0 +1 @@
# Command line scripts

81
nilmdb/scripts/nilmdb_server.py Executable file
View 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
View 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()

View File

@@ -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

View File

@@ -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).

View File

@@ -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

View File

@@ -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'] = (

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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(...)"

View File

@@ -1,6 +0,0 @@
#!/usr/bin/python
import nilmdb
import sys
nilmdb.cmdline.Cmdline(sys.argv[1:]).run()

View File

@@ -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()

106
setup.py
View File

@@ -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,
) )

View File

@@ -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

View File

@@ -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):

View File

@@ -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
View 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,
}