UnitTest Case for Flask application - python

Writting UniTTest For Flask Application
I have a simple Flask Application below , flask application will work perfectly fine without any issues
import sys , os , os.path , time , datetime , json , logging, warnings
from os import listdir
from os.path import isfile, join
from flask_wtf.csrf import CSRFProtect
from flask import Flask
from flask import render_template
from flask import request
from datetime import datetime
from logging import FileHandler
from logging import Formatter
# --> Required Host and Env
_cloudhost='dev.net'
_cloudenv='dev'
_portNumber=4444
#CRF Compliant / (.)
app = Flask(__name__)
csrf = CSRFProtect()
csrf.init_app(app)
#app.route('/')
#app.route('/index')
def index():
project = "CFTP - TBRP & TBLP "
framework = "Publish Framework"
version = '0.1'
hostname = os.popen("echo $(hostname)").read().split('\n')[0]
return render_template('index.html', title=project , description=f'{project} : {framework} v - {version}' , hostname=hostname , logFile=readlogs(), get_env=_cloudenv)
app.run(host=_cloudhost, port=_portNumber)
UnitTest Function . I am struggling to understand on how i can create unit test cases for flask application. Inside my unittest file which i created , i was wondering if i am doing this right .
1 -> To test the csrf if that i included above and also on how i can test my index page can result as pass
test_flask.py
import unittest , sys , tempfile, os , json , shutil
from unittest import mock
from subprocess import Popen, PIPE
import logging
with mock.patch.dict(os.environ, {'PROJECT_FOLDER':'CitiFTP Reports','RPM_ENVIRONMENT': 'DEV', 'HOST_NAME': 'sd-nzvp-czog.nam.nsroot.net', 'USER': 'citiftptabhyper','TABLEAU_PUBLISH_TYPE':'TABCMD','TABCMD_PATH':'/home/citiftptabhyper/tabcmd','CYBERARK_TYPE':'DYNAMIC','JOB_TYPE':'workbook_TP','JOB_FILE':'publish_workbook_TP','RESTAPI_LOG_TYPE':'TP'}):
sys.path.insert(2, 'C:/Users/mm13854/Desktop/FileCopy_Dec_122/cftp_tableau_filecopy/deploy/src')
sys.path.insert(1, 'C:/Users/mm13854/Desktop/FileCopy_Dec_122/cftp_tableau_filecopy/deploy/app')
import env_conf as conf
import run_flask as flk_app
from run_flask import app
class test_tbrp_case(unittest.TestCase):
def setUp(self):
self.ctx = app.app_context()
self.ctx.push()
self.client = app.test_client()
def tearDown(self):
self.ctx.pop()
def test_home(self): ## Fail
response = self.client.post("/index", data={"title": "CFTP - TBRP & TBLP "})
assert response.status_code == 200
## test flask port used -- PASS
def test_port(self):
self.assertEqual(flk_app._portNumber,4444)
## Test host is returning the same value -- PASS
def test_host(self):
self.assertEqual(flk_app._cloudhost,conf.FLASK_CONFIGURATION.get('ENV_URL'))
def test_csrf(self):
pass
if __name__=='__main__':
unittest.main()

Related

AttributeError: 'function' object has no attribute 'test_client'

