Skip to content

PCC39 pavelekshin #870

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

Open
wants to merge 6 commits into
base: community
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added 13/pavelekshin/.directors.py.un~
Binary file not shown.
89 changes: 89 additions & 0 deletions 13/pavelekshin/directors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import csv
from collections import defaultdict, namedtuple, OrderedDict, Counter
from pprint import pprint

MOVIE_DATA = "movie_metadata.csv"
NUM_TOP_DIRECTORS = 20
MIN_MOVIES = 4
MIN_YEAR = 1960
Movie = namedtuple("Movie", "title year score")


def get_movies_by_director():
"""Extracts all movies from csv and stores them in a dictionary
where keys are directors, and values is a list of movies (named tuples)"""
with open(MOVIE_DATA, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
d = defaultdict(list)
for row in reader:
try:
director = row["director_name"]
title = row["movie_title"].replace("\xa0", "")
year = int(row["title_year"])
score = float(row["imdb_score"])
except ValueError:
continue
if year > MIN_YEAR:
d[director].append(Movie(title=title, year=year, score=score))
return d


def get_average_scores(directors):
"""Filter directors with < MIN_MOVIES and calculate averge score"""
fd, dd = {}, {}
Movie = namedtuple("Movie", "score movies")
for k, movies in directors.items():
if not len(movies) < MIN_MOVIES:
score = _calc_mean(movies)
fd[k] = Movie(score=score, movies=movies)
sd = OrderedDict(
{k: v for k, v in sorted(fd.items(), key=lambda x: x[1][0], reverse=True)}
)
return sd


def _calc_mean(movies):
"""Helper method to calculate mean of list of Movie namedtuples"""
return round(sum(i.score for i in movies) / len(movies), 1)


def most_film_directors(directors):
cnt = Counter()
for director, movies in directors.items():
cnt[director] += len(movies[1])
return cnt.most_common(5)


def print_results(directors):
"""Print directors ordered by highest average rating. For each director
print his/her movies also ordered by highest rated movie.
See http://pybit.es/codechallenge13.html for example output"""
directors = dict(list(directors.items())[:20])
fmt_director_entry = "{counter:02}. {director:<52} {avg}"
fmt_movie_entry = "{year} {title:<50} {score}"
sep_line = "-" * 60
for count, (director, movies) in enumerate(directors.items(), 1):
print(
fmt_director_entry.format(
counter=count, director=director, avg=movies.score
)
)
print(sep_line)
for title, year, score in sorted(
movies.movies, key=lambda x: x[2], reverse=True
):
print(fmt_movie_entry.format(year=year, title=title, score=score))
print("\n")


def main():
"""This is a template, feel free to structure your code differently.
We wrote some tests based on our solution: test_directors.py"""
directors = get_movies_by_director()
directors = get_average_scores(directors)
# pprint(most_film_directors(directors))
print_results(directors)


if __name__ == "__main__":
main()
5,044 changes: 5,044 additions & 0 deletions 13/pavelekshin/movie_metadata.csv

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions 13/pavelekshin/test_directors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from directors import get_movies_by_director, get_average_scores, _calc_mean


def test():
directors = get_movies_by_director()

assert "Sergio Leone" in directors
assert "Andrew Stanton" in directors # has 3 movies, but not yet filtered
assert len(directors["Sergio Leone"]) == 4
assert len(directors["Peter Jackson"]) == 12

movies_sergio = directors["Sergio Leone"]
movies_nolan = directors["Christopher Nolan"]
assert _calc_mean(movies_sergio) == 8.5
assert _calc_mean(movies_nolan) == 8.4

directors = get_average_scores(directors)
assert "Andrew Stanton" not in directors # director 3 movies now filtered out

expected_directors = [
"Sergio Leone",
"Christopher Nolan",
"Quentin Tarantino",
"Hayao Miyazaki",
"Frank Darabont",
"Stanley Kubrick",
]
expected_avg_scores = [8.5, 8.4, 8.2, 8.2, 8.0, 8.0]
expected_num_movies = [4, 8, 8, 4, 4, 7]
report = sorted(directors.items(), key=lambda x: float(x[1][0]), reverse=True)
for counter, (i, j, k) in enumerate(
zip(expected_directors, expected_avg_scores, expected_num_movies)
):
assert report[counter][0], [counter][1][0] == (i, j)
assert len(report[counter][1][1]) == k
assert _calc_mean(report[counter][1][1]) == j

return "tests pass"


if __name__ == "__main__":
print(test())
96 changes: 96 additions & 0 deletions 39/pavelekshin/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from flask import Flask, jsonify, abort, make_response, request

NOT_FOUND = "Not found"
BAD_REQUEST = "Bad request"

app = Flask(__name__)

items = [
{"id": 1, "name": "laptop", "value": 1000},
{
"id": 2,
"name": "chair",
"value": 300,
},
{
"id": 3,
"name": "book",
"value": 20,
},
]


def _get_item(id):
return [item for item in items if item["id"] == id]


def _record_exists(name):
return [item for item in items if item["name"] == name]


@app.errorhandler(404)
def not_found(error):
return make_response(jsonify({"error": NOT_FOUND}), 404)


@app.errorhandler(400)
def bad_request(error):
return make_response(jsonify({"error": BAD_REQUEST}), 400)


@app.route("/api/v1.0/items", methods=["GET"])
def get_items():
return jsonify({"items": items})


@app.route("/api/v1.0/items/<int:id>", methods=["GET"])
def get_item(id):
item = _get_item(id)
if not item:
abort(404)
return jsonify({"items": item})


@app.route("/api/v1.0/items", methods=["POST"])
def create_item():
if not request.json or "name" not in request.json or "value" not in request.json:
abort(400)
item_id = items[0].get("id") + 1
name = request.json.get("name")
if _record_exists(name):
abort(400)
value = request.json.get("value")
if type(value) is not int:
abort(400)
item = {"id": item_id, "name": name, "value": value}
items.append(item)
return jsonify({"item": item}), 201


@app.route("/api/v1.0/items/<int:id>", methods=["PUT"])
def update_item(id):
item = _get_item(id)
if len(item) == 0:
abort(404)
if not request.json:
abort(400)
name = request.json.get("name", item[0]["name"])
value = request.json.get("value", item[0]["value"])
if type(value) is not int:
abort(400)
item[0]["name"] = name
item[0]["value"] = value
return jsonify({"item": item[0]}), 200


@app.route("/api/v1.0/items/<int:id>", methods=["DELETE"])
def delete_item(id):
item = _get_item(id)
if len(item) == 0:
abort(404)
items.remove(item[0])
return jsonify({}), 204


if __name__ == "__main__":
app.run(debug=True)
38 changes: 38 additions & 0 deletions 39/pavelekshin/curl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import os

tests = """
# get items
curl -i http://127.0.0.1:5000/api/v1.0/items
# item 1
curl -i http://127.0.0.1:5000/api/v1.0/items/1
# item2
curl -i http://127.0.0.1:5000/api/v1.0/items/2
# err: non-existing item
curl -i http://127.0.0.1:5000/api/v1.0/items/4
# err: add item already in items
curl -i -H "Content-Type: application/json" -X POST -d '{"name":"book", "value": 20}' http://127.0.0.1:5000/api/v1.0/items
# err: add item with value not int
curl -i -H "Content-Type: application/json" -X POST -d '{"name":"monitor", "value": "200"}' http://127.0.0.1:5000/api/v1.0/items
# add item with proper values
curl -i -H "Content-Type: application/json" -X POST -d '{"name":"monitor", "value": 200}' http://127.0.0.1:5000/api/v1.0/items
# show items
curl -i http://127.0.0.1:5000/api/v1.0/items
# err: edit non-existing item
curl -i -H "Content-Type: application/json" -X PUT -d '{"value": 30}' http://127.0.0.1:5000/api/v1.0/items/5
# OK: edit existing item
curl -i -H "Content-Type: application/json" -X PUT -d '{"value": 30}' http://127.0.0.1:5000/api/v1.0/items/3
# show items
curl -i http://127.0.0.1:5000/api/v1.0/items
# err: delete non-existing item
curl -i -H "Content-Type: application/json" -X DELETE http://127.0.0.1:5000/api/v1.0/items/5
# OK: delete existing item
curl -i -H "Content-Type: application/json" -X DELETE http://127.0.0.1:5000/api/v1.0/items/3
# show items
curl -i http://127.0.0.1:5000/api/v1.0/items
"""

for line in tests.strip().split("\n"):
print("\n{}".format(line))
if not line.startswith("#"):
cmd = line.strip()
os.system(cmd)
130 changes: 130 additions & 0 deletions 39/pavelekshin/test_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import pytest
import json

from app import app

BASE_URL = "http://127.0.0.1:5000/api/v1.0/items"
BAD_ITEM_URL = "{}/5".format(BASE_URL)
GOOD_ITEM_URL = "{}/3".format(BASE_URL)


def setUp():
backup_items = deepcopy(app.items)
app = app.test_client()
assert app.testing == False


def test_get_items():
with app.test_client() as tc:
# get items
response = tc.get(BASE_URL)
assert response.status_code == 200
data = json.loads(response.data)
# items count in response
assert len(data["items"]) == 3


def test_get_item():
with app.test_client() as tc:
# get one non exist item
response = tc.get(BAD_ITEM_URL)
assert response.status_code == 404
# get one exist item
response = tc.get(GOOD_ITEM_URL)
data = json.loads(response.data)
assert response.status_code == 200
assert len(data["items"]) == 1


def test_not_found():
with app.test_client() as tc:
response = tc.get(BAD_ITEM_URL)
assert response.status_code == 404


def test_bad_request():
item = {"name": "laptop", "some-value": "1000"}
# item wrong request body
with app.test_client() as tc:
response = tc.post(
BASE_URL, data=json.dumps(item), content_type="application/json"
)
assert response.status_code == 400


def test_create_item():
with app.test_client() as tc:
# wrong request body
item = {"name": "laptop", "some-value": "1000"}
response = tc.post(
BASE_URL, data=json.dumps(item), content_type="application/json"
)
assert response.status_code == 400
# item exist
item = {"name": "laptop", "value": int(1)}
response = tc.post(
BASE_URL, data=json.dumps(item), content_type="application/json"
)
assert response.status_code == 400
# item wrong request value type
item = {"name": "monitor", "value": str(1)}
response = tc.post(
BASE_URL, data=json.dumps(item), content_type="application/json"
)
assert response.status_code == 400
# create item
item = {"name": "printer", "value": 100}
reponse = tc.post(
BASE_URL, data=json.dumps(item), content_type="application/json"
)


def test_update_item():
item = {"name": "laptop", "some-value": "1000"}
# item wrong request body
with app.test_client() as tc:
response = tc.post(
BASE_URL, data=json.dumps(item), content_type="application/json"
)
assert response.status_code == 400


def test_update_item():
with app.test_client() as tc:
# update non exist item
response = tc.put(BAD_ITEM_URL)
assert response.status_code == 404
# empty request body
response = tc.put(
GOOD_ITEM_URL, data=json.dumps({}), content_type="application/json"
)
assert response.status_code == 400
# request body contains str instead of int value
response = tc.put(
GOOD_ITEM_URL,
data=json.dumps({"name": "book", "value": str(10)}),
content_type="application/json",
)
assert response.status_code == 400
# correct request
response = tc.put(
GOOD_ITEM_URL,
data=json.dumps({"name": "book", "value": int(10)}),
content_type="application/json",
)
assert response.status_code == 200


def test_delete_item():
with app.test_client() as tc:
# delete non exist item
response = tc.delete(BAD_ITEM_URL)
assert response.status_code == 404
# delete exist item
response = tc.delete(GOOD_ITEM_URL)
assert response.status_code == 204


def setDown():
# reset state
app.items = backup_items