├── .env
├── .gitignore
├── README.md
├── config.py
├── index.py
├── preview1.png
├── preview2.png
├── requirements.txt
├── scripts
├── flask_jwt_backup.sql
└── stored_procedures.sql
└── src
├── __init__.py
├── database
├── __init__.py
└── db_mysql.py
├── models
├── LanguageModel.py
├── UserModel.py
└── __init__.py
├── routes
├── AuthRoutes.py
├── IndexRoutes.py
├── LanguageRoutes.py
└── __init__.py
├── services
├── AuthService.py
├── LanguageService.py
└── __init__.py
├── tests
├── __init__.py
└── services
│ ├── __init__.py
│ └── test_LanguageService.py
└── utils
├── Logger.py
├── Security.py
└── log
└── app.log
/.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
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | venv/
2 |
3 | __pycache__/
4 | *.py[cod]
5 |
6 | .pytest_cache/
--------------------------------------------------------------------------------
/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 |
6 |
7 | Primero, crear un entorno virtual:
8 | ### `python -m virtualenv venv`
9 |
10 | Para instalar los paquetes necesarios:
11 | ### `pip install -r requirements.txt`
12 |
13 |
14 |
15 | 
16 |
17 | 
18 |
19 | # 🌍 Por si deseas contactarme 👨💻 :
20 |
21 | [](https://pe.linkedin.com/in/uskokrum2010)
22 | [](https://youtube.com/uskokrum2010)
23 | [](https://twitter.com/uskokrum2010)
24 | [](https://instagram.com/uskokrum2010)
25 | [](https://facebook.com/uskokrum2010)
26 | [](https://www.udemy.com/course/sql-para-administracion-de-bases-de-datos-con-mysql/)
27 | [](https://uskokrum2010.com)
28 | [](mailto:uskokrum2010@gmail.com)
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/preview1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UskoKruM/flask-folders-structure/188e6bd66907e8879b9d1220a93e8046b21846be/preview1.png
--------------------------------------------------------------------------------
/preview2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UskoKruM/flask-folders-structure/188e6bd66907e8879b9d1220a93e8046b21846be/preview2.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UskoKruM/flask-folders-structure/188e6bd66907e8879b9d1220a93e8046b21846be/requirements.txt
--------------------------------------------------------------------------------
/scripts/flask_jwt_backup.sql:
--------------------------------------------------------------------------------
1 | -- phpMyAdmin SQL Dump
2 | -- version 5.1.1
3 | -- https://www.phpmyadmin.net/
4 | --
5 | -- Servidor: 127.0.0.1
6 | -- Tiempo de generación: 31-05-2023 a las 03:54:55
7 | -- Versión del servidor: 10.4.22-MariaDB
8 | -- Versión de PHP: 8.1.2
9 |
10 | SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
11 | START TRANSACTION;
12 | SET time_zone = "+00:00";
13 |
14 |
15 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
16 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
17 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
18 | /*!40101 SET NAMES utf8mb4 */;
19 |
20 | --
21 | -- Base de datos: `flask_jwt`
22 | --
23 |
24 | DELIMITER $$
25 | --
26 | -- Procedimientos
27 | --
28 | CREATE PROCEDURE `sp_addUser` (IN `pUsername` VARCHAR(20), IN `pPassword` VARCHAR(20), IN `pFullname` VARCHAR(50)) BEGIN
29 | INSERT INTO user (username, password, fullname)
30 | VALUES (pUsername, AES_ENCRYPT(pPassword, SHA2('B!1w8*NAt1T^%kvhUI*S^_', 512)), pFullname);
31 | END$$
32 |
33 | CREATE PROCEDURE `sp_listLanguages` () BEGIN
34 | SELECT LAN.id, LAN.name
35 | FROM language LAN
36 | ORDER BY LAN.name ASC;
37 | END$$
38 |
39 | CREATE PROCEDURE `sp_verifyIdentity` (IN `pUsername` VARCHAR(20), IN `pPassword` VARCHAR(20)) BEGIN
40 | SELECT USER.id, USER.username, USER.fullname
41 | FROM user USER
42 | WHERE 1 = 1
43 | AND USER.username = pUsername
44 | AND CAST(AES_DECRYPT(USER.password, SHA2('B!1w8*NAt1T^%kvhUI*S^_', 512)) AS CHAR(30)) = pPassword;
45 | END$$
46 |
47 | DELIMITER ;
48 |
49 | -- --------------------------------------------------------
50 |
51 | --
52 | -- Estructura de tabla para la tabla `language`
53 | --
54 |
55 | CREATE TABLE `language` (
56 | `id` tinyint(2) UNSIGNED NOT NULL,
57 | `name` varchar(20) COLLATE utf8_unicode_ci NOT NULL
58 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Store the languages data.';
59 |
60 | --
61 | -- Volcado de datos para la tabla `language`
62 | --
63 |
64 | INSERT INTO `language` (`id`, `name`) VALUES
65 | (1, 'Python'),
66 | (2, 'Java'),
67 | (3, 'C#'),
68 | (4, 'PHP'),
69 | (5, 'JavaScript'),
70 | (6, 'Kotlin');
71 |
72 | -- --------------------------------------------------------
73 |
74 | --
75 | -- Estructura de tabla para la tabla `user`
76 | --
77 |
78 | CREATE TABLE `user` (
79 | `id` smallint(3) UNSIGNED NOT NULL,
80 | `username` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
81 | `password` blob NOT NULL,
82 | `fullname` varchar(50) COLLATE utf8_unicode_ci NOT NULL
83 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Stores the user''s data.';
84 |
85 | --
86 | -- Volcado de datos para la tabla `user`
87 | --
88 |
89 | INSERT INTO `user` (`id`, `username`, `password`, `fullname`) VALUES
90 | (1, 'USKOKRUM', 0x98fb67ca8f459f49841208bd4261bceb, 'Oscar García');
91 |
92 | --
93 | -- Índices para tablas volcadas
94 | --
95 |
96 | --
97 | -- Indices de la tabla `language`
98 | --
99 | ALTER TABLE `language`
100 | ADD PRIMARY KEY (`id`);
101 |
102 | --
103 | -- Indices de la tabla `user`
104 | --
105 | ALTER TABLE `user`
106 | ADD PRIMARY KEY (`id`);
107 |
108 | --
109 | -- AUTO_INCREMENT de las tablas volcadas
110 | --
111 |
112 | --
113 | -- AUTO_INCREMENT de la tabla `language`
114 | --
115 | ALTER TABLE `language`
116 | MODIFY `id` tinyint(2) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7;
117 |
118 | --
119 | -- AUTO_INCREMENT de la tabla `user`
120 | --
121 | ALTER TABLE `user`
122 | MODIFY `id` smallint(3) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
123 | COMMIT;
124 |
125 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
126 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
127 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
128 |
--------------------------------------------------------------------------------
/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/__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/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UskoKruM/flask-folders-structure/188e6bd66907e8879b9d1220a93e8046b21846be/src/database/__init__.py
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/src/models/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UskoKruM/flask-folders-structure/188e6bd66907e8879b9d1220a93e8046b21846be/src/models/__init__.py
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UskoKruM/flask-folders-structure/188e6bd66907e8879b9d1220a93e8046b21846be/src/routes/__init__.py
--------------------------------------------------------------------------------
/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/services/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UskoKruM/flask-folders-structure/188e6bd66907e8879b9d1220a93e8046b21846be/src/services/__init__.py
--------------------------------------------------------------------------------
/src/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UskoKruM/flask-folders-structure/188e6bd66907e8879b9d1220a93e8046b21846be/src/tests/__init__.py
--------------------------------------------------------------------------------
/src/tests/services/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UskoKruM/flask-folders-structure/188e6bd66907e8879b9d1220a93e8046b21846be/src/tests/services/__init__.py
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/src/utils/log/app.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UskoKruM/flask-folders-structure/188e6bd66907e8879b9d1220a93e8046b21846be/src/utils/log/app.log
--------------------------------------------------------------------------------