Skip to content

Commit 6513321

Browse files
authored
rest-api: implement exercise (exercism#1431)
* implement exercise, add to config.json * add README
1 parent 5b1178c commit 6513321

5 files changed

Lines changed: 519 additions & 0 deletions

File tree

config.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,6 +1366,17 @@
13661366
"strings"
13671367
]
13681368
},
1369+
{
1370+
"slug": "rest-api",
1371+
"uuid": "2abe6eed-c7e4-4ad7-91e3-778bc5176726",
1372+
"core": true,
1373+
"unlocked_by": null,
1374+
"difficulty": 8,
1375+
"topics": [
1376+
"parsing",
1377+
"strings"
1378+
]
1379+
},
13691380
{
13701381
"slug": "accumulate",
13711382
"uuid": "e7351e8e-d3ff-4621-b818-cd55cf05bffd",

exercises/rest-api/README.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Rest Api
2+
3+
Implement a RESTful API for tracking IOUs.
4+
5+
Four roommates have a habit of borrowing money from each other frequently, and have trouble remembering who owes whom, and how much.
6+
7+
Your task is to implement a simple [RESTful API](https://en.wikipedia.org/wiki/Representational_state_transfer) that receives [IOU](https://en.wikipedia.org/wiki/IOU)s as POST requests, and can deliver specified summary information via GET requests.
8+
9+
### API Specification
10+
11+
#### User object
12+
```json
13+
{
14+
"name": "Adam",
15+
"owes": {
16+
"Bob": 12.0,
17+
"Chuck": 4.0,
18+
"Dan": 9.5
19+
},
20+
"owed_by": {
21+
"Bob": 6.5,
22+
"Dan": 2.75,
23+
},
24+
"balance": "<(total owed by other users) - (total owed to other users)>"
25+
}
26+
```
27+
28+
#### Methods
29+
30+
| Description | HTTP Method | URL | Payload Format | Response w/o Payload | Response w/ Payload |
31+
| --- | --- | --- | --- | --- | --- |
32+
| List of user information | GET | /users | `{"users":["Adam","Bob"]}` | `{"users":<List of all User objects>}` | {"users":\<List of User objects for \<users\>\>} |
33+
| Create user | POST | /add | {"user":\<name of new user (unique)} | N/A | \<User object for new user\> |
34+
| Create IOU | POST | /iou | {"lender":\<name of lender\>,"borrower":\<name of borrower>,"amount":5.25} | N/A | {"users":\<updated User objects for \<lender\> and \<borrower\>\>} |
35+
36+
### Other Resources:
37+
- https://restfulapi.net/
38+
- Example RESTful APIs
39+
- [GitHub](https://developer.github.com/v3/)
40+
- [Reddit](https://www.reddit.com/dev/api/)
41+
## Exception messages
42+
43+
Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to
44+
indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not
45+
every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include
46+
a message.
47+
48+
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
49+
`raise Exception`, you should write:
50+
51+
```python
52+
raise Exception("Meaningful message indicating the source of the error")
53+
```
54+
55+
## Running the tests
56+
57+
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
58+
59+
- Python 2.7: `py.test rest_api_test.py`
60+
- Python 3.4+: `pytest rest_api_test.py`
61+
62+
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
63+
`python -m pytest rest_api_test.py`
64+
65+
### Common `pytest` options
66+
67+
- `-v` : enable verbose output
68+
- `-x` : stop running tests on first failure
69+
- `--ff` : run failures from previous test before running other test cases
70+
71+
For other options, see `python -m pytest -h`
72+
73+
## Submitting Exercises
74+
75+
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/rest-api` directory.
76+
77+
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
78+
79+
For more detailed information about running tests, code style and linting,
80+
please see [Running the Tests](http://exercism.io/tracks/python/tests).
81+
82+
## Submitting Incomplete Solutions
83+
84+
It's possible to submit an incomplete solution so you can see how others have completed the exercise.

exercises/rest-api/example.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import json
2+
3+
4+
class RestAPI(object):
5+
def __init__(self, database=None):
6+
self.database = database or {'users': []}
7+
8+
def update(self):
9+
for user in self.database['users']:
10+
owed_by = user['owed_by']
11+
owes = user['owes']
12+
for debtor in list(owed_by.keys()):
13+
if debtor in owes:
14+
diff = 0
15+
if debtor in owes:
16+
diff = owes[debtor]
17+
del owes[debtor]
18+
if debtor in owed_by:
19+
diff -= owed_by[debtor]
20+
del owed_by[debtor]
21+
if diff > 0:
22+
owes[debtor] = diff
23+
elif diff < 0:
24+
owed_by[debtor] = -diff
25+
user['balance'] = sum(owed_by.values()) - sum(owes.values())
26+
27+
def get(self, url, payload=None):
28+
if payload is not None:
29+
payload = json.loads(payload)
30+
if url == '/users':
31+
if payload is None:
32+
return json.dumps(self.database)
33+
else:
34+
return json.dumps({
35+
'users': [
36+
u for u in self.database['users']
37+
if u['name'] in payload['users']
38+
]
39+
})
40+
41+
def post(self, url, payload=None):
42+
result = None
43+
if payload is not None:
44+
payload = json.loads(payload)
45+
if url == '/add':
46+
if payload is not None:
47+
name = payload['user']
48+
users = self.database['users']
49+
user = None
50+
for u in users:
51+
if u['name'] == name:
52+
user = u
53+
break
54+
if user is None:
55+
new_user = {
56+
'name': name,
57+
'owes': {},
58+
'owed_by': {},
59+
'balance': 0
60+
}
61+
users.append(new_user)
62+
self.update()
63+
result = json.dumps(new_user)
64+
elif url == '/iou':
65+
if payload is not None:
66+
lender_name = payload['lender']
67+
borrower_name = payload['borrower']
68+
amount = payload['amount']
69+
lender = borrower = None
70+
for u in self.database['users']:
71+
if u['name'] == lender_name:
72+
lender = u
73+
elif u['name'] == borrower_name:
74+
borrower = u
75+
if lender is not None and borrower is not None:
76+
lender['owed_by'].setdefault(borrower_name, 0)
77+
lender['owed_by'][borrower_name] += amount
78+
borrower['owes'].setdefault(lender_name, 0)
79+
borrower['owes'][lender_name] += amount
80+
self.update()
81+
result = self.get(
82+
'/users',
83+
json.dumps({'users': [lender_name, borrower_name]})
84+
)
85+
return result

exercises/rest-api/rest_api.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class RestAPI(object):
2+
def __init__(self, database=None):
3+
pass
4+
5+
def get(self, url, payload=None):
6+
pass
7+
8+
def post(self, url, payload=None):
9+
pass

0 commit comments

Comments
 (0)