In the last post of this Python FastAPI learning series, we concluded with a list of to-do items. In this post, we will address these issues. Additionally, we are performing some code cleanup and improvements.

🐍 Index of the Complete Series.

127-feature-image.png
Python FastAPI: Finishing Off the Pending Items, Code Cleanup, and Improvements

The code requires Python 3.12.4. Please refer to the following discussion on how to upgrade to Python 3.12.4.

🚀 Please note, complete code for this post can be downloaded from GitHub with:

git clone -b v0.13.0 https://github.com/behai-nguyen/fastapi_learning.git

❶ During and after the last post, I realised that the code could be significantly improved. Some parts have been rewritten. The changes implemented in this post are listed below:

  1. A single code path for login redirection.
  2. A single code path for verifying user scopes.
  3. All JSON responses, including HTTPException, now use ResultStatus.
  4. Rewrote template code to determine the enabled state of UI elements.
  5. The logged-in user model now includes the assigned scope list as well. The logged-in user's assigned scope list is still in TokenData. This code change eliminates the need to repeatedly decode the access token to get the logged-in user's assigned scope list.
  6. Temporary data between requests is now stored in the session. (For a discussion on persistent stateful HTTP sessions, please refer to the third post). This simplifies the code and reduces processing.

❷ No new files were added. The current structure of the project is outlined below.

-- Please note, those marked with are updated, and those marked with are new.

/home/behai/fastapi_learning/
.
├── cert
│   ├── cert.pem
│   └── key.pem
├── .env
├── logger_config.yaml
├── main.py ★
├── pyproject.toml ★
├── pytest.ini
├── README.md ★
├── src
│   └── fastapi_learning
│       ├── businesses
│       │   ├── app_business.py
│       │   ├── base_business.py
│       │   ├── base_validation.py
│       │   ├── employees_mgr.py
│       │   └── employees_validation.py
│       ├── common
│       │   ├── consts.py ★
│       │   ├── jwt_utils.py
│       │   ├── queue_logging.py
│       │   └── scope_utils.py
│       ├── controllers
│       │   ├── admin.py ★
│       │   ├── auth.py ★
│       │   ├── employees_admin.py ★
│       │   ├── __init__.py ★
│       │   └── required_login.py ★
│       ├── __init__.py
│       ├── models
│       │   └── employees.py ★
│       ├── static
│       │   └── js
│       │       └── application.js
│       └── templates
│           ├── admin
│           │   └── me.html ★
│           ├── auth
│           │   ├── home.html ★
│           │   └── login.html
│           ├── base.html
│           └── emp
│               ├── insert.html ★
│               ├── search.html ★
│               ├── search_result.html ★
│               └── update.html ★
└── tests
    ├── business
    │   ├── test_employees_mgr.py
    │   ├── test_employees_validation.py
    │   └── test_scope_utils.py
    ├── conftest.py
    ├── __init__.py
    ├── integration
    │   ├── test_admin_itgt.py ★
    │   ├── test_api_itgt.py ★
    │   ├── test_auth_itgt.py ★
    │   ├── test_employees_itgt.py
    │   ├── test_expired_jwt.py ★
    │   ├── test_scope_permission_itgt.py ★
    │   └── test_scope_ui_itgt.py
    ├── README.md
    └── unit
        └── test_employees.py

❸ Code changes. For this post, we will not go into a detailed discussion as we did in the previous posts. Blog statistics have shown that these posts aren't frequently read. Instead, I am listing the entire content of the GitHub check-in commands. I believe that, together with this short post, these commands will help in understanding the refactorings done to each code module.

Complete GitHub check-in commands:

$ git add pyproject.toml
$ git commit -m "Standardised JSON response via ResultStatus. General refactoring. Improved logic." -m "Removed the deprecated and unused 'aioredis'. The equivalence is 'starsessions[redis]'."
~~~

$ git add main.py
$ git commit -m "Standardised JSON response via ResultStatus. General refactoring. Improved logic." -m "Refactored the requires login custom exception handler,"^

"@app.exception_handler(RequiresLogin) / async def requires_login(request: Request, _: Exception)"^

"    - stores the exception message in session under the key login_redirect_msg."^

"    - conditionally returns JSON or HTML"^

"    - for JSON response, when error, return a JSON similar to ResultStatus"
~~~

$ git add .\src\fastapi_learning\models\employees.py
$ git commit -m "Standardised JSON response via ResultStatus. General refactoring. Improved logic." -m "Added scopes: list[str] = [] to LoggedInEmployee."
~~~