main.py
import os
import psycopg2
import gunicorn
from flask import Flask
from app.routes.universities_routes import app_universities_routes
from app.routes.degrees_routes import app_degrees_routes
from app.routes.curriculum_ratings_routes import app_curriculum_ratings_routes
from app.routes.departments_route import app_departments_routes
from app.routes.user_route import app_users_routes
from app.routes.history_route import app_history_routes
from app.routes.curriculums_route import app_curriculum_routes
from app.routes.courses_route import app_course_routes
from flask_cors import CORS, cross_origin
app = Flask(__name__)
CORS(app)
app.register_blueprint(app_universities_routes)
app.register_blueprint(app_degrees_routes)
app.register_blueprint(app_curriculum_ratings_routes)
app.register_blueprint(app_departments_routes)
app.register_blueprint(app_users_routes)
app.register_blueprint(app_history_routes)
app.register_blueprint(app_curriculum_routes)
app.register_blueprint(app_course_routes)
#app.route('/')
def hello():
return 'Hello, Class Track!'
...
test_routes.py
import pytest
from flask import current_app as app
#pytest.fixture
def app():
app.testing = True
yield app
#pytest.fixture
def client(app):
return app.test_client()
#pytest.fixture
def runner(app):
return app.test_cli_runner()
def test_hello(client):
response = client.get('/')
assert response.status_code == 200
File Structure
backend
app
routes
models
tests
test_routes.py
main.py
I'm trying to run the most simple test for routes, but for some reason this doesn't work and throws this error:
app = <function app at 0x000001FD18A0EF80>
#pytest.fixture
def client(app):
> return app.test_client()
E AttributeError: 'function' object has no attribute 'test_client'
backend\app\tests\test_routes.py:24: AttributeError
I run this through VSCode and through the terminal with the 'pytest' command
Is there a way to make this work without changing the code in main.py

fastapi - import config from main.py

I'm new to fastapi, which is really great so far, but I struggle to find a clean way to import my app config to in another module.
EDIT: I need to be able to change the config when running unit test
Here is my dir tree:
/app
| __init__.py
| /router
| | __init__.py
| | my_router.py
| /test
| | test_api.py
| config.py
| main.py
Here is my main.py file:
from functools import lru_cache
from fastapi import FastAPI
from .router import my_router
from . import config
app = FastAPI()
app.include_router(
my_router.router,
prefix="/r",
tags=["my-router"],
)
#lru_cache()
def get_setting():
return config.Settings(admin_email="admin#domain.com")
#app.get('/')
def hello():
return 'Hello world'
Here is the router.py:
from fastapi import APIRouter
from ..main import get_setting
router = APIRouter()
#router.get('/test')
def get_param_list(user_id: int):
config = get_setting()
return 'Import Ok'
And here is the config file
from pydantic import BaseSettings
class Settings(BaseSettings):
param_folder: str = "param"
result_folder: str = "output"
class Config:
env_prefix = "APP_"
Then runing uvicorn app.main:app --reload I got : ERROR: Error loading ASGI app. Could not import module "app.main".
I guess because of a kind of circular import. But then I don't how to pass my config to my router ?
Thanks for your help :)
How about setting up the lru cache directly inside the config.py.
from functools import lru_cache
from pydantic import BaseSettings
class Settings(BaseSettings):
admin_email: str = "admin#example.com"
param_folder: str = "param"
result_folder: str = "output"
class Config:
env_prefix = "APP_"
#lru_cache()
def get_setting():
return Settings()
And my_router.py
from fastapi import APIRouter, Depends
from ..config import Settings, get_setting
router = APIRouter()
#router.get('/test')
def get_param_list(config: Settings = Depends(get_setting)):
return config
And test.py
from fastapi.testclient import TestClient
from . import config, main
client = TestClient(main.app)
def get_settings_override():
return config.Settings(admin_email="testing_admin#example.com")
main.app.dependency_overrides[config.get_settings] = get_settings_override
def test_app():
response = client.get("/r/test")
data = response.json()
assert data == config.Settings(admin_email="testing_admin#example.com")
The only draw back with this is that I must add the setting: config.Setting = Depends(config.get_setting), which is quite "heavy", to every function call that needs the setting.
You can use Class Based Views from the fastapi_utils package:
from fastapi import APIRouter, Depends
from fastapi_utils.cbv import cbv
from starlette import requests
from logging import Logger
from .. import config
router = APIRouter()
#cbv(router)
class MyQueryCBV:
settings: config.Setting = Depends(config.get_setting) # you can introduce settings dependency here
def __init__(self, r: requests.Request): # called for each query
self.logger: Logger = self.settings.logger
self.logger.warning(str(r.headers))
#router.get('/test')
def get_param_list(self, user_id: int)
self.logger.warning(f"get_param_list: {user_id}")
return self.settings
#router.get("/test2")
def get_param_list2(self):
self.logger.warning(f"get_param_list2")
return self.settings
I got it working using the FastAPI Dependency system and, as suggested by #Kassym Dorsel, by moving the lru_cache to the config.py.
The only draw back with this is that I must add the setting: config.Setting = Depends(config.get_setting), which is quite "heavy", to every function call that needs the setting.
Here is how I did it:
config.py file:
from pydantic import BaseSettings
class Settings(BaseSettings):
param_folder: str = "param"
result_folder: str = "output"
class Config:
env_prefix = "APP_"
#lru_cache()
def get_setting():
return config.Settings(admin_email="admin#domain.com")
main.py file:
from functools import lru_cache
from fastapi import FastAPI
from .router import my_router
app = FastAPI()
app.include_router(
my_router.router,
prefix="/r",
tags=["my-router"],
)
#app.get('/')
def hello():
return 'Hello world'
router.py file:
from fastapi import APIRouter, Depends
from .. import config
router = APIRouter()
#router.get('/test')
def get_param_list(user_id: int, setting: config.Setting = Depends(config.get_setting)):
return setting
This way I can use the dependency_overrides in my test_api.py to change the config for the test:
from fastapi.testclient import TestClient
from .. import config, server
client = TestClient(server.app)
TEST_PARAM_FOLDER = 'server/test/param'
TEST_RESULT_FOLDER = 'server/test/result'
def get_setting_override():
return config.Setting(param_folder=TEST_PARAM_FOLDER, result_folder=TEST_RESULT_FOLDER)
server.app.dependency_overrides[config.get_setting] = get_setting_override
def test_1():
...

