Skip to content

Commit 64b6436

Browse files
committed
initial commit
0 parents  commit 64b6436

File tree

3 files changed

+288
-0
lines changed

3 files changed

+288
-0
lines changed

README.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Simple REST API with pure Python
2+
3+
This is an example REST API project.
4+
5+
## Run
6+
```
7+
# python server.py
8+
```
9+
10+
Default port is 5000, you can change it in `server.py`.
11+
12+
## Index
13+
```
14+
$ curl "http://127.0.0.1:5000/"
15+
{
16+
"name": "Python REST API Example",
17+
"summary": "This is simple REST API architecture with pure Python",
18+
"actions": [
19+
"add",
20+
"delete",
21+
"list",
22+
"search"
23+
],
24+
"version": "1.0.0"
25+
}
26+
```
27+
28+
## List Items
29+
```
30+
$ curl "http://127.0.0.1:5000/list"
31+
{
32+
"count": 3,
33+
"items": [
34+
{
35+
"id": 1000,
36+
"name": "cat",
37+
"description": "cat is meowing"
38+
},
39+
{
40+
"id": 1001,
41+
"name": "dog",
42+
"description": "dog is barking"
43+
},
44+
{
45+
"id": 1002,
46+
"name": "bird",
47+
"description": "bird is singing"
48+
}
49+
]
50+
}
51+
```
52+
53+
## Search
54+
```
55+
$ curl "http://127.0.0.1:5000/search?q=d"
56+
{
57+
"count": 2,
58+
"items": [
59+
{
60+
"id": 1001,
61+
"name": "dog",
62+
"description": "dog is barking"
63+
},
64+
{
65+
"id": 1002,
66+
"name": "bird",
67+
"description": "bird is singing"
68+
}
69+
]
70+
}
71+
```
72+
73+
## Delete Item
74+
```
75+
$ curl "http://127.0.0.1:5000/delete" -H "Content-Type: application/json" -d '{"id": 1001}'
76+
{
77+
"deleted": 1001
78+
}
79+
```
80+
81+
## Add Item
82+
```
83+
$ curl "http://127.0.0.1:5000/add" -H "Content-Type: application/json" \
84+
> -d '{"name": "fish", "description": "fish is swimming"}'
85+
{
86+
"id": 1005,
87+
"name": "fish",
88+
"description": "fish is swimming"
89+
}
90+
```
91+
92+
## List Again
93+
```
94+
$ curl "http://127.0.0.1:5000/list"
95+
{
96+
"count": 3,
97+
"items": [
98+
{
99+
"id": 1000,
100+
"name": "cat",
101+
"description": "cat is meowing"
102+
},
103+
{
104+
"id": 1002,
105+
"name": "bird",
106+
"description": "bird is singing"
107+
},
108+
{
109+
"id": 1003,
110+
"name": "fish",
111+
"description": "fish is swimming"
112+
}
113+
]
114+
}
115+
```
116+
117+
118+
# License
119+
The Unlicense. Feel free to use or change it how you need.

UNLICENSE

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
This is free and unencumbered software released into the public domain.
2+
3+
Anyone is free to copy, modify, publish, use, compile, sell, or
4+
distribute this software, either in source code form or as a compiled
5+
binary, for any purpose, commercial or non-commercial, and by any
6+
means.
7+
8+
In jurisdictions that recognize copyright laws, the author or authors
9+
of this software dedicate any and all copyright interest in the
10+
software to the public domain. We make this dedication for the benefit
11+
of the public at large and to the detriment of our heirs and
12+
successors. We intend this dedication to be an overt act of
13+
relinquishment in perpetuity of all present and future rights to this
14+
software under copyright law.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22+
OTHER DEALINGS IN THE SOFTWARE.
23+
24+
For more information, please refer to <https://unlicense.org>

