diff --git a/README.md b/README.md index 9381abe..d7c63fe 100755 --- a/README.md +++ b/README.md @@ -99,4 +99,4 @@ The project maintains an exhaustive test suite under the `tests/` directory. --- ## 📜 Release Snapshot -Current Baseline: **`release/2026-01-28-v3_prod-snapshot`** (Stable v4.9.0). \ No newline at end of file +Current Baseline: **`release/2026-01-28-v3_prod-snapshot`** (Stable v3.0.99). \ No newline at end of file diff --git a/app/lib_general_v3.py.snapshot b/app/lib_general_v3.py.snapshot deleted file mode 100644 index 53328a0..0000000 --- a/app/lib_general_v3.py.snapshot +++ /dev/null @@ -1,171 +0,0 @@ -""" -This file contains general utility functions and helpers specifically for API v3. -It aims to provide a clean slate for new methods and refactor existing ones from lib_general.py -that are relevant to the v3 API, while removing unused or outdated functionalities. -""" - -# Standard library imports -import time -import logging -from typing import ( - Any, - Dict, - List, - Optional, - Union, -) - -# Third-party imports -from fastapi import ( - APIRouter, - Depends, - Header, - HTTPException, - Query, - Request, - Response, - status, -) -from pydantic import ( - BaseModel, - Field, - ValidationError, - computed_field, - model_validator, -) - -# Internal imports (from this project) -from app.config import settings -from app.db_sql import redis_lookup_id_random -from app.log import get_logger - -logger = get_logger(__name__) - - -# --- Pydantic Model for Account Context --- -class AccountContext(BaseModel): - account_id: Optional[int] - account_id_random: Optional[str] - - -# --- Dependency Function for Account Context --- -def get_account_context( - x_account_id: Optional[str] = Header(None, min_length=11, max_length=22), - x_no_account_id: Optional[str] = Header(None, min_length=3, max_length=100), # Assuming 'bypass' or similar string - x_no_account_id_token: Optional[str] = Query(None, min_length=11, max_length=22), -) -> AccountContext: - """ - Resolves the account context from headers/query parameters with defined precedence. - Precedence: x_account_id (header) > x_no_account_id_token (query) > x_no_account_id (header flag) - Raises HTTPException 403 if no valid account is found and no bypass is indicated. - """ - logger.setLevel(logging.DEBUG) # Adjust as needed - logger.debug(locals()) - - resolved_account_id = None - resolved_account_id_random = None - - if x_account_id: - # Primary check: x_account_id header - resolved_account_id_random = x_account_id - if looked_up_id := redis_lookup_id_random(table_name='account', record_id_random=x_account_id): - resolved_account_id = looked_up_id - logger.info(f'Found account from x_account_id header: {resolved_account_id}') - else: - logger.warning(f'Invalid x_account_id header provided: {x_account_id}') - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail='Invalid X-Account-ID header.') - elif x_no_account_id_token: - # Secondary check: x_no_account_id_token query parameter - resolved_account_id_random = x_no_account_id_token - if looked_up_id := redis_lookup_id_random(table_name='account', record_id_random=x_no_account_id_token): - resolved_account_id = looked_up_id - logger.info(f'Found account from x_no_account_id_token query: {resolved_account_id}') - else: - logger.warning(f'Invalid x_no_account_id_token query provided: {x_no_account_id_token}') - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail='Invalid X-No-Account-ID-Token query parameter.') - elif x_no_account_id: - # Tertiary check: x_no_account_id header for bypass - # For now, just presence indicates bypass. Can add a specific value check later if needed. - logger.info(f'X-No-Account-ID header found: {x_no_account_id}. Proceeding without specific account context.') - resolved_account_id = None # Explicitly None for "no specific account" - resolved_account_id_random = '--- NO ACCOUNT ---' - else: - logger.warning('No valid account context provided via X-Account-ID, X-No-Account-ID-Token, or X-No-Account-ID.') - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail='Account context required. Please provide X-Account-ID, X-No-Account-ID-Token, or X-No-Account-ID.') - - return AccountContext(account_id=resolved_account_id, account_id_random=resolved_account_id_random) - - -# --- Pydantic Model for Pagination --- -class PaginationParams(BaseModel): - limit: int = 100 # Default limit - offset: int = 0 - -# --- Dependency Function for Pagination --- -def get_pagination_params( - limit: int = Query(100, ge=0, description="Maximum number of items to return"), - offset: int = Query(0, ge=0, description="Number of items to skip (for pagination)"), -) -> PaginationParams: - return PaginationParams(limit=limit, offset=offset) - - -# --- Pydantic Model for Status Filtering --- -class StatusFilterParams(BaseModel): - enabled: str = 'enabled' # 'enabled', 'disabled', 'all' - hidden: str = 'not_hidden' # 'hidden', 'not_hidden', 'all' - -# --- Dependency Function for Status Filtering --- -def get_status_filter_params( - enabled: str = Query('enabled', description="Filter by object enabled status ('enabled', 'disabled', 'all')"), - hidden: str = Query('not_hidden', description="Filter by object hidden status ('hidden', 'not_hidden', 'all')"), -) -> StatusFilterParams: - allowed_enabled_values = {'enabled', 'disabled', 'all'} - allowed_hidden_values = {'hidden', 'not_hidden', 'all'} - - if enabled not in allowed_enabled_values: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Invalid value for 'enabled'. Must be one of {list(allowed_enabled_values)}." - ) - if hidden not in allowed_hidden_values: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Invalid value for 'hidden'. Must be one of {list(allowed_hidden_values)}." - ) - return StatusFilterParams(enabled=enabled, hidden=hidden) - - -# --- Pydantic Model for Serialization Options --- -class SerializationParams(BaseModel): - by_alias: bool = True - exclude_unset: bool = False - exclude_defaults: bool = False # Added based on common_route_params - exclude_none: bool = False # Added based on common_route_params - -# --- Dependency Function for Serialization Options --- -def get_serialization_params( - by_alias: bool = Query(True, description="Whether to use field aliases for serialization"), - exclude_unset: bool = Query(False, description="Whether to exclude unset fields from the response"), - exclude_defaults: bool = Query(False, description="Whether to exclude fields with their default values from the response"), - exclude_none: bool = Query(False, description="Whether to exclude fields that are None from the response"), -) -> SerializationParams: - return SerializationParams( - by_alias=by_alias, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - ) - - -# --- Pydantic Model for Delay --- -class DelayParams(BaseModel): - sleep_time_ms: int = 0 # Raw delay value in ms - sleep_time_s: float = 0.0 # Converted to seconds for time.sleep() - -# --- Dependency Function for Delay --- -def get_delay_params( - x_delay_ms: Optional[int] = Header(0, alias='X-Delay-ms', description="Delay response for X milliseconds (header)"), - delay_ms: Optional[int] = Query(0, description="Delay response for X milliseconds (query parameter)"), -) -> DelayParams: - calculated_delay_ms = max(x_delay_ms or 0, delay_ms or 0) - return DelayParams(sleep_time_ms=calculated_delay_ms, sleep_time_s=calculated_delay_ms / 1000.0) \ No newline at end of file diff --git a/app/log.py.snapshot b/app/log.py.snapshot deleted file mode 100644 index c72dd49..0000000 --- a/app/log.py.snapshot +++ /dev/null @@ -1,100 +0,0 @@ -import functools, logging - -from app.config import settings - -# stream options: 'ext://sys.stderr' or 'ext://sys.stdout' - -# NOTE: This log config is confusing and may need work... 2022-10-07 -# 'uvicorn' under 'loggers' creates an output to the 'console' handler -# Do not also add 'console' handler to the 'root' 'handlers' list -# For now just using that to add or remove file logging options. -# logging.config.dictConfig({ -# 'version': 1, -# 'formatters': { -# 'default': {'format': '[%(asctime)s] %(levelname)s @ %(module)s.%(funcName)s()#%(lineno)d: %(message)s'}, -# 'long': {'format': '[%(asctime)s] %(levelname)s @ %(module)s.%(funcName)s()#%(lineno)d: %(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S'}, -# 'short': {'format': '[%(asctime)s] %(levelname)s @ %(module)s.%(funcName)s()#%(lineno)d: %(message)s', 'datefmt': '%H:%M:%S', 'use_colors': True}, -# }, -# #'filename': 'example.log', -# # 'level': logging.ERROR, -# 'handlers': { -# 'console': { -# 'class': 'logging.StreamHandler', -# 'stream': 'ext://sys.stderr', -# 'formatter': 'short', -# }, -# 'log_file_all': { -# 'level': 'NOTSET', -# 'class': 'logging.handlers.RotatingFileHandler', -# 'formatter': 'long', -# 'filename': settings.LOG_PATH['app'], -# 'maxBytes': 10485760, # 5,242,880 = 5 MB; 10,485,760 = 10 MB -# 'backupCount': 9 -# }, -# # 'log_file_warning': { -# # 'level': 'WARNING', -# # 'class': 'logging.handlers.RotatingFileHandler', -# # 'formatter': 'long', -# # 'filename': settings.LOG_PATH['app_warning'], -# # 'maxBytes': 512000, # 524,288 = 512KB -# # 'backupCount': 9 -# # }, -# # 'test_handler': { -# # 'class': 'logging.StreamHandler', -# # 'level': 'INFO', -# # 'formatter': 'short', -# # }, -# # 'test_handler_all_rotate': { -# # 'class': 'logging.handlers.RotatingFileHandler', -# # 'level': 'NOTSET', -# # 'formatter': 'short', -# # 'filename': '/logs/test_rotate.log', -# # 'maxBytes': 100000, # 5120000 = 5 MB -# # 'backupCount': 2, -# # } -# }, -# 'loggers': { -# # 'uvicorn': {'handlers': ['default'], 'level': 'INFO'}, -# 'uvicorn': {'handlers': ['console'], 'level': 'INFO'}, -# # 'uvicorn.error': {'level': 'INFO', 'handlers': ['default'], 'propagate': True}, -# # 'uvicorn.error': {'level': 'INFO', 'handlers': ['console'], 'propagate': True}, -# # 'uvicorn.access': {'handlers': ['access'], 'level': 'INFO', 'propagate': False}, -# # 'gunicorn': {'handlers': ['console'], 'level': 'INFO'}, -# }, -# 'root': { -# 'handlers': ['log_file_all'], #, 'log_file_all', 'log_file_warning'], -# # 'handlers': ['console', 'log_file_all'], #, 'log_file_all', 'log_file_warning'], -# 'level': 'WARNING', # WARNING -# } -# }) - - -# log = logging.getLogger('root') -# # log.setLevel(logging.INFO) # DEBUG > INFO > WARNING > ERROR > CRITICAL -# # logging.basicConfig( -# # format='[%(asctime)s] %(levelname)s @ %(module)s.%(funcName)s()#%(lineno)d: %(message)s' -# # ) - - - -# ### BEGIN ### Log ### logger_reset() ### -# https://realpython.com/primer-on-python-decorators/ -# Updated 2022-02-15 -# def logger_reset(func): -# # log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL -# # log.info(locals()) -# @functools.wraps(func) -# def wrapper(*args, **kwargs): -# if func.__name__ not in ['redis_lookup_id_random', 'sql_enable_part', 'sql_hidden_part']: -# log.info(f'*** Function: "{func.__name__}()"') -# log.debug(f'*** Function Positional Args: {args}\nFunction Key Args: {kwargs}') -# init_log_level = log.level -# returned_result = func(*args, **kwargs) -# log.debug(f'*** Function finished: "{func.__name__}()". Resetting logger level to level: {log.level} ***') -# log.setLevel(init_log_level) -# return returned_result -# return wrapper -# ### END ### Log ### logger_reset() ### - -def get_logger(name: str) -> logging.Logger: - return logging.getLogger(name) diff --git a/app/main.py.snapshot b/app/main.py.snapshot deleted file mode 100644 index 1cdc951..0000000 --- a/app/main.py.snapshot +++ /dev/null @@ -1,625 +0,0 @@ -import datetime, json, os, pytz, random, secrets # , uvicorn - -from enum import Enum -#from datetime import datetime, time, timedelta -from fastapi import Body, Cookie, Depends, FastAPI, File, Form, Header, HTTPException, Path, Query, Request, Response, status, UploadFile -from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, PlainTextResponse -from fastapi.staticfiles import StaticFiles -from functools import lru_cache -from pydantic import BaseModel, EmailStr, Field -from typing import Dict, List, Optional, Set, Union - -from . import config - -from app.log import log, logging - -# Import the routers here first: -from app.routers import ae_obj, aether_cfg, api_crud, api_crud_v2, api_crud_v3, api, importing, sql, account, activity_log, address, archive, archive_content, contact, cont_edu_cert, cont_edu_cert_person, data_store, event, event_abstract, event_badge, event_badge_importing, event_badge_template, event_device, event_exhibit, event_exhibit_tracking, event_file, event_importing, event_location, event_person, event_person_detail, event_person_tracking, event_presentation, event_presenter, event_registration, event_session, flask_cfg, fundraising, grant, hosted_file, journal, journal_entry, log_client_viewing, lookup, membership_cfg, membership_group, membership_person_group, membership_person, membership_person_profile, membership_type, membership_person_type, order, order_v3, order_line, order_cart, organization, page, person, person_user, post, post_comment, product, qr, site, site_domain, user, util_email, websockets_redis, e_confex, e_cvent, c_idaa, e_impexium, e_stripe - -# from app.routers import aether_cfg, sql - -from app.db_sql import sql_select, reset_redis # , sql_connect - - -print('### **** *** ** * The Aether API v4 using FastAPI is loading... * ** *** **** ###') - - - - - -app = FastAPI( - # debug = True, - title = 'Aether API', - description = 'One Sky IT\'s Aether API v4 using FastAPI.', - version = '4.9.0', - operationsSorter = 'method', - ) - - -log.setLevel(logging.INFO) -# log.debug(config.settings) - -if aether_cfg_sql_result := sql_select( - table_name = 'cfg', - record_id = config.settings.AETHER_CFG['id'], - as_list = False, - max_count = 1, - ): - aether_cfg_sql = aether_cfg_sql_result - - config.settings.DB['server'] = aether_cfg_sql.get('db_server') - config.settings.DB['port'] = aether_cfg_sql.get('db_port') - config.settings.DB['name'] = aether_cfg_sql.get('db_name') - config.settings.DB['username'] = aether_cfg_sql.get('db_username') - config.settings.DB['password'] = aether_cfg_sql.get('db_password') - - DB = config.settings.DB - config.settings.SQLALCHEMY_DB_URI = 'mysql://'+DB['username']+':'+DB['password']+'@'+DB['server']+'/'+DB['name'] - # db_result = sql_connect(config.settings.SQLALCHEMY_DB_URI) - log.debug(config.settings.DB) - - config.settings.SMTP['server'] = aether_cfg_sql.get('smtp_server') - config.settings.SMTP['port'] = aether_cfg_sql.get('smtp_port') - config.settings.SMTP['username'] = aether_cfg_sql.get('smtp_username') - config.settings.SMTP['password'] = aether_cfg_sql.get('smtp_password') - - # config.settings.FILES_PATH['hosted_files_root'] = aether_cfg_sql.get('PATH_HOSTED_FILES_ROOT') - # config.settings.FILES_PATH['hosted_tmp_root'] = aether_cfg_sql.get('PATH_HOSTED_TMP_ROOT') - - config.settings.FILES_PATH['hosted_files_root'] = aether_cfg_sql.get('path_hosted_files_root') - config.settings.FILES_PATH['hosted_tmp_root'] = aether_cfg_sql.get('path_hosted_tmp_root') -else: - # aether_cfg_sql_result - pass -log.debug(aether_cfg_sql_result) -log.debug(config.settings) - -# @lru_cache() -# def get_settings(): -# return config.Settings() - - -app.mount('/static', StaticFiles(directory='static'), name='static') - - -# Set up each route once the router has been imported -app.include_router( - ae_obj.router, - prefix='/ae_obj', - tags=['AE Object'], -) -app.include_router( - aether_cfg.router, - tags=['Aether Config'], -) -app.include_router( - api_crud.router, - prefix='/crud', - tags=['CRUD v1.2 (Legacy)'], - #dependencies=[Depends(get_token_header)], - #dependencies=[Depends(get_account_header)], - #responses={404: {'description': 'Not found'}}, -) -app.include_router( - api_crud_v2.router, - prefix='/v2/crud', - tags=['CRUD v2.5'], - #dependencies=[Depends(get_token_header)], - #dependencies=[Depends(get_account_header)], - #responses={404: {'description': 'Not found'}}, -) -app.include_router( - api_crud_v3.router, - prefix='/v3/crud', - tags=['CRUD v3'], -) -app.include_router( - api.router, - prefix='/api', - tags=['API'], -) -app.include_router( - flask_cfg.router, - prefix='/flask_cfg', - tags=['Flask CFG'], -) -app.include_router( - importing.router, - prefix='/importing', - tags=['Importing'], -) -app.include_router( - sql.router, - # prefix='/sql', - tags=['SQL'], -) -# # app.include_router( -# # flask_cfg.router, -# # prefix='/redis', -# # tags=['Redis'], -# # ) - -app.include_router( - account.router, - # prefix='/account', - tags=['Account'], -) -app.include_router( - activity_log.router, - prefix='/activity_log', - tags=['Activity Log'], -) -app.include_router( - address.router, - prefix='/address', - tags=['Address'], -) -app.include_router( - archive.router, - # prefix='/archive', - tags=['Archive'], -) -app.include_router( - archive_content.router, - prefix='/archive/content', - tags=['Archive Content'], -) -app.include_router( - contact.router, - prefix='/contact', - tags=['Contact'], -) -app.include_router( - cont_edu_cert.router, - tags=['Cont Edu Cert'], -) -app.include_router( - cont_edu_cert_person.router, - tags=['Cont Edu Cert Person'], -) -app.include_router( - data_store.router, - # prefix='/data_store', - tags=['Data Store'], -) -app.include_router( - event.router, - # prefix='/event', - tags=['Event'], -) -app.include_router( - event_abstract.router, - tags=['Event Abstract'], -) -app.include_router( - event_badge.router, - tags=['Event Badge'], -) -app.include_router( - event_badge_importing.router, - tags=['Event Badge Importing'], -) -app.include_router( - event_badge_template.router, - # prefix='/event/badge/template', - tags=['Event Badge Template'], -) -app.include_router( - event_device.router, - # prefix='/event/device', - tags=['Event Device'], -) -app.include_router( - event_exhibit.router, - # prefix='/event/exhibit', - tags=['Event Exhibit'], -) -app.include_router( - event_exhibit_tracking.router, - # prefix='/event/exhibit/tracking', - tags=['Event Exhibit Tracking'], -) -app.include_router( - event_file.router, - # prefix='/event/file', - tags=['Event File'], -) -app.include_router( - event_importing.router, - # prefix='/event/importing', - tags=['Event Importing'], -) -app.include_router( - event_location.router, - # prefix='/event/location', - tags=['Event Location'], -) -app.include_router( - event_person.router, - # prefix='/event/person', - tags=['Event Person'], -) -app.include_router( - event_person.router, - prefix='/event/person/detail', - tags=['Event Person Detail'], -) -app.include_router( - event_person_tracking.router, - tags=['Event Person Tracking'], -) -app.include_router( - event_presentation.router, - # prefix='/event/presentation', - tags=['Event Presentation'], -) -app.include_router( - event_presenter.router, - prefix='/event/presenter', - tags=['Event Presenter'], -) -app.include_router( - event_registration.router, - prefix='/event/registration', - tags=['Event Registration'], -) -app.include_router( - event_session.router, - # prefix='/event/session', - tags=['Event Session'], -) -app.include_router( - fundraising.router, - tags=['Fundraising'], -) -app.include_router( - grant.router, - tags=['Grant'], -) -app.include_router( - hosted_file.router, - prefix='/hosted_file', - tags=['Hosted File'], -) -app.include_router( - journal.router, - prefix='/journal', - tags=['Journal'], -) -app.include_router( - journal_entry.router, - # prefix='/journal/entry', - tags=['Journal Entry'], -) -app.include_router( - log_client_viewing.router, - # prefix='/log/client_viewing', - tags=['Log Client Viewing'], -) -app.include_router( - lookup.router, - prefix='/lu', - tags=['Lookup'], -) -app.include_router( - membership_cfg.router, - tags=['Membership Config'], -) -app.include_router( - membership_group.router, - tags=['Membership Group'], -) -app.include_router( - membership_person_group.router, - tags=['Membership Group Person'], -) -app.include_router( - membership_person_profile.router, - tags=['Membership Person Profile'], -) -app.include_router( - membership_person.router, - tags=['Membership Person'], -) -app.include_router( - membership_type.router, - tags=['Membership Type'], -) -app.include_router( - membership_person_type.router, - tags=['Membership Type Person'], -) -app.include_router( - order.router, - # prefix='/order', - tags=['Order'], -) -app.include_router( - order_v3.router, - # prefix='/order', - tags=['Order v3'], -) -app.include_router( - order_line.router, - # prefix='/order', - tags=['Order Line'], -) -app.include_router( - order_cart.router, - prefix='/order/cart', - tags=['Order Cart'], -) -app.include_router( - organization.router, - prefix='/organization', - tags=['Organization'], -) -app.include_router( - page.router, - prefix='/page', - tags=['Page'], -) -app.include_router( - person.router, - tags=['Person'], -) -app.include_router( - person_user.router, - prefix='/person_user', - tags=['Person User'], -) -app.include_router( - post.router, - # prefix='/post', - tags=['Post'], -) -app.include_router( - post_comment.router, - prefix='/post/comment', - tags=['Post Comment'], -) -app.include_router( - product.router, - # prefix='/product', - tags=['Product'], -) -app.include_router( - qr.router, - tags=['QR'], -) -app.include_router( - site.router, - # prefix='/site', - tags=['Site'], -) -app.include_router( - site_domain.router, - # prefix='/site/domain', - tags=['Site Domain'], -) -app.include_router( - user.router, - tags=['User'], -) -app.include_router( - util_email.router, - tags=['Utility: Email'], -) -# app.include_router( -# websockets.router, -# # prefix='/websocket', -# tags=['Websockets'], -# # dependencies=[Depends(get_token_header)], -# # responses={404: {'description': 'Not found'}}, -# ) -app.include_router( - websockets_redis.router, - tags=['Websockets (Redis)'], -) -app.include_router( - e_confex.router, - prefix='/e/confex', - tags=['External Service: Confex'], -) -app.include_router( - e_cvent.router, - prefix='/e/cvent', - tags=['External Service: Cvent'], -) -app.include_router( - e_impexium.router, - prefix='/e/impexium', - tags=['External Service: Impexium'], -) -app.include_router( - e_stripe.router, - prefix='/e/stripe', - tags=['External Service: Stripe'], -) - -app.include_router( - c_idaa.router, - prefix='/c/idaa', - tags=['Client: IDAA'], -) - - -# BEGIN: CORS -# NOTE: Eventually this should query the DB for the specific list based on the cfg table and or site_domain table. That way it is dynamic and only allowing those defined in the DB. No wildcards or regex. -# NOTE: Need to include .localhost for less browser restrictions! Mainly for audio and video. -app.add_middleware( - CORSMiddleware, - # allow_origins = origins, - allow_origins = config.settings.ORIGINS, - allow_origin_regex = config.settings.ORIGINS_REGEX, - # allow_origin_regex = 'https://.*\.oneskyit\.com', - allow_credentials = True, - allow_methods = ['*'], - allow_headers = ['*'], - #expose_headers = [], - #max_age = 600, -) -# END: CORS - - -@app.on_event('startup') -async def startup(): - log.setLevel(logging.INFO) # DEBUG, INFO, WARN, WARNING, ERROR, EXCEPTION, CRITICAL - log.debug(locals()) - - log.info('The Aether FastAPI API is starting up...') - #await database.connect() - - -@app.on_event('shutdown') -async def shutdown(): - log.setLevel(logging.INFO) # DEBUG, INFO, WARN, WARNING, ERROR, EXCEPTION, CRITICAL - log.debug(locals()) - - log.info('The Aether FastAPI API is shutting down...') - #await database.disconnect() - - -#Add the processing time to the response header. -@app.middleware('http') -async def add_process_time_header(request: Request, call_next): - import time - start_time = time.time() - response = await call_next(request) - process_time = time.time() - start_time - response.headers['X-Process-Time'] = str(process_time) - return response - - -# ### BEGIN ### API Main ### fastapi_root() ### -@app.get('/', tags=['Root'], response_class=PlainTextResponse) -async def fastapi_root(response: Response = Response): - log.setLevel(logging.DEBUG) # DEBUG, INFO, WARN, WARNING, ERROR, EXCEPTION, CRITICAL - log.debug(locals()) - - # log.info(config.settings.APP_NAME) - log.info('One Sky IT\'s Aether API root (FastAPI)') - - log.info('***') - log.debug('This is debug') # 10 DEBUG - log.info('This is info') # 20 INFO - log.warning('This is a warning') # 30 WARNING (and WARN) - log.error('This is an error') # 40 ERROR - log.exception('This is an exception') # 40 ERROR - log.critical('This is critical') # 50 CRITICAL - log.info('^^^') - - log.warning('Resetting Redis...') - reset_redis() - log.info('Reset Redis') - - response_data = {} - response_data['message'] = 'This is One Sky IT\'s Aether API root (FastAPI).' - - - current_datetime = datetime.datetime.now() - current_datetime_string = current_datetime.isoformat() - - timezone = pytz.timezone("America/New_York") - current_datetime_tz = timezone.localize(current_datetime) - current_datetime_tz_string = current_datetime_tz.isoformat() - - current_datetime_utc = datetime.datetime.utcnow() - current_datetime_utc_string = current_datetime_utc.isoformat() - - current_datetime_utc_localize = pytz.utc.localize(current_datetime_utc) - current_datetime_utc_localize_string = current_datetime_utc_localize.isoformat() - - current_datetime_utc_localize_pst = current_datetime_utc_localize.astimezone(pytz.timezone("America/Los_Angeles")) - current_datetime_utc_localize_pst_string = current_datetime_utc_localize_pst.isoformat() - - response_data['datetime'] = current_datetime_string - response_data['datetime_tz'] = current_datetime_tz_string - response_data['datetime_utc'] = current_datetime_utc_string - response_data['datetime_utc_localize'] = current_datetime_utc_localize_string - response_data['datetime_utc_localize_pst'] = current_datetime_utc_localize_pst_string - - response_data['url_safe_string_4_bytes_1'] = secrets.token_urlsafe(4) - - response_data['url_safe_string_8_bytes_1'] = secrets.token_urlsafe(8) - response_data['url_safe_string_8_bytes_2'] = secrets.token_urlsafe(8) - response_data['url_safe_string_8_bytes_3'] = secrets.token_urlsafe(8) - response_data['url_safe_string_8_bytes_4'] = secrets.token_urlsafe(8) - response_data['url_safe_string_8_bytes_5'] = secrets.token_urlsafe(8) - - response_data['url_safe_string_16_bytes_1'] = secrets.token_urlsafe(16) - response_data['url_safe_string_16_bytes_2'] = secrets.token_urlsafe(16) - response_data['url_safe_string_16_bytes_3'] = secrets.token_urlsafe(16) - response_data['url_safe_string_16_bytes_4'] = secrets.token_urlsafe(16) - response_data['url_safe_string_16_bytes_5'] = secrets.token_urlsafe(16) - - response_data['hex_string_4_bytes_1'] = secrets.token_hex(4) - response_data['hex_string_8_bytes_1'] = secrets.token_hex(8) - response_data['hex_string_16_bytes_1'] = secrets.token_hex(16) - response_data['hex_string_32_bytes_1'] = secrets.token_hex(32) - - log.debug(json.dumps(response_data, indent=4)) - return json.dumps(response_data, indent=4) # , sort_keys=True -# ### END ### API Main ### fastapi_root() ### - - -# ### BEGIN ### API Main ### generate_id_random() ### -# NOTE: This is just a quick utility function to generate a bunch of random IDs. -# Updated 2022-03-30 -@app.get('/generate_id_random', tags=['Root'], response_class=PlainTextResponse) -async def generate_id_random(response: Response = Response): - log.setLevel(logging.INFO) # DEBUG, INFO, WARN, WARNING, ERROR, EXCEPTION, CRITICAL - log.debug(locals()) - - response_data = {} - - html_list = '