Using flask track usage with blueprint and config from object

I have a question regarding the flask_track_usage module.
All my blueprints should have the Trackusage function included.
Unfortunately i didn't find a way to solve my problem.
Why isn't it possible to simply use the flask.current_app?
route.py
import datetime
from uuid import uuid4
from flask import Blueprint, session, render_template, url_for, flash, redirect, current_app, request, jsonify
from flask_template.main.utils import my_func
import os
import json
from flask_track_usage import TrackUsage
from flask_track_usage.storage.printer import PrintWriter
from flask_track_usage.storage.output import OutputWriter
main = Blueprint('main', __name__)
t = TrackUsage(current_app, [
PrintWriter(),
OutputWriter(transform=lambda s: "OUTPUT: " + str(s))
])
#t.include
#main.before_request
def session_management():
now = datetime.datetime.now()
session_lifetime = current_app.config['SESSION_DURATION']
# Create session, if not already existing
if session.get('session_ID') is None:
# Initiate session, set a random UUID as Session ID
session['session_ID'] = str(uuid4())
session.permanent = True # will expire after 30 minutes of inactivity
session['timeout'] = False
print(f'Initated session with ID:', session.get('session_ID'))
return redirect(url_for('main.index'))
else:
try:
last_active = session.get('last_active')
delta = now - last_active
if delta.seconds > 1740:
print(f'Note: Session lifetime less than one minute. Expires in:',
session_lifetime - delta.seconds, 'sec')
if delta.seconds > session_lifetime:
session['last_active'] = now
session['timeout'] = True
print(f'Your session has expired after 30 minutes, you have been logged out (files are deleted).')
return redirect(url_for('main.logout'))
except:
pass
try:
session['last_active'] = now
except:
pass
#main.route('/')
def index():
return redirect(url_for('main.home'))
#main.route('/home')
def home():
return render_template('home.html', title='home', subheader='Template Main Page')
__init__.py
from flask import Flask
from flask_template.config import Config
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(Config)
from flask_template.sample_extension.routes import sample_extension
from flask_template.main.routes import main
from flask_template.errors.handlers import errors
app.register_blueprint(sample_extension)
app.register_blueprint(main)
app.register_blueprint(errors)
return app
Config
import os
import json
from datetime import timedelta
with open(os.path.join(os.getcwd(), 'etc', 'app_config.json')) as config_file:
config = json.load(config_file)
class Config:
SECRET_KEY = config.get('SECRET_KEY')
SESSION_DURATION = 1800 # 30 minutes for delete function
PERMANENT_SESSION_LIFETIME = timedelta(minutes=30) # session lifetime
root_dir = os.path.join(os.path.realpath('.'), 'flask_template')
# Path to template to illustrate download functionality
TEMPLATE_FOLDER = os.path.join(root_dir, 'sample_extension', 'assets', 'template')
TRACK_USAGE_USE_FREEGEOIP = False
TRACK_USAGE_INCLUDE_OR_EXCLUDE_VIEWS = 'include'
run.py
from flask_template import create_app
from flask_track_usage import TrackUsage
from flask_track_usage.storage.printer import PrintWriter
from flask_track_usage.storage.output import OutputWriter
app = create_app()
if __name__ == '__main__':
app.run(ssl_context=('******'),
host='0.0.0.0',
port=5000,
debug=True)
error
RuntimeError: Working outside of application context.
There are several global Flask variables such as current_app and g that can be accessed only while the application is running (more about them in the Flask Application Context documentation entry). Using them outside the application context raises RuntimeError.
You can instantiate TrackUsage without parameters in your routes.py module:
track_usage = TrackUsage()
#track_usage.include
#main.before_request
def session_management():
...
And then you can import it in your __init__.py module and apply to your application instance:
from flask import Flask
from flask_template.config import Config
from flask_track_usage.storage.printer import PrintWriter
from flask_track_usage.storage.output import OutputWriter
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(Config)
from flask_template.sample_extension.routes import sample_extension, track_usage
from flask_template.main.routes import main
from flask_template.errors.handlers import errors
track_usage.init_app(app, [
PrintWriter(),
OutputWriter(transform=lambda s: "OUTPUT: " + str(s)),
])
app.register_blueprint(sample_extension)
app.register_blueprint(main)
app.register_blueprint(errors)
return app
I have not found this solution in the Flask-Track-Usage documentation but it is a common interface for Flask extentions. It allows to instantiate an extension in one module and connect it to Flask application in main module.

