Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metric api put #82

Merged
merged 3 commits into from
Feb 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
+ `GET /api/v1/metric/<metric_name>` has been implemented (#73)
+ `DELETE /api/v1/metric/<metric_name>` has been implemented (#78)
+ `POST /api/v1/metric` has been implemented (#74)
+ `PUT /api/v1/metric/<metric_name>` has been implemented (#75)


## v0.3.0 (2019-01-28)
Expand Down
122 changes: 122 additions & 0 deletions src/trendlines/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

# peewee
from peewee import DoesNotExist
from peewee import IntegrityError
from playhouse.shortcuts import model_to_dict

from trendlines import logger
Expand Down Expand Up @@ -247,3 +248,124 @@ def delete_metric(metric):
return resp.as_response(), http_status
else:
return "", 204


@api.route("/api/v1/metric/<metric>", methods=["PUT"])
def put_metric(metric):
"""
Replace a metric with new values.

This function cannot change the ``metric_id`` value.

Keys not given are assumed to be ``None``.

Accepts JSON data with the following format:

.. code-block::json
{
"name": "your.metric_name.here",
"units": {string, optional},
"upper_limit": {float, optional},
"lower_limit": {float, optional},
}

Returns
-------
200 :
Success. Returned JSON data has two keys: ``old_value`` and
``new_value``, each containing a full :class:`orm.Metric` object.
400 :
Malformed JSON data (such as when ``name`` is missing)
404 :
The requested metric is not found.
409 :
The metric already exists.


See Also
--------
:func:`routes.get_metric_as_json`
:func:`routes.post_metric`
:func:`routes.delete_metric`
"""
data = request.get_json()

# First see if our item actually exists
try:
metric = db.Metric.get(db.Metric.name == metric)
old_name = metric.name
old_units = metric.units
old_lower = metric.lower_limit
old_upper = metric.upper_limit
except DoesNotExist:
http_status = 404
detail = "The metric '{}' does not exist".format(metric)
resp = utils.Rfc7807ErrorResponse(
type_="metric-not-found",
title="Metric not found",
status=http_status,
detail=detail,
)
logger.warning("API error: %s" % detail)
return resp.as_response(), http_status

# Parse our json.
# TODO: possible to replace with peewee.dict_to_model?
try:
name = data['name']
except KeyError:
http_status = 400
detail = "Missing required key 'name'."
resp = utils.Rfc7807ErrorResponse(
type_="invalid-request",
title="Missing required JSON key.",
status=http_status,
detail=detail,
)
logger.warning("API error: %s" % detail)
return resp.as_response(), http_status

# All other fields we assume to be None if they're missing.
units = data.get('units', None)
upper_limit = data.get('upper_limit', None)
lower_limit = data.get('lower_limit', None)

# Update the values with the new thingy.
# TODO: use dict_to_model?
metric.name = name
metric.units = units
metric.lower_limit = lower_limit
metric.upper_limit = upper_limit
try:
metric.save()
except IntegrityError:
# Failed the unique constraint on Metric.name
http_status = 409
detail = ("Unable to change metric name '{}': target name '{}'"
" already exists.")
detail = detail.format(old_name, name)
resp = utils.Rfc7807ErrorResponse(
type_="integrity-error",
title="Constraint Failure",
status=http_status,
detail=detail,
)
logger.warning("API error: %s" % detail)
return resp.as_response(), http_status

rv = {
"old_value": {
"name": old_name,
"units": old_units,
"lower_limit": old_lower,
"upper_limit": old_upper,
},
"new_value": {
"name": metric.name,
"units": metric.units,
"lower_limit": metric.lower_limit,
"upper_limit": metric.upper_limit,
},
}

return jsonify(rv), 200
78 changes: 78 additions & 0 deletions tests/test_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,81 @@ def test_api_post_metric_missing_key(client, populated_db):
assert rv.is_json
d = rv.get_json()
assert "Missing required" in d['detail']


def test_api_put_metric(client, populated_db):
name = "foo.bar"
data = {
"name": name,
"units": "lines",
"upper_limit": 100,
"lower_limit": 0,
}
rv = client.put("/api/v1/metric/{}".format(name), json=data)
assert rv.status_code == 200
assert rv.is_json
d = rv.get_json()
assert d['old_value']['units'] is None
assert d['new_value']['units'] == "lines"


def test_api_put_metric_not_found(client, populated_db):
name = "missing"
data = {
"name": name,
"units": "lines",
"upper_limit": 100,
"lower_limit": 0,
}
rv = client.put("/api/v1/metric/{}".format(name), json=data)
assert rv.status_code == 404
assert rv.is_json
d = rv.get_json()
assert name in d['detail']
assert "does not exist" in d['detail']


def test_api_put_metric_duplicate_name(client, populated_db):
new_name = "foo.bar"
old_name = "old_data"
data = {"name": new_name}
rv = client.put("/api/v1/metric/{}".format(old_name), json=data)
assert rv.status_code == 409
assert rv.is_json
d = rv.get_json()
assert old_name in d['detail']
assert new_name in d['detail']
assert "Unable to change metric name" in d['detail']


def test_api_put_metric_missing_name(client, populated_db):
data = {"units": "goats"}
rv = client.put("/api/v1/metric/foo", json=data)
assert rv.status_code == 400
assert rv.is_json
d = rv.get_json()
assert "Missing required" in d['detail']


def test_api_put_metric_idempotence(client, populated_db):
# TODO: I don't think this is the right way to test idempotence...
name = "foo.bar"
data = {
"name": name,
"units": "lines",
"upper_limit": 100,
"lower_limit": 0,
}
rv = client.put("/api/v1/metric/{}".format(name), json=data)
assert rv.status_code == 200
assert rv.is_json
d = rv.get_json()
# First verify that things changed.
assert d['old_value']['units'] != d['new_value']

rv = client.put("/api/v1/metric/{}".format(name), json=data)
assert rv.status_code == 200
assert rv.is_json
d = rv.get_json()
# Then verify that they *didn't* change.
assert d['old_value'] == d['new_value']