?????????????????????????????????????????
████████╗ █████╗ ███╗ ██╗ ██████╗ ███████╗██████╗ ██╗███╗ ██╗███████╗
╚══██╔══╝██╔══██╗████╗ ██║██╔════╝ ██╔════╝██╔══██╗██║████╗ ██║██╔════╝
██║ ███████║██╔██╗ ██║██║ ███╗█████╗ ██████╔╝██║██╔██╗ ██║█████╗
██║ ██╔══██║██║╚██╗██║██║ ██║██╔══╝ ██╔══██╗██║██║╚██╗██║██╔══╝
██║ ██║ ██║██║ ╚████║╚██████╔╝███████╗██║ ██║██║██║ ╚████║███████╗
╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚══════╝
????????????????????????????????????????
Tangerine Discord: https://discord.gg/abzmthch
¡Gracias por visitar Tangerine! Este es un nuevo marco de Python en el que estoy trabajando. Es casi a la versión beta. Comencé a escribirlo porque Flask no admitía las funciones de View Async en ese momento. Ahora lo hacen a partir de 2.0, pero aún no están fuera de la caja. Algunos de los objetivos de este marco:
Ser el marco web de Python más fácil para comprender para los desarrolladores que provienen de JavaScript.
Para que la API base sea extremadamente ligera y no opinionada, pero también tiene extensiones opcionales que funcionarán fuera de la caja a medida que las necesite. Básicamente, puede iniciar una API extremadamente liviana para servir sitios estáticos en unos minutos, pero también existe la opción de descargar complementos que ofrecen una CLI totalmente funcionada y otras herramientas que lo harían funcionar más como Ruby en Rails o Django. En este momento hay un módulo de autenticación extremadamente fácil de usar que le permite usar un algoritmo de hash predeterminado o intercambiar para usar el suyo con solo unas pocas líneas de código.
Apoya a Async/espera fuera de la caja.
Este proyecto no alcanza todos estos objetivos en su estado actual, pero estoy trabajando en ello. Estoy publicando esto aquí en caso de que pueda haber alguna gente interesada en contribuir para que podamos publicar con confianza una versión beta que funcione a PYPI pronto. Que tengas un hermoso día :)
También estoy trabajando en una herramienta CLI para esto. Sigue haciendo esqueletos/experimentando con la arquitectura básica y los módulos que quiero usar. El trabajo de configuración actual está bajo la rama de arquitectura. Hay otros repos asociados con esto: Bergamot, la mano de Buda y las limas clave.
Aquí están los enlaces a aquellos: https://github.com/nora-july-stoke/key-limes https://github.com/nora-july-stoke/buddhas-hand
I use emojis for commit messages. Here's what they mean:
# for updating any readmes or roadmaps
: <relevant comment>:
# for new features
: <relevant comment>:
# for debug commits.
?? : <relevant comment>:
# for refactoring
? : <relevant comment>:
# for documentation
? : <relevant comment>
# branch initialization commit
? : <relevant comment>
# standard merge message
? : <relevant comment>
from tangerine import Tangerine , Ctx , Router
from pymongo import MongoClient
from key_lime import KeyLime
from yuzu import Yuzu
import json
import jwt
app = Tangerine ()
client = MongoClient ( 'mongodb://localhost:27017/' )
keychain = KeyLime ({
"SECRET_KEY" : "ILOVECATS" ,
})
# Initialize Yuzu with the db funcs.
def get_user_by_email ( email ):
db = client [ 'mydatabase' ]
users = db [ 'users' ]
query = { 'email' : email }
user = users . find_one ( query )
if user :
user [ '_id' ] = str ( user [ '_id' ]) # Convert ObjectId to string
return user
def create_user ( user_data ):
db = client [ 'mydatabase' ]
users = db [ 'users' ]
result = users . insert_one ( user_data )
if result . inserted_id :
user_data [ '_id' ] = str ( result . inserted_id ) # Convert ObjectId to string
return user_data
auth = Yuzu ( keychain , get_user_by_email , create_user )
# serve static files to any request not starting with /api
app . static ( '^/(?!api).*$' , './public' )
# This is how you define a custom middleware.
def hello_middle ( ctx : Ctx , next ) -> None :
print ( "Hello from middleware!" )
ctx . hello_message = json . dumps ({ "message" : "Hello from middleware!" })
next ()
# ==================== AUTH HANDLERS ====================
def api_hello_world ( ctx : Ctx ) -> None :
ctx . body = ctx . hello_message
ctx . send ( 200 , content_type = 'application/json' )
def signup ( ctx : Ctx ) -> None :
user_data = ctx . request . body
created_user = auth . sign_up ( user_data )
if created_user :
ctx . body = json . dumps ( created_user )
ctx . send ( 201 , content_type = 'application/json' )
else :
ctx . send ( 500 , content_type = 'application/json' )
def login ( ctx : Ctx ) -> None :
user_data = ctx . request . body
email = user_data [ 'email' ]
password = user_data [ 'password' ]
user_id , token = auth . login ( email , password )
if token :
ctx . body = json . dumps ({ "message" : "Logged in successfully" , "token" : token })
ctx . set_res_header ( "Set-Cookie" , f"auth_token= { token } ; HttpOnly; Path=/" )
ctx . send ( 200 , content_type = 'application/json' )
# Set the token as a cookie or in the response headers
else :
ctx . body = json . dumps ({ "message" : "Invalid credentials" })
ctx . send ( 401 , content_type = 'application/json' )
def logout ( ctx : Ctx ) -> None :
auth . logout ()
ctx . body = json . dumps ({ "message" : "Logged out successfully" })
ctx . send ( 200 , content_type = 'application/json' )
@ Router . auth_required
def get_protected_content ( ctx : Ctx ) -> None :
ctx . body = json . dumps ({ "message" : "This is protected content. Only authenticated users can see this. I hope you feel special ???." })
ctx . send ( 200 , content_type = 'application/json' )
# ==================== API ROUTES ====================
# if you need to bind more variables to your handler, you can pass in a closure
api_router = Router ( prefix = '/api' )
api_router . post ( '/logout' , logout )
api_router . post ( '/login' , login )
api_router . post ( '/signup' , signup )
api_router . get ( '/hello' , api_hello_world )
# api_router.get('/users', get_and_delete_users)
api_router . get ( '/protected' , get_protected_content )
app . use ( hello_middle )
app . use ( auth . jwt_middleware )
app . use_router ( api_router )
app . start () from tangerine import Tangerine , Ctx , Router
from key_lime import KeyLime
from yuzu import Yuzu
import json
import jwt
import psycopg2
# ==================== If you don't have schema and tables this part will set that up for you ====================
conn = psycopg2 . connect ( "postgresql://postgres:<POSTGRESPASSWORD>@localhost:5432/local_development" )
# Open a cursor to perform database operations
cur = conn . cursor ()
# Create schema if it's not there
cur . execute ( """
CREATE SCHEMA IF NOT EXISTS tangerine;
""" )
# Execute a command: this creates a new table named 'users'
cur . execute ( """
CREATE TABLE IF NOT EXISTS tangerine.users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL
)
""" )
# Commit the transaction
conn . commit ()
# Close the connection
cur . close ()
conn . close ()
# =================================================================================================================
app = Tangerine ()
keychain = KeyLime ({
"SECRET_KEY" : "ILOVECATS" ,
})
def get_user_by_email ( email ):
conn = psycopg2 . connect ( "postgresql://postgres:<POSTGRESPASSWORD>@localhost:5432/local_development" )
cur = conn . cursor ()
cur . execute ( "SELECT * FROM tangerine.users WHERE email = %s" , ( email ,))
user = cur . fetchone ()
cur . close ()
conn . close ()
if user :
return { '_id' : user [ 0 ], 'email' : user [ 1 ], 'password' : user [ 2 ]}
else :
return None
def create_user ( user_data ):
conn = psycopg2 . connect ( "postgresql://postgres:<POSTGRESPASSWORD>@localhost:5432/local_development" )
cur = conn . cursor ()
cur . execute ( "INSERT INTO tangerine.users (email, password) VALUES (%s, %s) RETURNING id" , ( user_data [ 'email' ], user_data [ 'password' ]))
user_id = cur . fetchone ()[ 0 ]
conn . commit ()
cur . close ()
conn . close ()
return { '_id' : user_id , 'email' : user_data [ 'email' ], 'password' : user_data [ 'password' ]}
auth = Yuzu ( keychain , get_user_by_email , create_user )
# serve static files to any request not starting with /api
app . static ( '^/(?!api).*$' , './public' )
def signup ( ctx : Ctx ) -> None :
user_data = ctx . request . body
created_user = auth . sign_up ( user_data )
if created_user :
ctx . body = json . dumps ( created_user )
ctx . send ( 201 , content_type = 'application/json' )
else :
ctx . send ( 500 , content_type = 'application/json' )
def login ( ctx : Ctx ) -> None :
user_data = ctx . request . body
email = user_data [ 'email' ]
password = user_data [ 'password' ]
user_id , token = auth . login ( email , password )
if token :
ctx . body = json . dumps ({ "message" : "Logged in successfully" , "token" : token })
ctx . set_res_header ( "Set-Cookie" , f"auth_token= { token } ; HttpOnly; Path=/" )
ctx . send ( 200 , content_type = 'application/json' )
else :
ctx . body = json . dumps ({ "message" : "Invalid credentials" })
ctx . send ( 401 , content_type = 'application/json' )
def logout ( ctx : Ctx ) -> None :
auth . logout ()
ctx . body = json . dumps ({ "message" : "Logged out successfully" })
ctx . send ( 200 , content_type = 'application/json' )
@ Router . auth_required
def get_protected_content ( ctx : Ctx ) -> None :
ctx . body = json . dumps ({ "message" : "This is protected content. Only authenticated users can see this. I hope you feel special ???." })
ctx . send ( 200 , content_type = 'application/json' )
# ==================== API ROUTES ====================
api_router = Router ( prefix = '/api' )
api_router . post ( '/logout' , logout )
api_router . post ( '/login' , login )
api_router . post ( '/signup' , signup )
api_router . get ( '/protected' , get_protected_content )
app . use ( auth . jwt_middleware )
app . use_router ( api_router )
app . start ()
## More Details TBD
## This readme is a work in progress so keep an eye out for more documentation/outlines of the project.