$ git add .\src\fastapi_learning\controllers\required_login.py
$ git commit -m "Standardised JSON response via ResultStatus. General refactoring. Improved logic." -m "Updated async def get_logged_in_user(...):"^

"    - when failed decoded token raises RequiresLogin with token_data.detail."^

"    - copies token payload scopes over to the returned LoggedInEmployee."^

"    - Cache the returned LoggedInEmployee to session as JSON under the key logged_in_user."^

"Added async def get_cached_logged_in_user(request: Request) -> LoggedInEmployee:"^

"Refactored class RequiresLogin(HTTPException): sub-class of HTTPException."
~~~

$ git add .\src\fastapi_learning\controllers\__init__.py
$ git commit -m "Standardised JSON response via ResultStatus. General refactoring. Improved logic." -m "Updates include:"^

"Removed def is_logged_in(...), def has_required_scopes(...)."^

"Refactored async def verify_user_scopes(...):"^

"    - LoggedInEmployee replaced access token param."^

"    - the return tuple is now (bool, permissions checking result)."^

"Improved template codes:"^

"    - added template method: def get_ui_states(...): replaces has_required_scopes(...)."^

"Added the following one-liners session helper methods:"^

"    - def set_access_token(request: Request, token: str):"^

"    - def get_access_token(request: Request) -> Union[str | None]:"^

"    - def delete_access_token(request: Request):"^

"    - def no_access_token(request: Request) -> bool:"^

"    - set_login_redirect_code(request: Request, code: int):"^

"    - get_login_redirect_code(request: Request, delete_also: bool=False) -> int:"^

"    - delete_login_redirect_code(request: Request):"^

"    - def set_login_redirect_message(request: Request, message: str):"^

"    - def get_login_redirect_message(request: Request, delete_also: bool=False) -> str:"^

"    - def delete_login_redirect_message(request: Request):"
~~~

$ git add .\src\fastapi_learning\common\consts.py
$ git commit -m "Standardised JSON response via ResultStatus. General refactoring. Improved logic." -m "Added some new constants."
~~~

$ git add .\src\fastapi_learning\controllers\admin.py
$ git commit -m "Standardised JSON response via ResultStatus. General refactoring. Improved logic." -m "Updates include:"^

"All JSON responses are returned via ResultStatus."^

"async def read_users_me(...):"^

"    - code cleaned up."^

"    - no longer check for authenticated session."^

"    - for JSON response, now returns ResultStatus."^

"async def get_current_user(...):"^

"    - uses [ user = Depends(get_logged_in_user), ]"^

"    - uses verify_user_scopes(...)"
~~~

$ git add .\src\fastapi_learning\controllers\auth.py
$ git commit -m "Standardised JSON response via ResultStatus. General refactoring. Improved logic." -m "Updates include:"^

"All JSON responses are returned via ResultStatus."^

"Removed def __login_page_context(...)."^

"Completely rewrote async def login_page(...)."^

"New def __login_response(...) replaces def __login_page(...)."^

"Refactored async def home_page(...):"^

"    - uses [user = Depends(get_logged_in_user)]"^

"Removed helper method def __home_page(...)."^

"Refactored async def login(...) -- check for access token exists, if it does then call get_logged_in_user(...)."^

"Refactored async def logout(request: Request):"^

"    - stores redirect message in session under the key login_redirect_msg."^

"    - redirects state is now LOGIN_REDIRECT_STATE_CERTAIN."
~~~

$ git add .\src\fastapi_learning\controllers\employees_admin.py
$ git commit -m "Standardised JSON response via ResultStatus. General refactoring. Improved logic." -m "Update as follows:"^

"In all methods where applicable:"^

"    user: Annotated[LoggedInEmployee, Depends(get_cached_logged_in_user)]"^

"replaced ( token: Annotated[str, Depends(oauth2_scheme)] )."
~~~

$ git add .\tests\integration\test_admin_itgt.py
$ git commit -m "Standardised JSON response via ResultStatus. General refactoring. Improved logic." -m "Updates include:"^

"Fixed test_integration_invalid_credentials_admin_own_detail_json(test_client):"^

"    - pass emp_no to token payload."^

"Fixed test_integration_valid_admin_own_detail_json(test_client):"^

"    - calls ( assert PasswordHasher().verify(status['password'], 'password') == True )."^

"Refactored:"^

"    - def test_integration_not_auth_admin_own_detail_json(test_client): JSON via ResultStatus."^

"    - def test_integration_invalid_credentials_admin_own_detail_json(test_client): JSON via ResultStatus."^

"    - def test_integration_not_auth_admin_own_detail_html(test_client): message condition."^

"    - def test_integration_invalid_credentials_admin_own_detail_html(test_client): message condition."^

