├── src ├── models │ ├── __init__.py │ ├── UserModel.py │ └── LanguageModel.py ├── routes │ ├── __init__.py │ ├── IndexRoutes.py │ ├── LanguageRoutes.py │ └── AuthRoutes.py ├── tests │ ├── __init__.py │ └── services │ │ ├── __init__.py │ │ └── test_LanguageService.py ├── utils │ ├── log │ │ └── app.log │ ├── Logger.py │ └── Security.py ├── database │ ├── __init__.py │ └── db_mysql.py ├── services │ ├── __init__.py │ ├── AuthService.py │ └── LanguageService.py └── __init__.py ├── .gitignore ├── preview1.png ├── preview2.png ├── requirements.txt ├── .env ├── index.py ├── config.py ├── scripts ├── stored_procedures.sql └── flask_jwt_backup.sql └── README.md /src/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/routes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/log/app.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/database/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/services/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/tests/services/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | 3 | __pycache__/ 4 | *.py[cod] 5 | 6 | .pytest_cache/ -------------------------------------------------------------------------------- /preview1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UskoKruM/flask-folders-structure/HEAD/preview1.png -------------------------------------------------------------------------------- /preview2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UskoKruM/flask-folders-structure/HEAD/preview2.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UskoKruM/flask-folders-structure/HEAD/requirements.txt -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | SECRET_KEY=B!1w8*NAt1T^%kvhUI*S^_ 2 | 3 | MYSQL_HOST=localhost 4 | MYSQL_USER=root 5 | MYSQL_PASSWORD=123456 6 | MYSQL_DB=flask_jwt 7 | 8 | JWT_KEY=D5*F?_1?-d$f*1 -------------------------------------------------------------------------------- /index.py: -------------------------------------------------------------------------------- 1 | from config import config 2 | from src import init_app 3 | 4 | configuration = config['development'] 5 | app = init_app(configuration) 6 | 7 | if __name__ == '__main__': 8 | app.run() 9 | -------------------------------------------------------------------------------- /src/models/UserModel.py: -------------------------------------------------------------------------------- 1 | class User(): 2 | 3 | def __init__(self, id, username, password, fullname) -> None: 4 | self.id = id 5 | self.username = username 6 | self.password = password 7 | self.fullname = fullname 8 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | from decouple import config 2 | 3 | 4 | class Config(): 5 | SECRET_KEY = config('SECRET_KEY') 6 | 7 | 8 | class DevelopmentConfig(Config): 9 | DEBUG = True 10 | 11 | 12 | config = { 13 | 'development': DevelopmentConfig 14 | } 15 | -------------------------------------------------------------------------------- /src/models/LanguageModel.py: -------------------------------------------------------------------------------- 1 | class Language(): 2 | 3 | def __init__(self, id, name) -> None: 4 | self.id = id 5 | self.name = name 6 | 7 | def to_json(self): 8 | return { 9 | 'id': self.id, 10 | 'name': self.name 11 | } 12 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | # Routes 4 | from .routes import AuthRoutes, IndexRoutes, LanguageRoutes 5 | 6 | app = Flask(__name__) 7 | 8 | 9 | def init_app(config): 10 | # Configuration 11 | app.config.from_object(config) 12 | 13 | # Blueprints 14 | app.register_blueprint(IndexRoutes.main, url_prefix='/') 15 | app.register_blueprint(AuthRoutes.main, url_prefix='/auth') 16 | app.register_blueprint(LanguageRoutes.main, url_prefix='/languages') 17 | 18 | return app 19 | -------------------------------------------------------------------------------- /src/database/db_mysql.py: -------------------------------------------------------------------------------- 1 | from decouple import config 2 | 3 | import pymysql 4 | import traceback 5 | 6 | # Logger 7 | from src.utils.Logger import Logger 8 | 9 | 10 | def get_connection(): 11 | try: 12 | return pymysql.connect( 13 | host=config('MYSQL_HOST'), 14 | user=config('MYSQL_USER'), 15 | password=config('MYSQL_PASSWORD'), 16 | db=config('MYSQL_DB') 17 | ) 18 | except Exception as ex: 19 | Logger.add_to_log("error", str(ex)) 20 | Logger.add_to_log("error", traceback.format_exc()) 21 | -------------------------------------------------------------------------------- /src/tests/services/test_LanguageService.py: -------------------------------------------------------------------------------- 1 | # Services 2 | from src.services.LanguageService import LanguageService 3 | 4 | 5 | def test_get_languages_not_none(): 6 | languages = LanguageService.get_languages() 7 | assert languages != None 8 | 9 | 10 | def test_get_languages_has_elements(): 11 | languages = LanguageService.get_languages() 12 | assert len(languages) > 0 13 | 14 | 15 | def test_get_languages_check_elements_length(): 16 | languages = LanguageService.get_languages() 17 | for language in languages: 18 | assert len(language) > 0 19 | -------------------------------------------------------------------------------- /src/routes/IndexRoutes.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, jsonify, request 2 | 3 | import traceback 4 | 5 | # Logger 6 | from src.utils.Logger import Logger 7 | 8 | main = Blueprint('index_blueprint', __name__) 9 | 10 | 11 | @main.route('/') 12 | def index(): 13 | try: 14 | Logger.add_to_log("info", "{} {}".format(request.method, request.path)) 15 | return "Ok" 16 | except Exception as ex: 17 | Logger.add_to_log("error", str(ex)) 18 | Logger.add_to_log("error", traceback.format_exc()) 19 | 20 | response = jsonify({'message': "Internal Server Error", 'success': False}) 21 | return response, 500 22 | -------------------------------------------------------------------------------- /scripts/stored_procedures.sql: -------------------------------------------------------------------------------- 1 | -- Language 2 | DELIMITER // 3 | CREATE PROCEDURE sp_listLanguages() 4 | BEGIN 5 | SELECT LAN.id, LAN.name 6 | FROM language LAN 7 | ORDER BY LAN.name ASC; 8 | END // 9 | DELIMITER ; 10 | 11 | -- User 12 | DELIMITER // 13 | CREATE PROCEDURE sp_addUser(IN pUsername VARCHAR(20), IN pPassword VARCHAR(20), IN pFullname VARCHAR(50)) 14 | BEGIN 15 | INSERT INTO user (username, password, fullname) 16 | VALUES (pUsername, AES_ENCRYPT(pPassword, SHA2('B!1w8*NAt1T^%kvhUI*S^_', 512)), pFullname); 17 | END // 18 | DELIMITER ; 19 | 20 | DELIMITER // 21 | CREATE PROCEDURE sp_verifyIdentity(IN pUsername VARCHAR(20), IN pPassword VARCHAR(20)) 22 | BEGIN 23 | SELECT USER.id, USER.username, USER.fullname 24 | FROM user USER 25 | WHERE 1 = 1 26 | AND USER.username = pUsername 27 | AND CAST(AES_DECRYPT(USER.password, SHA2('B!1w8*NAt1T^%kvhUI*S^_', 512)) AS CHAR(30)) = pPassword; 28 | END // 29 | DELIMITER ; -------------------------------------------------------------------------------- /src/services/AuthService.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | 3 | # Database 4 | from src.database.db_mysql import get_connection 5 | # Logger 6 | from src.utils.Logger import Logger 7 | # Models 8 | from src.models.UserModel import User 9 | 10 | 11 | class AuthService(): 12 | 13 | @classmethod 14 | def login_user(cls, user): 15 | try: 16 | connection = get_connection() 17 | authenticated_user = None 18 | with connection.cursor() as cursor: 19 | cursor.execute('call sp_verifyIdentity(%s, %s)', (user.username, user.password)) 20 | row = cursor.fetchone() 21 | if row != None: 22 | authenticated_user = User(int(row[0]), row[1], None, row[2]) 23 | connection.close() 24 | return authenticated_user 25 | except Exception as ex: 26 | Logger.add_to_log("error", str(ex)) 27 | Logger.add_to_log("error", traceback.format_exc()) 28 | -------------------------------------------------------------------------------- /src/services/LanguageService.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | 3 | # Database 4 | from src.database.db_mysql import get_connection 5 | # Logger 6 | from src.utils.Logger import Logger 7 | # Models 8 | from src.models.LanguageModel import Language 9 | 10 | 11 | class LanguageService(): 12 | 13 | @classmethod 14 | def get_languages(cls): 15 | try: 16 | connection = get_connection() 17 | languages = [] 18 | with connection.cursor() as cursor: 19 | cursor.execute('call sp_listLanguages()') 20 | resultset = cursor.fetchall() 21 | for row in resultset: 22 | language = Language(int(row[0]), row[1]) 23 | languages.append(language.to_json()) 24 | connection.close() 25 | return languages 26 | except Exception as ex: 27 | Logger.add_to_log("error", str(ex)) 28 | Logger.add_to_log("error", traceback.format_exc()) 29 | -------------------------------------------------------------------------------- /src/routes/LanguageRoutes.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, request, jsonify 2 | 3 | import traceback 4 | 5 | # Logger 6 | from src.utils.Logger import Logger 7 | # Security 8 | from src.utils.Security import Security 9 | # Services 10 | from src.services.LanguageService import LanguageService 11 | 12 | main = Blueprint('language_blueprint', __name__) 13 | 14 | 15 | @main.route('/') 16 | def get_languages(): 17 | has_access = Security.verify_token(request.headers) 18 | 19 | if has_access: 20 | try: 21 | languages = LanguageService.get_languages() 22 | if (len(languages) > 0): 23 | return jsonify({'languages': languages, 'message': "SUCCESS", 'success': True}) 24 | else: 25 | return jsonify({'message': "NOTFOUND", 'success': True}) 26 | except Exception as ex: 27 | Logger.add_to_log("error", str(ex)) 28 | Logger.add_to_log("error", traceback.format_exc()) 29 | 30 | return jsonify({'message': "ERROR", 'success': False}) 31 | else: 32 | response = jsonify({'message': 'Unauthorized'}) 33 | return response, 401 34 | -------------------------------------------------------------------------------- /src/routes/AuthRoutes.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, request, jsonify 2 | 3 | import traceback 4 | 5 | # Logger 6 | from src.utils.Logger import Logger 7 | # Models 8 | from src.models.UserModel import User 9 | # Security 10 | from src.utils.Security import Security 11 | # Services 12 | from src.services.AuthService import AuthService 13 | 14 | main = Blueprint('auth_blueprint', __name__) 15 | 16 | 17 | @main.route('/', methods=['POST']) 18 | def login(): 19 | try: 20 | username = request.json['username'] 21 | password = request.json['password'] 22 | 23 | _user = User(0, username, password, None) 24 | authenticated_user = AuthService.login_user(_user) 25 | 26 | if (authenticated_user != None): 27 | encoded_token = Security.generate_token(authenticated_user) 28 | return jsonify({'success': True, 'token': encoded_token}) 29 | else: 30 | response = jsonify({'message': 'Unauthorized'}) 31 | return response, 401 32 | except Exception as ex: 33 | Logger.add_to_log("error", str(ex)) 34 | Logger.add_to_log("error", traceback.format_exc()) 35 | 36 | return jsonify({'message': "ERROR", 'success': False}) 37 | -------------------------------------------------------------------------------- /src/utils/Logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import traceback 4 | 5 | 6 | class Logger(): 7 | 8 | def __set_logger(self): 9 | log_directory = 'src/utils/log' 10 | log_filename = 'app.log' 11 | 12 | logger = logging.getLogger(__name__) 13 | logger.setLevel(logging.DEBUG) 14 | 15 | log_path = os.path.join(log_directory, log_filename) 16 | file_handler = logging.FileHandler(log_path, encoding='utf-8') 17 | file_handler.setLevel(logging.DEBUG) 18 | 19 | formatter = logging.Formatter('%(asctime)s | %(levelname)s | %(message)s', "%Y-%m-%d %H:%M:%S") 20 | file_handler.setFormatter(formatter) 21 | 22 | if (logger.hasHandlers()): 23 | logger.handlers.clear() 24 | 25 | logger.addHandler(file_handler) 26 | 27 | return logger 28 | 29 | @classmethod 30 | def add_to_log(cls, level, message): 31 | try: 32 | logger = cls.__set_logger(cls) 33 | 34 | if (level == "critical"): 35 | logger.critical(message) 36 | elif (level == "debug"): 37 | logger.debug(message) 38 | elif (level == "error"): 39 | logger.error(message) 40 | elif (level == "info"): 41 | logger.info(message) 42 | elif (level == "warn"): 43 | logger.warn(message) 44 | except Exception as ex: 45 | print(traceback.format_exc()) 46 | print(ex) 47 | -------------------------------------------------------------------------------- /src/utils/Security.py: -------------------------------------------------------------------------------- 1 | from decouple import config 2 | 3 | import datetime 4 | import jwt 5 | import pytz 6 | import traceback 7 | 8 | # Logger 9 | from src.utils.Logger import Logger 10 | 11 | 12 | class Security(): 13 | 14 | secret = config('JWT_KEY') 15 | tz = pytz.timezone("America/Lima") 16 | 17 | @classmethod 18 | def generate_token(cls, authenticated_user): 19 | try: 20 | payload = { 21 | 'iat': datetime.datetime.now(tz=cls.tz), 22 | 'exp': datetime.datetime.now(tz=cls.tz) + datetime.timedelta(minutes=10), 23 | 'username': authenticated_user.username, 24 | 'fullname': authenticated_user.fullname, 25 | 'roles': ['Administrator', 'Editor'] 26 | } 27 | return jwt.encode(payload, cls.secret, algorithm="HS256") 28 | except Exception as ex: 29 | Logger.add_to_log("error", str(ex)) 30 | Logger.add_to_log("error", traceback.format_exc()) 31 | 32 | @classmethod 33 | def verify_token(cls, headers): 34 | try: 35 | if 'Authorization' in headers.keys(): 36 | authorization = headers['Authorization'] 37 | encoded_token = authorization.split(" ")[1] 38 | 39 | if ((len(encoded_token) > 0) and (encoded_token.count('.') == 3)): 40 | try: 41 | payload = jwt.decode(encoded_token, cls.secret, algorithms=["HS256"]) 42 | roles = list(payload['roles']) 43 | 44 | if 'Administrator' in roles: 45 | return True 46 | return False 47 | except (jwt.ExpiredSignatureError, jwt.InvalidSignatureError): 48 | return False 49 | 50 | return False 51 | except Exception as ex: 52 | Logger.add_to_log("error", str(ex)) 53 | Logger.add_to_log("error", traceback.format_exc()) 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python & Flask: Estructura de Carpetas y Archivos 2 | 3 | Aprende a estructurar un proyecto web (RESTful API) de Python y Flask organizando tus archivos y carpetas de forma ordenada, incluyendo directorios para: routes, models, services, testing y más. 4 | 5 |