Flask ImportError: cannot import name (for app in __init__.py)

I'm new to flask, and REST-APIs / server side scripting in general. I get the error "ImportError: cannot import name 'flask_app'" when I try executing run_app.py
This is my dir structure.
my_project
- webapp
- __init__.py
- helpers.py
- c_data.py
- run_app.py
Contents of each file:
__init__.py
"""This is init module."""
from flask import Flask
from webapp import c_data
# Place where webapp is defined
flask_app = Flask(__name__)
c_data.py
"""This module will serve the api request."""
from app_config import client
from webapp import flask_app
from webapp import helpers
from flask import request, jsonify
# Select the database
db = client.newDB
# Select the collection
collection = db.collection
#flask_app.route("/")
def get_initial_response():
"""Welcome message for the API."""
# Message to the user
message = {
'apiVersion': 'v1.0',
'status': '200',
'message': 'Welcome to the Flask API'
}
# Making the message looks good
resp = jsonify(message)
# Returning the object
return resp
run_app.py
# -*- coding: utf-8 -*-
from webapp import flask_app
if __name__ == '__main__':
# Running webapp in debug mode
flask_app.run(debug=True)
What am I doing wrong?
It is because you import c_data in init.py, this makes recursive import
To be clearer, you import c_data and define flask_app inside __init__, but later than c_data you import flask_app which is not defined yet.
from webapp import c_data # Remove it, it makes recursive import
# Place where webapp is defined
flask_app = Flask(__name__)
Try to remove it. Or change the way to import c_data.
Possible solution, change your run_app.py
Remember to remove from webapp import c_data in __init__.py
from webapp import flask_app
from webapp import c_data # New import
if __name__ == '__main__':
# Running webapp in debug mode
flask_app.run(debug=True)