server.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import json
2+
from http.server import HTTPServer, BaseHTTPRequestHandler
3+
from urllib.parse import urlparse, parse_qs
4+
5+
PORT = 5000
6+
7+
class API():
8+
def __init__(self):
9+
self.routing = { "GET": { }, "POST": { } }
10+
11+
def get(self, path):
12+
def wrapper(fn):
13+
self.routing["GET"][path] = fn
14+
return wrapper
15+
16+
def post(self, path):
17+
def wrapper(fn):
18+
self.routing["POST"][path] = fn
19+
return wrapper
20+
21+
api = API()
22+
23+
example_data = {
24+
"items": [
25+
{ "id": 1000, "name": "cat", "description": "cat is meowing" },
26+
{ "id": 1001, "name": "dog", "description": "dog is barking" },
27+
{ "id": 1002, "name": "bird", "description": "bird is singing" }
28+
]
29+
}
30+
31+
@api.get("/")
32+
def index(_):
33+
return {
34+
"name": "Python REST API Example",
35+
"summary": "This is simple REST API architecture with pure Python",
36+
"actions": [ "add", "delete", "list", "search" ],
37+
"version": "1.0.0"
38+
}
39+
40+
41+
@api.get("/list")
42+
def list(_):
43+
return {
44+
"count": len(example_data["items"]),
45+
"items": example_data["items"]
46+
}
47+
48+
@api.get("/search")
49+
def search(args):
50+
q = args.get("q", None)
51+
52+
if q is None:
53+
return { "error": "q parameter required" }
54+
else:
55+
results = []
56+
for item in example_data["items"]:
57+
if item["name"].count(q) > 0:
58+
results.append(item)
59+
return { "count": len(results), "items": results }
60+
61+
62+
@api.post("/add")
63+
def add(args):
64+
id = example_data["items"].copy().pop()["id"] + 1
65+
name = args.get("name", None)
66+
description = args.get("description", None)
67+
68+
if name is None or description is None:
69+
return { "error": "name and description are required parameters" }
70+
else:
71+
item = { "id": id, "name": name, "description": description }
72+
example_data["items"].append(item)
73+
return item
74+
75+
76+
@api.post("/delete")
77+
def delete(args):
78+
id = args.get("id", None)
79+
if id is None:
80+
return { "error": "id parameter required" }
81+
else:
82+
item_deleted = False
83+
84+
for item in example_data["items"]:
85+
if item["id"] == id:
86+
example_data["items"].remove(item)
87+
item_deleted = True
88+
break
89+
90+
if item_deleted:
91+
return { "deleted": id }
92+
else:
93+
return { "error": f"item not found with id {id}" }
94+
95+
96+
if __name__ == "__main__":
97+
class ApiRequestHandler(BaseHTTPRequestHandler):
98+
global api
99+
100+
def call_api(self, method, path, args):
101+
if path in api.routing[method]:
102+
try:
103+
result = api.routing[method][path](args)
104+
self.send_response(200)
105+
self.end_headers()
106+
self.wfile.write(json.dumps(result, indent=4).encode())
107+
except Exception as e:
108+
self.send_response(500, "Server Error")
109+
self.end_headers()
110+
self.wfile.write(json.dumps({ "error": e.args }, indent=4).encode())
111+
else:
112+
self.send_response(404, "Not Found")
113+
self.end_headers()
114+
self.wfile.write(json.dumps({"error": "not found"}, indent=4).encode())
115+
116+
def do_GET(self):
117+
parsed_url = urlparse(self.path)
118+
path = parsed_url.path
119+
args = parse_qs(parsed_url.query)
120+
121+
for k in args.keys():
122+
if len(args[k]) == 1:
123+
args[k] = args[k][0]
124+
125+
self.call_api("GET", path, args)
126+
127+
def do_POST(self):
128+
parsed_url = urlparse(self.path)
129+
path = parsed_url.path
130+
if self.headers.get("content-type") != "application/json":
131+
self.send_response(400)
132+
self.end_headers()
133+
self.wfile.write(json.dumps({
134+
"error": "posted data must be in json format"
135+
}, indent=4).encode())
136+
else:
137+
data_len = int(self.headers.get("content-length"))
138+
data = self.rfile.read(data_len).decode()
139+
self.call_api("POST", path, json.loads(data))
140+
141+
142+
httpd = HTTPServer(('', PORT), ApiRequestHandler)
143+
print(f"Application started at http://127.0.0.1:{PORT}/")
144+
httpd.serve_forever()
145+

0 commit comments

Comments
 (0)