Skip to content

Commit 980955e

Browse files
committed
Merge pull request #18 from al4/master
Flesh out /stats API
2 parents 6eb37df + d7ee06a commit 980955e

20 files changed

+906
-305
lines changed

Vagrantfile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,14 @@ Vagrant.configure(2) do |config|
4343
# backing providers for Vagrant. These expose provider-specific options.
4444
# Example for VirtualBox:
4545
#
46-
# config.vm.provider "virtualbox" do |vb|
46+
config.vm.provider "virtualbox" do |vb|
4747
# # Display the VirtualBox GUI when booting the machine
4848
# vb.gui = true
4949
#
5050
# # Customize the amount of memory on the VM:
5151
# vb.memory = "1024"
52-
# end
52+
vb.cpus = "2"
53+
end
5354
#
5455
# View the documentation for the provider you are using for more
5556
# information on available options.
@@ -77,6 +78,9 @@ Vagrant.configure(2) do |config|
7778
7879
# Build tools
7980
sudo apt-get -y install build-essential git-buildpackage debhelper python-dev dh-systemd
81+
wget -P /tmp/ \
82+
'https://launchpad.net/ubuntu/+archive/primary/+files/dh-virtualenv_0.11-1_all.deb'
83+
dpkg -i /tmp/dh-virtualenv_0.11-1_all.deb
8084
8185
sudo pip install --upgrade pip
8286
sudo pip install sphinx sphinxcontrib-httpdomain

debian/changelog

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,94 @@
1+
orlo (0.1.1) stable; urgency=medium
2+
3+
* Cast package_rollback as a boolean
4+
* Add log files for gunicorn
5+
* Separate debug mode and debug logging
6+
* Return 400 when getting /releases with no filter
7+
* Remove skip from documentation, it was renamed to offset
8+
* Fix tests, require filter on GET /releases
9+
* Bump vagrant box cpus to 2
10+
* Implement stats by date/time
11+
* Fix query in release stats to group by release
12+
* Add boolean True to acceptable success values
13+
* Bump version
14+
* Cast package_rollback as a boolean
15+
* Add log files for gunicorn
16+
* Separate debug mode and debug logging
17+
* Return 400 when getting /releases with no filter
18+
* Remove skip from documentation, it was renamed to offset
19+
* Fix tests, require filter on GET /releases
20+
* Bump vagrant box cpus to 2
21+
* Implement stats by date/time
22+
* Fix query in release stats to group by release
23+
* Add boolean True to acceptable success values
24+
* Bump version
25+
26+
-- Alex Forbes <[email protected]> Mon, 01 Feb 2016 18:19:03 +0000
27+
28+
orlo (0.1.0) stable; urgency=medium
29+
30+
* Move orlo to /api/ in nginx
31+
* Add ability to filter on rollback
32+
* Add DB created to vagrant file
33+
* Add more get_releases package filters and "latest" option
34+
* Abstract filtering logic +
35+
* Show easy to understand json message when filtering on invalid field
36+
* Fix last/latest parameter name
37+
* Use limit(1) instead of first for latest query
38+
* Move helper functions from views.py to util.py
39+
* Rename views.py to route_api.py
40+
* Move filter function to util
41+
* Start adding stats endpoints and functions
42+
* Add index to stime field on Release
43+
* Add __version__ attribute to package
44+
* Add _version.py to gitignore
45+
* Move cli interface into python page
46+
* Add queries, for use by the stats and info routes
47+
* Implement more /info endpoints, separate tests from stats
48+
* Refactor non-endpoint tests to use internal methods rather than http
49+
* Remove data.py
50+
* Make the release counting function (now count_releases) more generic
51+
* Add rollback filter to count_packages
52+
* Comments, plus bump version
53+
* Install requirements in vagrant file
54+
* Fix platform name in log message
55+
* Implement /stats endpoints
56+
* Add /stats/package and consolidate the dictionary creation
57+
* Handle poorly formatted time gracefully in stats endpoints
58+
* Bump version
59+
* Reverse Release-Package relationship
60+
* Fix missing arguments passed to count_releases and implement /stats
61+
* Fix to package_versions
62+
* Rename /info urls
63+
* Documentation updates
64+
* Update Travis config to use Postgres
65+
* Remove password from test postgres DB
66+
* Fix postgres command
67+
* Fix database setup
68+
* Fix quotes in travis DB string
69+
* Fix package_versions under postgres
70+
* Set Flask packages to >= $version in requirements.txt
71+
* Change /info/user endpoint
72+
* Remove print statement, fix minor documentation bugs in /info
73+
* Move platform in /info routes to query parameter
74+
* Stream output of GET /releases to reduce memory usage
75+
* Move /releases streaming json generator to util.py
76+
* Add example curl to documentation for /import
77+
* Abstract release logic away from the get_releases route
78+
* Add status to get_releases parameters
79+
* Remove "latest" filter option in favour of "desc" and "limit"
80+
* Rename test_import
81+
* Add offset to get_releases
82+
* Ensure limit and offset are ints
83+
* Implement time-based stats for charts
84+
* Move orlo.conf to orlo/orlo.ini
85+
* Rename package from python-orlo to orlo
86+
* Deb packaging fixes
87+
* Add tests for stop package
88+
* Bump version to 0.1.0
89+
90+
-- Alex Forbes <[email protected]> Tue, 19 Jan 2016 16:07:52 +0000
91+
192
python-orlo (0.0.4) stable; urgency=medium
293

