-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
aff956f
commit 0c806b7
Showing
21 changed files
with
367 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# NANO : A Nano Web Framework | ||
|
||
Nano is an Express inspired nano web framework which is under development. Currently, it has features of adding routes, controllers and sending API responses (Refer the [example directory](example/) for reference). Defining models with custom field types is also available but is under development. | ||
|
||
**Status** : Under developement | ||
<br /> | ||
|
||
### To run example app | ||
```bash | ||
$ pip3 install -r requirements.txt | ||
|
||
$ gunicorn main:api | ||
``` | ||
|
||
## Documentation | ||
Refer the example app. Working on a better documentation |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from nano.handler import Response | ||
|
||
def index(req): | ||
return Response(status=200, data={"message": "Welocome to your first Nano app"}) | ||
|
||
def create_user(req): | ||
print(req.data) | ||
return Response(status=201, data=req.data) | ||
|
||
def get_user(req): | ||
return Response(status=200, data={"user": "User found"}) | ||
|
||
def delete_user(req): | ||
return Response(status=200, data={"message": "Deleted user"}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from nano.db import model | ||
|
||
class User(model.Model): | ||
username = model.StringType('username', max_length=50, min_length=4, unique=True, required=True) | ||
ph_no = model.IntegerType('ph_no', max=9999999999, min=9000000000) | ||
is_admin = model.BooleanType('is_admin', default=False) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from main import api | ||
from .controllers import index, create_user, get_user, delete_user | ||
|
||
api.router.get("/", index) | ||
api.router.get("/user", get_user) | ||
api.router.post("/user/create", create_user) | ||
api.router.get("/user/delete", delete_user) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from nano.app import Nano | ||
|
||
api = Nano() | ||
|
||
import example.routes |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from nano.utils import msg, warn, err | ||
from nano.handler import Router, Request, Response | ||
import os | ||
|
||
class Nano: | ||
def __init__(self): | ||
print(msg("RUN", "")) | ||
self.router = Router() | ||
self.APP_DIR = os.path.abspath(os.getcwd()) | ||
|
||
def __call__(self, environ, start_response): | ||
response = self.request_handler(environ) | ||
start_response(response.status_msg, response.headers) | ||
return [bytes(response.data, 'utf-8')] | ||
|
||
def __del__(self): | ||
print(msg("END", "")) | ||
|
||
def request_handler(self, request_environ): | ||
request = Request(request_environ) | ||
response = Response() | ||
found_url = False | ||
print(request) | ||
for i in range(len(self.router.urls)): | ||
if request.path == self.router.urls[i] and request.method == self.router.methods[i]: | ||
found_url = True | ||
response = self.router.controllers[i](request) | ||
if not found_url: | ||
response = Response(status=404) | ||
print(err("REQUEST", f"URL '{request.path}' not found in server router")) | ||
if not isinstance(response, Response): | ||
response = Response(status=500) | ||
print(err("RESPONSE", "The controller must return a Response object")) | ||
print(response) | ||
raise TypeError(warn("The controller must return a HTTP Response object")) | ||
print(response) | ||
return response |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .errors import ResponseTypeError, DBError |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
class _BaseError(Exception): | ||
pass | ||
|
||
class ResponseTypeError(_BaseError): | ||
pass | ||
|
||
class DBError(_BaseError): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .db import DB |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import sqlite3 | ||
from nano.utils import err | ||
|
||
class DB: | ||
def __init__(self, models): | ||
self.server = sqlite3.connect('database.db') | ||
self.admin = self.server.cursor() | ||
self.models = models | ||
for model in self.models: | ||
model.table_name = model.__name__.lower() | ||
|
||
def is_booted(self): | ||
is_booted = False | ||
for model in self.models: | ||
self.admin.execute( f"SELECT count(name) FROM sqlite_master WHERE type='table' AND name='{model.__name__.lower()}'") | ||
if self.admin.fetchone()[0]: | ||
is_booted = True | ||
return is_booted | ||
|
||
def boot(self): | ||
fields = "id INT PRIMARY KEY NOT NULL" | ||
for model in self.models: | ||
for field in model.get_fields(): | ||
fields = fields + ", " + field | ||
try: | ||
self.admin.execute( | ||
f"CREATE TABLE {model.__name__.lower()} ({fields})") | ||
except sqlite3.OperationalError: | ||
print(err("MODEL", f"Model {model.__name__} already booted")) | ||
fields = "id INT PRIMARY KEY NOT NULL" | ||
self.server.commit() | ||
|
||
def __del__(self): | ||
self.admin.close() | ||
self.server.close() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import sqlite3 | ||
from .prop_types import IntegerType, StringType, BooleanType | ||
|
||
class Model: | ||
|
||
table_name = str() | ||
fields = list() | ||
db_server = sqlite3.connect('database.db') | ||
db_admin = db_server.cursor() | ||
id = str() | ||
|
||
def db_handler(self, command): | ||
self.db_admin.execute(command) | ||
self.db_admin.commit() | ||
return self.db_admin.fetchone() | ||
|
||
def save(self): | ||
return self.db_handler(f"UPDATE {self.table_name} SET {self} WHERE id={self.id}") | ||
|
||
@classmethod | ||
def create(self, data): | ||
return self.db_handler(f"INSERT INTO {self.table_name} VALUES ({data})") | ||
|
||
@classmethod | ||
def find(self, id): | ||
return self.db_handler(f"SELECT * FROM {self.table_name} WHERE id={id}") | ||
|
||
@classmethod | ||
def update(self, id, data): | ||
return self.db_handler(f"UPDATE {self.table_name} SET {data} WHERE id={id}") | ||
|
||
@classmethod | ||
def delete(self, id): | ||
self.db_handler(f"DELETE FROM {self.table_name} WHERE id={id}") | ||
|
||
def test(self): | ||
print(self.table_name) | ||
|
||
@classmethod | ||
def get_fields(self): | ||
for field in self.__dict__: | ||
if ((field.startswith("__") and field.endswith("__")) | ||
or str(self.__dict__[field]).startswith("<classmethod") | ||
or str(self.__dict__[field]).startswith("<function")): | ||
pass | ||
else: | ||
self.fields.append(self.__dict__[field].command) | ||
print(self.fields) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
class _BaseType(): | ||
def __init__(self, model_type, name, unique, required, default): | ||
self.name = str(name).lower() | ||
self.unique = 'UNIQUE ' if unique else '' | ||
self.required = 'NOT NULL ' if required else '' # Default : False | ||
self.base_cmd = f"{self.name} {model_type} {self.required}{self.unique}" | ||
|
||
|
||
class IntegerType(_BaseType): | ||
def __init__(self, name="", max=10000000, min=-10000000, unique=False, required=False, default=None): | ||
super().__init__('INTEGER', name, unique, required, default) | ||
self.check = f"CHECK ({self.name} < {max} AND {self.name} > {min})" | ||
self.default = f"DEFAULT {default if default else 'NULL'} " | ||
self.command = self.base_cmd + self.default + self.check | ||
|
||
|
||
class StringType(_BaseType): | ||
def __init__(self, name="", max_length=None, min_length=0, unique=False, required=False, default=None): | ||
super().__init__('TEXT', name, unique, required, default) | ||
self.default = f"DEFAULT {default if default else 'NULL'} " | ||
self.check = f"CHECK (LEN({self.name}) < {max_length} AND LEN({self.name}) > {min_length})" | ||
self.command = self.base_cmd + self.check | ||
|
||
|
||
class BooleanType(_BaseType): | ||
def __init__(self, name="", unique=False, required=False, default=None): | ||
super().__init__('INTEGER', name, unique, required, default) | ||
self.default = f"DEFAULT {1 if default else 0} " | ||
self.command = self.base_cmd + self.default | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .router import Router | ||
from .request import Request | ||
from .response import Response |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from nano.utils import log | ||
|
||
class Request: | ||
|
||
def __init__(self, environ): | ||
self.path = environ['PATH_INFO'] | ||
self.method = environ['REQUEST_METHOD'] | ||
self.content_length = int(environ.get('CONTENT_LENGTH', 0)) | ||
self.data = environ['wsgi.input'].read(self.content_length).decode('utf-8') | ||
|
||
def __str__(self): | ||
return log("REQ", self.method, self.path) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from nano.utils import log | ||
from .status import get_status_text | ||
|
||
class Response: | ||
|
||
def __init__(self, status=200, data={}): | ||
self.status_code = int(status) | ||
self.status_text = str(get_status_text[self.status_code]) | ||
self.status_msg = str(self.status_code) + " " + self.status_text | ||
self.data = str(data) | ||
self.headers = [ | ||
('Content-Type', 'text/plain'), | ||
('Content-Length', str(len(self.data))) | ||
] | ||
|
||
def __str__(self): | ||
return log("RES", self.status_code, self.status_text) | ||
|
||
if __name__ == '__main__': | ||
test = Response(status=200) | ||
print(test) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
class Router: | ||
|
||
def __init__(self): | ||
self.urls = [] | ||
self.controllers = [] | ||
self.methods = [] | ||
|
||
def get(self, url: str, controller): | ||
self.urls.append(url) | ||
self.controllers.append(controller) | ||
self.methods.append('GET') | ||
|
||
def post(self, url: str, controller): | ||
self.urls.append(url) | ||
self.controllers.append(controller) | ||
self.methods.append('POST') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
get_status_text = { | ||
100: "CONTINUE", | ||
101: "SWITCHING PROTOCOLS", | ||
200: "OK", | ||
201: "CREATED", | ||
202: "ACCEPTED", | ||
203: "NON AUTHORITATIVE INFORMATION", | ||
204: "NO CONTENT", | ||
205: "RESET CONTENT", | ||
206: "PARTIAL CONTENT", | ||
207: "MULTI STATUS", | ||
208: "ALREADY REPORTED", | ||
226: "IM USED", | ||
300: "MULTIPLE CHOICES", | ||
301: "MOVED PERMANENTLY", | ||
302: "FOUND", | ||
303: "SEE OTHER", | ||
304: "NOT MODIFIED", | ||
305: "USE PROXY", | ||
306: "RESERVED", | ||
307: "TEMPORARY REDIRECT", | ||
308: "PERMANENT REDIRECT", | ||
400: "BAD REQUEST", | ||
401: "UNAUTHORIZED", | ||
402: "PAYMENT REQUIRED", | ||
403: "FORBIDDEN", | ||
404: "NOT FOUND", | ||
405: "METHOD NOT ALLOWED", | ||
406: "NOT ACCEPTABLE", | ||
407: "PROXY AUTHENTICATION REQUIRED", | ||
408: "REQUEST TIMEOUT", | ||
409: "CONFLICT", | ||
410: "GONE", | ||
411: "LENGTH REQUIRED", | ||
412: "PRECONDITION FAILED", | ||
413: "REQUEST ENTITY TOO LARGE", | ||
414: "REQUEST URI TOO LONG", | ||
415: "UNSUPPORTED MEDIA TYPE", | ||
416: "REQUESTED RANGE NOT SATISFIABLE", | ||
417: "EXPECTATION FAILED", | ||
418: "IM A TEAPOT", | ||
422: "UNPROCESSABLE ENTITY", | ||
423: "LOCKED", | ||
424: "FAILED DEPENDENCY", | ||
426: "UPGRADE REQUIRED", | ||
428: "PRECONDITION REQUIRED", | ||
429: "TOO MANY REQUESTS", | ||
431: "REQUEST HEADER FIELDS TOO LARGE", | ||
451: "UNAVAILABLE FOR LEGAL REASONS", | ||
500: "INTERNAL SERVER ERROR", | ||
501: "NOT IMPLEMENTED", | ||
502: "BAD GATEWAY", | ||
503: "SERVICE UNAVAILABLE", | ||
504: "GATEWAY TIMEOUT", | ||
505: "ION NOT SUPPORTED", | ||
506: "VARIANT ALSO NEGOTIATES", | ||
507: "INSUFFICIENT STORAGE", | ||
508: "LOOP DETECTED", | ||
509: "BANDWIDTH LIMIT EXCEEDED", | ||
510: "NOT EXTENDED", | ||
511: "NETWORK AUTHENTICATION REQUIRED" | ||
} | ||
|
||
|
||
def is_informational(code): | ||
return 100 <= code <= 199 | ||
|
||
|
||
def is_success(code): | ||
return 200 <= code <= 299 | ||
|
||
|
||
def is_redirect(code): | ||
return 300 <= code <= 399 | ||
|
||
|
||
def is_client_error(code): | ||
return 400 <= code <= 499 | ||
|
||
|
||
def is_server_error(code): | ||
return 500 <= code <= 599 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .logger import log, msg, warn, err |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from colorama import Fore, Style | ||
|
||
def err(field, err): | ||
return Fore.RED + Style.BRIGHT + "[ERR] " + Style.RESET_ALL + Fore.YELLOW + f"{field}: " + Style.RESET_ALL + f"{err}" | ||
|
||
def warn(msg): | ||
return Fore.YELLOW + msg + Style.RESET_ALL | ||
|
||
def msg(field, msg): | ||
return Fore.YELLOW + Style.BRIGHT + f"[{field}] " + Style.RESET_ALL + f"{msg}" | ||
|
||
def log(field, head, body): | ||
clr = Fore.GREEN if field == 'REQ' else Fore.BLUE | ||
return clr + Style.BRIGHT + f"[{field}] " + Style.RESET_ALL + Fore.YELLOW + f"{head}: " + Style.RESET_ALL + body | ||
|
||
if __name__ == '__main__': | ||
print(warn("Testing logging system")) | ||
print(log("REQ", "POST", "/test/url")) | ||
print(log("RES", 200, "OK")) | ||
print(err("TEST", "Testing logging system")) | ||
print(msg("Testing logging system")) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
colorama==0.4.4 | ||
gunicorn==20.0.4 |