melody-web 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: melody-web
3
+ Version: 0.1.1
4
+ Summary: A Python web framework to quickly build web apps
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: uvicorn
7
+ Requires-Dist: werkzeug
8
+ Requires-Dist: beautifulsoup4
9
+ Requires-Dist: Jinja2
10
+ Requires-Dist: python-dotenv
11
+ Requires-Dist: peewee
@@ -0,0 +1,31 @@
1
+ # Melody
2
+
3
+ Melody is a python based web framework designed to let users build websites as fast as possible, with almost no boilerplate code.
4
+
5
+ Till now, I have built-
6
+ 1. A Response and Request Class
7
+ 2. A very basic CLI to run and deploy the apps
8
+ 3. Dynamic routing with slugs (built on Werkzeug)
9
+ 4. Reading environment variables
10
+ 5. A basic sqlite3 ORM (built on peewee orm)
11
+
12
+ ## 1. How to build a simple app
13
+
14
+ You first need to install melody by
15
+ ```bash
16
+ pip install melody
17
+ ```
18
+
19
+ ```python
20
+ # routes.py
21
+ from .controllers import *
22
+
23
+ routes = [
24
+ '/': index,
25
+ 'POST /new': new
26
+ ]
27
+ ```
28
+
29
+ This is the basic snippet for running the code. You can either create a new file for storing the controllers, or define them in the routes.py itself.
30
+
31
+ To run your app, you can
@@ -0,0 +1,5 @@
1
+ from .request import Request
2
+ from .render import render
3
+ from .response import Response, redirect
4
+ from .cookie import cookie
5
+ from .environment import env
@@ -0,0 +1,66 @@
1
+ import json
2
+ import importlib
3
+ from bs4 import BeautifulSoup
4
+ from melody.request import Request
5
+ from .response import parse_response
6
+
7
+ routes_import = importlib.import_module('routes')
8
+ print(routes_import.routes)
9
+ routes = routes_import.routes
10
+
11
+ from werkzeug.routing import Map, Rule
12
+
13
+ route_map = Map([
14
+ Rule(route.split(' ')[1] if ' ' in route else route, endpoint= route, methods=[route.split(' ')[0]] if ' ' in route else None) for route in routes.keys()
15
+ ])
16
+
17
+ def is_html(text):
18
+ return bool(BeautifulSoup(text, "html.parser").find())
19
+
20
+ async def app(scope, receive, send):
21
+ adapter = route_map.bind('http://127.0.0.1:8000/')
22
+ if scope['type'] != 'http':
23
+ return
24
+
25
+ path = scope['path']
26
+ try:
27
+ endpoint, params = adapter.match(path, method=scope['method'])
28
+ except:
29
+ await send({
30
+ "type": "http.response.start",
31
+ "status": 404,
32
+ "headers": [
33
+ [b"content-type", b"text/plain"],
34
+ ],
35
+ })
36
+
37
+ await send({
38
+ "type": "http.response.body",
39
+ "body": b"Not Found",
40
+ })
41
+ return
42
+
43
+ data = routes[endpoint](Request(scope, params))
44
+ res_data, res_type, status, res_headers, cookies = parse_response(data)
45
+ headers = [[b"content-type", res_type]]
46
+ print('res', res_headers)
47
+
48
+ for key, value in res_headers.items():
49
+ print('p', key)
50
+ headers.append([key.lower().encode(), str(value).encode()])
51
+
52
+ for value in cookies:
53
+ headers.append(['Set-Cookie', value])
54
+
55
+ await send({
56
+ "type": "http.response.start",
57
+ "status": status,
58
+ "headers": headers
59
+ })
60
+
61
+ await send({
62
+ "type": "http.response.body",
63
+ "body": res_data,
64
+ })
65
+ return
66
+
@@ -0,0 +1,27 @@
1
+ import sys
2
+ from pathlib import Path
3
+
4
+ root = Path.cwd()
5
+ sys.path.insert(0, str(root))
6
+ # :this: it tells the application.py to look for routes.py in the cwd of where the cmd was executed
7
+
8
+ def main():
9
+ command = sys.argv[1] if len(sys.argv) > 1 else None
10
+
11
+ port = 8000
12
+
13
+ if "--port" in sys.argv:
14
+ index = sys.argv.index("--port")
15
+ port = int(sys.argv[index + 1])
16
+
17
+ if command == 'run':
18
+ from melody.server import run
19
+ run(port=port)
20
+ if command == 'deploy':
21
+ from melody.server import run
22
+ run(port=port, reload=False)
23
+ # if sys.argv[1] == 'new':
24
+
25
+
26
+ if __name__ == '__main__':
27
+ main()
@@ -0,0 +1,39 @@
1
+ def cookie(
2
+ name,
3
+ value,
4
+ max_age=None,
5
+ expires=None,
6
+ domain=None,
7
+ path="/",
8
+ secure=False,
9
+ httponly=False,
10
+ samesite=None,
11
+ partitioned=False,
12
+ ):
13
+ parts = [f"{name}={value}"]
14
+
15
+ if max_age is not None:
16
+ parts.append(f"Max-Age={max_age}")
17
+
18
+ if expires is not None:
19
+ parts.append(f"Expires={expires}")
20
+
21
+ if domain is not None:
22
+ parts.append(f"Domain={domain}")
23
+
24
+ if path is not None:
25
+ parts.append(f"Path={path}")
26
+
27
+ if secure:
28
+ parts.append("Secure")
29
+
30
+ if httponly:
31
+ parts.append("HttpOnly")
32
+
33
+ if samesite is not None:
34
+ parts.append(f"SameSite={samesite}")
35
+
36
+ if partitioned:
37
+ parts.append("Partitioned")
38
+
39
+ return "; ".join(parts)
@@ -0,0 +1 @@
1
+ from .table import Database, Table, CharField, IntegerField, BooleanField
@@ -0,0 +1,26 @@
1
+ from peewee import ModelBase, SqliteDatabase, Model, CharField, IntegerField, BooleanField
2
+
3
+
4
+ class Database:
5
+ current = None
6
+
7
+ def __init__(self, name="sqlite3.db"):
8
+ self.db = SqliteDatabase(name)
9
+ Database.current = self
10
+
11
+ def register(self, tables):
12
+ self.db.create_tables(tables)
13
+
14
+
15
+ class TableMeta(ModelBase):
16
+ def __new__(cls, name, bases, attrs):
17
+ table = super().__new__(cls, name, bases, attrs)
18
+
19
+ if Database.current:
20
+ table._meta.database = Database.current.db
21
+
22
+ return table
23
+
24
+
25
+ class Table(Model, metaclass=TableMeta):
26
+ pass
@@ -0,0 +1,7 @@
1
+ import os
2
+ import dotenv
3
+
4
+ dotenv.load_dotenv()
5
+
6
+ def env(name):
7
+ return os.getenv(name)
@@ -0,0 +1,7 @@
1
+ from jinja2 import Environment, FileSystemLoader
2
+
3
+ env = Environment(loader=FileSystemLoader('templates'))
4
+
5
+ def render(file, variables={}):
6
+ res = env.get_template(file).render(variables)
7
+ return res
@@ -0,0 +1,19 @@
1
+ from urllib.parse import parse_qs
2
+
3
+ class Request:
4
+ def __init__(self, scope, params):
5
+ print(scope)
6
+ self.path = scope['path']
7
+ self.method = scope['method']
8
+ self.query = parse_qs(scope["query_string"].decode('utf-8'))
9
+ self.headers = {key.decode('latin-1'): value.decode('latin-1') for key, value in scope["headers"]}
10
+ self.params = params
11
+ self.scope = scope
12
+ self.cookies = {}
13
+
14
+ for cookie in self.headers.get('cookie', '').split(';'):
15
+ if cookie == '':
16
+ continue
17
+ print('cookie', cookie)
18
+ key, value = cookie.strip().split('=')
19
+ self.cookies[key] = value
@@ -0,0 +1,37 @@
1
+ import json
2
+ from bs4 import BeautifulSoup
3
+
4
+ def is_html(text):
5
+ return bool(BeautifulSoup(text, "html.parser").find())
6
+
7
+ class Response:
8
+ def __init__(self, data, response_type=None, status=200, headers={}, cookies={}):
9
+ if response_type is not None:
10
+ self.response_type = str(response_type).encode()
11
+ self.response_data = (
12
+ data if isinstance(data, bytes)
13
+ else str(data).encode()
14
+ )
15
+ elif isinstance(data, dict):
16
+ self.response_data = json.dumps(data).encode()
17
+ self.response_type = b"application/json"
18
+ elif is_html(data):
19
+ self.response_data = str(data).encode()
20
+ self.response_type=b"text/html"
21
+ else:
22
+ self.response_data = str(data).encode()
23
+ self.response_type = b"text/plain"
24
+ self.status = status
25
+ self.headers = headers
26
+ self.cookies = cookies
27
+
28
+ def parse_response(response):
29
+ print('type', type(response))
30
+ if isinstance(response, Response):
31
+ return response.response_data, response.response_type, response.status, response.headers, response.cookies
32
+ temp_response = Response(response)
33
+ print('t', temp_response.cookies)
34
+ return temp_response.response_data, temp_response.response_type, temp_response.status, temp_response.headers, temp_response.cookies
35
+
36
+ def redirect(path):
37
+ return Response('', status=302, headers={'Location': path})
@@ -0,0 +1,9 @@
1
+ import uvicorn
2
+
3
+ def run(port=8000, reload=True):
4
+ uvicorn.run(
5
+ "melody.application:app",
6
+ host="127.0.0.1",
7
+ port=port,
8
+ reload=reload
9
+ )
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: melody-web
3
+ Version: 0.1.1
4
+ Summary: A Python web framework to quickly build web apps
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: uvicorn
7
+ Requires-Dist: werkzeug
8
+ Requires-Dist: beautifulsoup4
9
+ Requires-Dist: Jinja2
10
+ Requires-Dist: python-dotenv
11
+ Requires-Dist: peewee
@@ -0,0 +1,19 @@
1
+ README.md
2
+ pyproject.toml
3
+ melody/__init__.py
4
+ melody/application.py
5
+ melody/cli.py
6
+ melody/cookie.py
7
+ melody/environment.py
8
+ melody/render.py
9
+ melody/request.py
10
+ melody/response.py
11
+ melody/server.py
12
+ melody/db/__init__.py
13
+ melody/db/table.py
14
+ melody_web.egg-info/PKG-INFO
15
+ melody_web.egg-info/SOURCES.txt
16
+ melody_web.egg-info/dependency_links.txt
17
+ melody_web.egg-info/entry_points.txt
18
+ melody_web.egg-info/requires.txt
19
+ melody_web.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ melody = melody.cli:main
@@ -0,0 +1,6 @@
1
+ uvicorn
2
+ werkzeug
3
+ beautifulsoup4
4
+ Jinja2
5
+ python-dotenv
6
+ peewee
@@ -0,0 +1 @@
1
+ melody
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "melody-web"
7
+ version = "0.1.1"
8
+ description = "A Python web framework to quickly build web apps"
9
+ requires-python = ">=3.10"
10
+
11
+ dependencies = [
12
+ "uvicorn",
13
+ "werkzeug",
14
+ "beautifulsoup4",
15
+ "Jinja2",
16
+ "python-dotenv",
17
+ "peewee",
18
+ ]
19
+
20
+ [project.scripts]
21
+ melody = "melody.cli:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+