"    - def test_integration_valid_admin_own_detail_json(test_client): JSON via ResultStatus."
~~~

$ git add .\tests\integration\test_api_itgt.py
$ git commit -m "Standardised JSON response via ResultStatus. General refactoring. Improved logic." -m "Updates include:"^

"Fixed test_integration_valid_admin_own_detail(test_client):"^

"    - calls ( assert PasswordHasher().verify(status['password'], 'password') == True )."^

"Refactored:"^

"    - def test_integration_valid_admin_own_detail(test_client): JSON via ResultStatus."^

"    - def test_integration_valid_login(test_client): JSON via ResultStatus."
~~~

$ git add .\tests\integration\test_expired_jwt.py
$ git commit -m "Standardised JSON response via ResultStatus. General refactoring. Improved logic." -m "Updates include:"^

"Refactored def test_expired_jwt_json_response(test_client): JSON via ResultStatus."^

"Rewrote def test_expired_jwt_html_response(test_client):"^

"    - the returned HTML now is the login page."
~~~

$ git add .\tests\integration\test_scope_permission_itgt.py
$ git commit -m "Standardised JSON response via ResultStatus. General refactoring. Improved logic." -m "Updates include:"^

"def test_scope_permission_invalid_json(test_client): JSON via ResultStatus."^

"def test_scope_permission_api_me(test_client): JSON via ResultStatus."^

"Refactored related to path /auth/token:"^

"    - def test_scope_permission_login_01(test_client): JSON via ResultStatus."^

"    - def test_scope_permission_login_02(test_client): JSON via ResultStatus."^

"    - def test_scope_permission_login_03(test_client): JSON via ResultStatus."
~~~

$ git add .\tests\integration\test_auth_itgt.py
$ git commit -m "Standardised JSON response via ResultStatus. General refactoring. Improved logic." -m "Updates include:"^

" On JSON error responses:"^

"    - def test_integration_login_bad_email_json(test_client)."^

"    - def test_integration_login_bad_password_json(test_client)."^

"    - def test_integration_invalid_username_login_json(test_client)."^

"    - def test_integration_invalid_password_login_json(test_client)."^

"On JSON valid responses:"^

"    - def test_integration_valid_login_json(test_client)."^

"Replaces some literals with already defined constants."
~~~

$ git add .\src\fastapi_learning\templates\admin\me.html
$ git commit -m "Standardised JSON response via ResultStatus. General refactoring. Improved logic." -m "Displaying added new fields employee number and scopes."
~~~

$ git add .\src\fastapi_learning\templates\auth\home.html
$ git add .\src\fastapi_learning\templates\emp\search.html
$ git add .\src\fastapi_learning\templates\emp\search_result.html
$ git add .\src\fastapi_learning\templates\emp\insert.html
$ git add .\src\fastapi_learning\templates\emp\update.html
$ git commit -m "Standardised JSON response via ResultStatus. General refactoring. Improved logic." -m "Use new method def get_ui_states(...). ( Stopped using def has_required_scopes(...) )."

~~~

$ git add README.md
$ git commit -m "Standardised JSON response via ResultStatus. General refactoring. Improved logic." -m "Updates include:"^

"    - added [ Implemented routes ] section."^

"    - added post [ Python FastAPI: Finishing Off the Pending Items, Code Cleanup, and Improvements ]"
~~~

$ git branch -M main
$ git push -u origin main
~~~

$ git tag -a v0.13.0 -m "Added post [ Python FastAPI: Finishing Off the Pending Items, Code Cleanup, and Improvements ]"
$ git push origin --tags

$ git tag

❹ Please take note of the following regarding the test modules: A majority of the refactoring centers around JSON responses returned via ResultStatus. Therefore, we have to check ['status']['code'] to get the actual returned HTTP code, as status_code is always HTTP_200_OK. For example:

        ...
        login_response = test_client.post('/auth/token', data=login_data)

        assert login_response != None
        # assert login_response.status_code == http_status.HTTP_400_BAD_REQUEST
        assert login_response.status_code == http_status.HTTP_200_OK

        status = login_response.json()

        # Should always check for this.
        assert status['status']['code'] == http_status.HTTP_500_INTERNAL_SERVER_ERROR
        assert status['status']['text'] == BAD_LOGIN_MSG
		...

❺ I believe this is the final post of this series. We have explored some essential features of the FastAPI framework. If I find something else interesting about it in the future, we might revisit this series once again.

Thank you for reading. I hope you find the information in this post useful. Stay safe, as always.

✿✿✿

Feature image source:

🐍 Index of the Complete Series.