394
* Update debian description

orlo/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from flask import Flask
2+
import logging
23
from logging.handlers import RotatingFileHandler
34

45
from orlo.config import config
@@ -15,8 +16,15 @@
1516
if config.getboolean('db', 'echo_queries'):
1617
app.config['SQLALCHEMY_ECHO'] = True
1718

18-
if config.getboolean('logging', 'debug'):
19+
# Debug mode ignores all custom logging and should only be used in
20+
# local testing...
21+
if config.getboolean('main', 'debug_mode'):
1922
app.debug = True
23+
24+
# ...as opposed to loglevel debug, which can be used anywhere
25+
if config.getboolean('logging', 'debug'):
26+
app.logger.setLevel(logging.DEBUG)
27+
2028
app.logger.debug('Debug enabled')
2129

2230
if not config.getboolean('main', 'strict_slashes'):

orlo/cli.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ def parse_args():
1919

2020
p_database = argparse.ArgumentParser(add_help=False)
2121
p_server = argparse.ArgumentParser(add_help=False)
22-
p_server.add_argument('--host', '-H', dest='host', default='127.0.0.1', help="Address to listen on")
23-
p_server.add_argument('--port', '-P', dest='port', type=int, default=5000, help="Port to listen on")
22+
p_server.add_argument('--host', '-H', dest='host', default='127.0.0.1',
23+
help="Address to listen on")
24+
p_server.add_argument('--port', '-P', dest='port', type=int, default=5000,
25+
help="Port to listen on")
2426