How to get rid of extra cgi-bin url components running a Flask App using wsgiref CGIHandler?

I am on a shared cpanel hosting plan that does not support wsgi apps directly. So I have to use the wsgiref CGIHandler workaround, as described here: http://flask.pocoo.org/docs/0.12/deploying/cgi/ .
It all works and produces expected results, but there is always this extra stuff in the urls: "/cgi-bin/index.cgi/" that the python app seems to be adding automatically (to match what it detects while called by the cgi handler).
For example, I would like it to be myhost.com/login/ instead of myhost.com/cgi-bin/index.cgi/login/, or myhost.com/ instead of myhost.com/cgi-bin/index.cgi/.
All those shorter versions of the links work well, because of the engine rewrite rules are in place. I have checked that. It is just a matter of finding a way to tell the flask app to get rid of "/cgi-bin/index.cgi/".
Some of my code:
cat www/.htaccess
# Redirect everything to CGI WSGI handler, but Don't interfere with static files
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /cgi-bin/index.cgi/$1 [L]
.
cat www/cgi-bin/index.cgi
#!/home/myhost/myhost.com/flasky/venv/bin/python
import os
import sys
sys.path.insert(0, '/home/myhost/myhost.com/flasky/venv/lib/python2.7/site-packages')
sys.path.insert(0, '/home/myhost/myhost.com/flasky')
from wsgiref.handlers import CGIHandler
from manage import app
CGIHandler().run(app)
.
cat www/flasky/manage.py
#!/usr/bin/env python
import os
from app import create_app, db
from app.models import User, Role, Permission
from flask_script import Manager, Shell
from flask_migrate import Migrate, MigrateCommand
app = create_app(os.getenv('FLASK_CONFIG') or 'default')
manager = Manager(app)
migrate = Migrate(app, db)
def make_shell_context():
return dict(app=app, db=db, User=User, Role=Role,
Permission=Permission)
manager.add_command("shell", Shell(make_context=make_shell_context))
manager.add_command('db', MigrateCommand)
if __name__ == '__main__':
manager.run()
.
Any ideas?
Thanks!
I have found a hack for it.. but it's just a hack :-(
If I override the value of SCRIPT_NAME environment variable in the wsgiref CGIHandler script, it works perfectly then.
Here's the updated code:
cat www/cgi-bin/index.cgi
#!/home/myhost/myhost.com/flasky/venv/bin/python
import os
import sys
sys.path.insert(0, '/home/myhost/myhost.com/flasky/venv/lib/python2.7/site-packages')
sys.path.insert(0, '/home/myhost/myhost.com/flasky')
from wsgiref.handlers import CGIHandler
from manage import app
os.environ['SCRIPT_NAME'] = ''
CGIHandler().run(app)
.
Basically, whatever the os.environ['SCRIPT_NAME'] value is, it gets prefixed to the flask app url every time a page is requested.
I am still looking for a more elegant "pythonic" solution though ..
cat www/cgi-bin/index.cgi
#!/home/myhost/myhost.com/flasky/venv/bin/python
import os
import sys
sys.path.insert(0, '/home/myhost/myhost.com/flasky/venv/lib/python2.7/site-packages')
sys.path.insert(0, '/home/myhost/myhost.com/flasky')
from wsgiref.handlers import CGIHandler
from manage import app
class ScriptNameStripper(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
environ['SCRIPT_NAME'] = ''
return self.app(environ, start_response)
app = ScriptNameStripper(app)
CGIHandler().run(app)

Categories