2527
subparsers = parser.add_subparsers(dest='action')
2628
sp_config = subparsers.add_parser(

orlo/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
config = ConfigParser.ConfigParser()
66

77
config.add_section('main')
8+
config.set('main', 'debug_mode', 'false')
89
config.set('main', 'propagate_exceptions', 'true')
910
config.set('main', 'time_format', '%Y-%m-%dT%H:%M:%SZ')
1011
config.set('main', 'time_zone', 'UTC')

orlo/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
class OrloError(Exception):
77
status_code = 500
8+
89
def __init__(self, message, status_code=None, payload=None):
910
Exception.__init__(self)
1011
self.message = message
@@ -28,4 +29,3 @@ class DatabaseError(OrloError):
2829

2930
class OrloWorkflowError(OrloError):
3031
status_code = 400
31-

orlo/queries.py

Lines changed: 14 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from orlo import app
66
from orlo.orm import db, Release, Platform, Package, release_platform
77
from orlo.exceptions import OrloError, InvalidUsage
8-
from orlo.util import is_int
98
from collections import OrderedDict
109

1110
__author__ = 'alforbes'
@@ -15,7 +14,7 @@
1514
"""
1615

1716

18-
def _filter_release_status(query, status):
17+
def filter_release_status(query, status):
1918
"""
2019
Filter the given query by the given release status
2120
@@ -43,7 +42,7 @@ def _filter_release_status(query, status):
4342
return query
4443

4544

46-
def _filter_release_rollback(query, rollback):
45+
def filter_release_rollback(query, rollback):
4746
"""
4847
Filter the given query by whether the releases are rollbacks or not
4948
@@ -81,12 +80,13 @@ def apply_filters(query, args):
8180
if field == 'latest': # this is not a comparison
8281
continue
8382

84-
# special logic for these ones, as they are package attributes
83+
# special logic for these ones, as they are release attributes that
84+
# are JIT calculated from package attributes
8585
if field == 'status':
86-
query = _filter_release_status(query, value)
86+
query = filter_release_status(query, value)
8787
continue
8888
if field == 'rollback':
89-
query = _filter_release_rollback(query, value)
89+
query = filter_release_rollback(query, value)
9090
continue
9191

9292
if field.startswith('package_'):
@@ -189,11 +189,15 @@ def releases(**kwargs):
189189
query = query.order_by(stime_field())
190190

191191
if limit:
192-
if not is_int(limit):
192+
try:
193+
limit = int(limit)
194+
except ValueError:
193195
raise InvalidUsage("limit must be a valid integer value")
194196
query = query.limit(limit)
195197
if offset:
196-
if not is_int(offset):
198+
try:
199+
offset = int(offset)
200+
except ValueError:
197201
raise InvalidUsage("offset must be a valid integer value")
198202
query = query.offset(offset)
199203

@@ -420,10 +424,10 @@ def count_releases(user=None, package=None, team=None, platform=None, status=Non
420424
query = query.filter(Release.stime <= ftime)
421425

422426
if rollback is not None:
423-
query = _filter_release_rollback(query, rollback)
427+
query = filter_release_rollback(query, rollback)
424428

425429
if status:
426-
query = _filter_release_status(query, status)
430+
query = filter_release_status(query, status)
427431

428432
return query
429433

@@ -500,116 +504,3 @@ def platform_list():
500504
return query
501505

502506

503-
def stats_release_time(unit, summarize_by_unit=False, **kwargs):
504-
"""
505-
Return stats by time from the given arguments
506-
507-
Functions in this file usually return a query object, but here we are
508-
returning the result, as there are several queries in play.
509-
510-
:param summarize_by_unit: Passed to add_release_by_time_to_dict()
511-
:param unit: Passed to add_release_by_time_to_dict()
512-
"""
513-
514-
root_query = db.session.query(Release.id, Release.stime).join(Package)
515-
root_query = apply_filters(root_query, kwargs)
516-
517-
# Build queries for the individual stats
518-
q_normal_successful = _filter_release_status(
519-
_filter_release_rollback(root_query, rollback=False), 'SUCCESSFUL'
520-
)
521-
q_normal_failed = _filter_release_status(
522-
_filter_release_rollback(root_query, rollback=False), 'FAILED'
523-
)
524-
q_rollback_successful = _filter_release_status(
525-
_filter_release_rollback(root_query, rollback=True), 'SUCCESSFUL'
526-
)
527-
q_rollback_failed = _filter_release_status(
528-
_filter_release_rollback(root_query, rollback=True), 'FAILED'
529-
)
530-
531-
output_dict = OrderedDict()
532-
533-
add_releases_by_time_to_dict(
534-
q_normal_successful, output_dict, ('normal', 'successful'), unit, summarize_by_unit)
535-
add_releases_by_time_to_dict(
536-
q_normal_failed, output_dict, ('normal', 'failed'), unit, summarize_by_unit)
537-
add_releases_by_time_to_dict(
538-
q_rollback_successful, output_dict, ('rollback', 'successful'), unit,
539-
summarize_by_unit)
540-
add_releases_by_time_to_dict(
541-
q_rollback_failed, output_dict, ('rollback', 'failed'), unit, summarize_by_unit)
542-
543-
return output_dict
544-
545-
546-
def add_releases_by_time_to_dict(query, releases_dict, t_category, unit='month',
547-
summarize_by_unit=False):
548-
"""
549-
Take a query and add each of its releases to a dictionary, broken down by time
550-
551-
:param dict releases_dict: Dict to add to
552-
:param tuple t_category: tuple of headings, i.e. (<normal|rollback>, <successful|failed>)
553-
:param query query: Query object to retrieve releases from
554-
:param string unit: Can be 'iso', 'hour', 'day', 'week', 'month', 'year',
555-
:param boolean summarize_by_unit: Only break down releases by the given unit, i.e. only one
556-
layer deep
557-
:return:
558-
"""
559-
560-
for release in query:
561-
if summarize_by_unit:
562-
tree_args = [str(getattr(release.stime, unit))]
563-
else:
564-
if unit == 'year':
565-
tree_args = [str(release.stime.year)]
566-
elif unit == 'month':
567-
tree_args = [str(release.stime.year), str(release.stime.month)]
568-
elif unit == 'week':
569-
# First two args of isocalendar(), year and week
570-
tree_args = [str(i) for i in release.stime.isocalendar()][0:2]
571-
elif unit == 'iso':
572-
tree_args = [str(i) for i in release.stime.isocalendar()]
573-
elif unit == 'day':
574-
tree_args = [str(release.stime.year), str(release.stime.month),
575-
str(release.stime.day)]
576-
elif unit == 'hour':
577-
tree_args = [str(release.stime.year), str(release.stime.month),
578-
str(release.stime.day), str(release.stime.hour)]
579-
else:
580-
raise InvalidUsage(
581-
'Invalid unit "{}" specified for release breakdown'.format(
582-
unit))
583-
# Append categories
584-
print(tree_args)
585-
tree_args += t_category
586-
append_tree_recursive(releases_dict, tree_args[0], tree_args)
587-
588-
589-
def append_tree_recursive(tree, parent, nodes):
590-
"""
591-
Recursively place the nodes under each other
592-
593-
:param dict tree: The dictionary we are operating on
594-
:param parent: The parent for this node
595-
:param nodes: The list of nodes
596-
:return:
597-
"""
598-
print('Called recursive function with args:\n{}, {}, {}'.format(
599-
str(tree), str(parent), str(nodes)))
600-
try:
601-
# Get the child, one after the parent
602-
child = nodes[nodes.index(parent) + 1]
603-
except IndexError:
604-
# Must be at end
605-
if parent in tree:
606-
tree[parent] += 1
607-
else:
608-
tree[parent] = 1
609-
return tree
610-
611-
# Otherwise recurse again
612-
if parent not in tree:
613-
tree[parent] = {}
614-
# Child becomes the parent
615-
append_tree_recursive(tree[parent], child, nodes)

0 commit comments

Comments
 (0)