Initial commit with the basics
This commit is contained in:
155
.gitignore
vendored
Normal file → Executable file
155
.gitignore
vendored
Normal file → Executable file
@@ -1,33 +1,108 @@
|
|||||||
# These are some examples of commonly ignored file patterns.
|
# Byte-compiled / optimized / DLL files
|
||||||
# You should customize this list as applicable to your project.
|
__pycache__/
|
||||||
# Learn more about .gitignore:
|
|
||||||
# https://www.atlassian.com/git/tutorials/saving-changes/gitignore
|
|
||||||
|
|
||||||
# Node artifact files
|
|
||||||
node_modules/
|
|
||||||
dist/
|
|
||||||
|
|
||||||
# Compiled Java class files
|
|
||||||
*.class
|
|
||||||
|
|
||||||
# Compiled Python bytecode
|
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
# Log files
|
# C extensions
|
||||||
*.log
|
*.so
|
||||||
|
|
||||||
# Package files
|
# Distribution / packaging
|
||||||
*.jar
|
.Python
|
||||||
|
build/
|
||||||
# Maven
|
develop-eggs/
|
||||||
target/
|
|
||||||
dist/
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
# JetBrains IDE
|
# PyInstaller
|
||||||
.idea/
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
# Unit test reports
|
# Installer logs
|
||||||
TEST*.xml
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# celery beat schedule file
|
||||||
|
celerybeat-schedule
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
environment/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
|
||||||
# Generated by MacOS
|
# Generated by MacOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
@@ -35,15 +110,23 @@ TEST*.xml
|
|||||||
# Generated by Windows
|
# Generated by Windows
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
# Applications
|
# Added by Scott Idem
|
||||||
*.app
|
# https://github.com/github/gitignore
|
||||||
*.exe
|
*.sock
|
||||||
*.war
|
*.csv
|
||||||
|
*.xlsx
|
||||||
# Large media files
|
#*.pdf
|
||||||
*.mp4
|
*.cfg
|
||||||
*.tiff
|
*.ini
|
||||||
*.avi
|
*.bak
|
||||||
*.flv
|
*.pid
|
||||||
*.mov
|
flask_config.py
|
||||||
*.wmv
|
config.py
|
||||||
|
#config.cfg
|
||||||
|
#users.cfg
|
||||||
|
.directory
|
||||||
|
tmp/
|
||||||
|
temp/
|
||||||
|
development/
|
||||||
|
myapp/files/
|
||||||
|
myapp/file_distribution/
|
||||||
|
|||||||
44
admin/documentation/run locally.txt
Normal file
44
admin/documentation/run locally.txt
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Go to root of application
|
||||||
|
cd ~/path/to/directory/my_application/
|
||||||
|
|
||||||
|
# Create new application environment
|
||||||
|
virtualenv environment
|
||||||
|
|
||||||
|
# Activate application environment
|
||||||
|
source environment/bin/activate
|
||||||
|
|
||||||
|
# Install application requirements
|
||||||
|
pip install -r admin/requirements.txt
|
||||||
|
|
||||||
|
# Start application
|
||||||
|
uvicorn app.main:app --host 0.0.0.0 --port 5005 --reload
|
||||||
|
|
||||||
|
# View app
|
||||||
|
http://localhost:5005
|
||||||
|
|
||||||
|
# Deactivate environment when done
|
||||||
|
deactivate
|
||||||
|
|
||||||
|
|
||||||
|
# Use git
|
||||||
|
# Go to root of application
|
||||||
|
cd ~/path/to/directory/my_application/
|
||||||
|
|
||||||
|
git init
|
||||||
|
|
||||||
|
git remote add origin https://scott_idem@bitbucket.org/oneskyit/one-sky-it-api.git
|
||||||
|
git add .
|
||||||
|
git commit -m 'Initial commit'
|
||||||
|
|
||||||
|
git push -u origin master
|
||||||
|
git push -u origin development
|
||||||
|
git push -u origin new-branch-name
|
||||||
|
|
||||||
|
# List branches
|
||||||
|
git branch -a
|
||||||
|
|
||||||
|
# Create new branch
|
||||||
|
git branch new-branch-name
|
||||||
|
|
||||||
|
# Switch branch
|
||||||
|
git switch new-branch-name
|
||||||
4
admin/requirements.txt
Normal file
4
admin/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
uvicorn
|
||||||
|
fastapi[all]
|
||||||
|
SQLAlchemy
|
||||||
|
mysqlclient
|
||||||
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
18
app/config.py.default
Normal file
18
app/config.py.default
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import secrets
|
||||||
|
from pydantic import AnyHttpUrl, BaseSettings, EmailStr, HttpUrl, PostgresDsn, validator
|
||||||
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
APP_NAME: str = "Aether API"
|
||||||
|
ADMIN_EMAIL: EmailStr = 'example@example.com'
|
||||||
|
|
||||||
|
AETHER_DB_SERVER = 'xxx'
|
||||||
|
AETHER_DB_PORT = '3306' # default = 3306
|
||||||
|
AETHER_DB_NAME = 'xxx'
|
||||||
|
AETHER_DB_USERNAME = 'xxx'
|
||||||
|
AETHER_DB_PASSWORD = 'xxx'
|
||||||
|
SQLALCHEMY_DATABASE_URI = 'mysql://'+AETHER_DB_USERNAME+':'+AETHER_DB_PASSWORD+'@'+AETHER_DB_SERVER+'/'+AETHER_DB_NAME
|
||||||
|
|
||||||
|
|
||||||
|
settings = Settings()
|
||||||
171
app/db.py
Normal file
171
app/db.py
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
from app.config import settings
|
||||||
|
|
||||||
|
from sqlalchemy import create_engine, text
|
||||||
|
from sqlalchemy.exc import IntegrityError, OperationalError
|
||||||
|
#from app import db
|
||||||
|
|
||||||
|
AMS_DB_SERVER = 'linode.oneskyit.com'
|
||||||
|
AMS_DB_PORT = '3306' # default = 3306
|
||||||
|
AMS_DB_NAME = 'aether_dev' #onesky_ams_dev
|
||||||
|
AMS_DB_USERNAME = 'onesky_aether'
|
||||||
|
AMS_DB_PASSWORD = '$onesky.Aether.2020'
|
||||||
|
|
||||||
|
|
||||||
|
connection_string = 'mysql://'+AMS_DB_USERNAME+':'+AMS_DB_PASSWORD+'@'+AMS_DB_SERVER+'/'+AMS_DB_NAME
|
||||||
|
engine = create_engine(name_or_url=connection_string, pool_size=10, pool_recycle=120, pool_pre_ping=True, echo=True, echo_pool=True, isolation_level='READ COMMITTED')
|
||||||
|
# NOTE: The default isolation_level is 'REPEATABLE READ'. This can sometimes not show updated data.
|
||||||
|
|
||||||
|
db = engine.connect()
|
||||||
|
|
||||||
|
|
||||||
|
# Insert a new record with values given.
|
||||||
|
def sql_insert(table_name=None, record=None, sql=None, data=None):
|
||||||
|
print('** sql_insert() ***')
|
||||||
|
|
||||||
|
if table_name and record:
|
||||||
|
fields = []
|
||||||
|
values = []
|
||||||
|
for key, value in record.items():
|
||||||
|
if key != 'id': # A special exception for the id auto increment field.
|
||||||
|
fields.append('`'+str(key)+'`')
|
||||||
|
values.append(':'+str(key))
|
||||||
|
fields_string = ', '.join(fields)
|
||||||
|
values_string = ', '.join(values)
|
||||||
|
|
||||||
|
sql_insert = text(
|
||||||
|
"""
|
||||||
|
INSERT INTO `"""+table_name+"""` ("""+fields_string+""") VALUES ("""+values_string+""");
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
elif table_name:
|
||||||
|
sql_insert = text(
|
||||||
|
"""
|
||||||
|
INSERT INTO `"""+table_name+"""` () VALUES ();
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
elif sql:
|
||||||
|
sql_insert = text(sql)
|
||||||
|
else:
|
||||||
|
print('One or more required fields are missing')
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
trans = db.begin()
|
||||||
|
try:
|
||||||
|
if record:
|
||||||
|
result_insert = db.execute(sql_insert, record)
|
||||||
|
else:
|
||||||
|
result_insert = db.execute(sql_insert)
|
||||||
|
trans.commit()
|
||||||
|
except OperationalError as e:
|
||||||
|
trans.rollback()
|
||||||
|
print('*** An exception happened: OperationalError ***')
|
||||||
|
print('* This is likely because a field that does not exist. *')
|
||||||
|
print(repr(e))
|
||||||
|
print('***')
|
||||||
|
print(str(e))
|
||||||
|
print('^^^ exception ^^^')
|
||||||
|
return False
|
||||||
|
except IntegrityError as e:
|
||||||
|
trans.rollback()
|
||||||
|
print('*** An exception happened: IntegrityError ***')
|
||||||
|
print('* This is likely because of a duplicate entry for a primary or unique field. *')
|
||||||
|
print(repr(e))
|
||||||
|
print('***')
|
||||||
|
print(str(e))
|
||||||
|
print('^^^ exception ^^^')
|
||||||
|
return True # NOTE: This is returning True even though there was an exception
|
||||||
|
except Exception as e:
|
||||||
|
trans.rollback()
|
||||||
|
print('*** An exception happened: catch all ***')
|
||||||
|
print(repr(e))
|
||||||
|
print('***')
|
||||||
|
print(str(e))
|
||||||
|
print('^^^ exception ^^^')
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
record_id = result_insert.lastrowid
|
||||||
|
if record_id == 0:
|
||||||
|
#print('******')
|
||||||
|
#print(dir(result_insert))
|
||||||
|
#print('******')
|
||||||
|
#print(vars(result_insert))
|
||||||
|
#print('******')
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return record_id
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE: Select records using custom SQL SELECT statements.
|
||||||
|
def sql_select(sql=None, data=None, table_name=None, record_id=None, record_id_random=None, field_name=None, field_value=None, as_list=False):
|
||||||
|
print('*** sql_select() ***')
|
||||||
|
|
||||||
|
if record_id and table_name:
|
||||||
|
sql = text(
|
||||||
|
"""
|
||||||
|
SELECT *
|
||||||
|
FROM `"""+table_name+"""`
|
||||||
|
WHERE `"""+table_name+"""`.id = :record_id
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
elif record_id_random and table_name:
|
||||||
|
sql = text(
|
||||||
|
"""
|
||||||
|
SELECT *
|
||||||
|
FROM `"""+table_name+"""`
|
||||||
|
WHERE `"""+table_name+"""`.id_random = :record_id_random
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
elif field_name and field_value and table_name:
|
||||||
|
sql = text(
|
||||||
|
"""
|
||||||
|
SELECT *
|
||||||
|
FROM `"""+table_name+"""`
|
||||||
|
WHERE `"""+table_name+"""`."""+field_name+""" = :field_value
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
elif table_name:
|
||||||
|
sql = text(
|
||||||
|
"""
|
||||||
|
SELECT *
|
||||||
|
FROM `"""+table_name+"""`
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
elif sql:
|
||||||
|
print('SQL found')
|
||||||
|
sql = text(sql)
|
||||||
|
else:
|
||||||
|
print('One or more required fields are missing')
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
#if record_id or record_id_random:
|
||||||
|
#result = db.execute(sql, record_id=record_id, record_id_random=record_id_random)
|
||||||
|
#elif field_name and field_value:
|
||||||
|
#result = db.execute(sql, field_value=field_value)
|
||||||
|
#elif sql and data:
|
||||||
|
#result = db.execute(sql, data)
|
||||||
|
print('Executing SQL...')
|
||||||
|
result = db.execute(sql, data=data, record_id=record_id, record_id_random=record_id_random, table_name=table_name, field_name=field_name, field_value=field_value)
|
||||||
|
except Exception as e:
|
||||||
|
print('*** An exception happened. ***')
|
||||||
|
print(repr(e))
|
||||||
|
print('***')
|
||||||
|
print(str(e))
|
||||||
|
print('^^^ exception ^^^')
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if result.rowcount == 1 and as_list:
|
||||||
|
print('Single as list')
|
||||||
|
record = result.fetchall()
|
||||||
|
return record
|
||||||
|
elif result.rowcount == 1 and not as_list:
|
||||||
|
print('Single as single')
|
||||||
|
record = result.fetchone()
|
||||||
|
return record
|
||||||
|
elif result.rowcount > 1:
|
||||||
|
print('List as list')
|
||||||
|
records = result.fetchall()
|
||||||
|
return records
|
||||||
|
else:
|
||||||
|
return False
|
||||||
52
app/lib_general.py
Normal file
52
app/lib_general.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
from datetime import datetime, time, timedelta
|
||||||
|
from fastapi import APIRouter, Depends, Header, HTTPException, status
|
||||||
|
from pydantic import BaseModel, EmailStr, Field
|
||||||
|
from typing import Dict, List, Optional, Set, Union
|
||||||
|
|
||||||
|
|
||||||
|
#router = APIRouter()
|
||||||
|
|
||||||
|
#import app
|
||||||
|
|
||||||
|
async def get_token_header(x_token: str = Header(...)):
|
||||||
|
if x_token != 'fake-super-secret-token':
|
||||||
|
raise HTTPException(status_code=400, detail='X-Token header invalid')
|
||||||
|
|
||||||
|
|
||||||
|
async def get_account_header(x_account_id: str = Header(...)):
|
||||||
|
print('get_account_header(): '+x_account_id)
|
||||||
|
return x_account_id
|
||||||
|
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
||||||
|
|
||||||
|
#async def get_token_header(x_token: str = Header(...)):
|
||||||
|
#if x_token != 'fake-super-secret-token':
|
||||||
|
#raise HTTPException(status_code=400, detail='X-Token header invalid')
|
||||||
|
|
||||||
|
|
||||||
|
#async def get_account_header(x_account_id: str = Header(...)):
|
||||||
|
#@app.middleware("http")
|
||||||
|
#async def get_account_header(x_account_id: str = Header(...)):
|
||||||
|
#return x_account_id
|
||||||
|
#x_account_id: str = Header(...)
|
||||||
|
#x_account_id = 'static random ID...'
|
||||||
|
#response = await call_next(request)
|
||||||
|
|
||||||
|
#print(x_account_id)
|
||||||
|
|
||||||
|
#return x_account_id
|
||||||
|
|
||||||
|
|
||||||
|
#async def get_account_header(x_account_id: str = Header(...)):
|
||||||
|
#print('get_account_header(): '+x_account_id+'z9999z')
|
||||||
|
#return x_account_id+'z9999z'
|
||||||
93
app/main.py
Normal file
93
app/main.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from datetime import datetime, time, timedelta
|
||||||
|
from enum import Enum
|
||||||
|
from fastapi import Body, Cookie, Depends, FastAPI, File, Form, Header, HTTPException, Path, Query, Request, status, UploadFile
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from fastapi.responses import 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 sqlalchemy import create_engine, text
|
||||||
|
from sqlalchemy.exc import IntegrityError, OperationalError
|
||||||
|
|
||||||
|
from . import config
|
||||||
|
from .lib_general import *
|
||||||
|
from .routers import items, users, websockets
|
||||||
|
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache()
|
||||||
|
def get_settings():
|
||||||
|
return config.Settings()
|
||||||
|
|
||||||
|
|
||||||
|
app.mount('/static', StaticFiles(directory='static'), name='static')
|
||||||
|
|
||||||
|
|
||||||
|
app.include_router(
|
||||||
|
users.router,
|
||||||
|
prefix='/user',
|
||||||
|
tags=['Users'],
|
||||||
|
#dependencies=[Depends(get_token_header)],
|
||||||
|
#dependencies=[Depends(get_account_header)],
|
||||||
|
#responses={404: {'description': 'Not found'}},
|
||||||
|
)
|
||||||
|
app.include_router(
|
||||||
|
items.router,
|
||||||
|
prefix='/item',
|
||||||
|
tags=['Items'],
|
||||||
|
#dependencies=[Depends(get_token_header)],
|
||||||
|
#responses={404: {'description': 'Not found'}},
|
||||||
|
)
|
||||||
|
app.include_router(
|
||||||
|
websockets.router,
|
||||||
|
#prefix='/item',
|
||||||
|
tags=['Websockets'],
|
||||||
|
#dependencies=[Depends(get_token_header)],
|
||||||
|
#responses={404: {'description': 'Not found'}},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# BEGIN: CORS
|
||||||
|
origins = [
|
||||||
|
'http://fastapi.localhost',
|
||||||
|
'http://localhost',
|
||||||
|
'http://localhost:5000',
|
||||||
|
'http://fastapi.localhost:5000',
|
||||||
|
'https://example.org',
|
||||||
|
]
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=origins,
|
||||||
|
allow_origin_regex='https://.*\.oneskyit\.com',
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=['*'],
|
||||||
|
allow_headers=['*'],
|
||||||
|
#expose_headers=[],
|
||||||
|
#max_age=600,
|
||||||
|
)
|
||||||
|
# END: CORS
|
||||||
|
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/', tags=['Default'])
|
||||||
|
async def get_root():
|
||||||
|
print(config.settings.APP_NAME)
|
||||||
|
|
||||||
|
return {'hello': 'This is the Aether API using FastAPI.'}
|
||||||
0
app/routers/__init__.py
Normal file
0
app/routers/__init__.py
Normal file
43
app/routers/items.py
Normal file
43
app/routers/items.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException, status
|
||||||
|
from pydantic import BaseModel, EmailStr, Field
|
||||||
|
from typing import Dict, List, Optional, Set, Union
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
class Image(BaseModel):
|
||||||
|
url: str
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class Item(BaseModel):
|
||||||
|
name: str
|
||||||
|
description: Optional[str] = Field(None, example='A very nice Item')
|
||||||
|
price: float
|
||||||
|
tax: Optional[float] = None
|
||||||
|
is_offer: Optional[bool] = None
|
||||||
|
#tags: List[str] = [] # not unique tags
|
||||||
|
tags: Set[str] = set() # unique tags
|
||||||
|
image: Optional[Image] = None # one image
|
||||||
|
images: Optional[List[Image]] = None # or as a list of images
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/')
|
||||||
|
async def read_items():
|
||||||
|
return [{'name': 'Item Foo'}, {'name': 'item Bar'}]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/{item_id}')
|
||||||
|
async def read_item(item_id: str):
|
||||||
|
return {'name': 'Fake Specific Item', 'item_id': item_id}
|
||||||
|
|
||||||
|
|
||||||
|
@router.put(
|
||||||
|
'/{item_id}',
|
||||||
|
tags=['Extra Tag'],
|
||||||
|
responses={403: {'description': 'Operation forbidden'}},
|
||||||
|
)
|
||||||
|
async def update_item(item_id: str):
|
||||||
|
if item_id != 'foo':
|
||||||
|
raise HTTPException(status_code=403, detail='You can only update the item: foo')
|
||||||
|
return {'item_id': item_id, 'name': 'The Fighters'}
|
||||||
24
app/routers/items_basic.py
Normal file
24
app/routers/items_basic.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/")
|
||||||
|
async def read_items():
|
||||||
|
return [{"name": "Item Foo"}, {"name": "item Bar"}]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{item_id}")
|
||||||
|
async def read_item(item_id: str):
|
||||||
|
return {"name": "Fake Specific Item", "item_id": item_id}
|
||||||
|
|
||||||
|
|
||||||
|
@router.put(
|
||||||
|
"/{item_id}",
|
||||||
|
tags=["custom"],
|
||||||
|
responses={403: {"description": "Operation forbidden"}},
|
||||||
|
)
|
||||||
|
async def update_item(item_id: str):
|
||||||
|
if item_id != "foo":
|
||||||
|
raise HTTPException(status_code=403, detail="You can only update the item: foo")
|
||||||
|
return {"item_id": item_id, "name": "The Fighters"}
|
||||||
153
app/routers/users.py
Normal file
153
app/routers/users.py
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
from datetime import datetime, time, timedelta
|
||||||
|
from fastapi import APIRouter, Depends, Header, HTTPException, status
|
||||||
|
from pydantic import BaseModel, EmailStr, Field
|
||||||
|
from typing import Dict, List, Optional, Set, Union
|
||||||
|
|
||||||
|
from ..lib_general import *
|
||||||
|
from app.config import settings
|
||||||
|
|
||||||
|
from app.db import *
|
||||||
|
|
||||||
|
|
||||||
|
#import logging
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
class UserBase(BaseModel):
|
||||||
|
id_random: str = None # This should not be None. It is required.
|
||||||
|
account_id_random: str = None # This should not be None. It is required.
|
||||||
|
username: str = Field(None, example='New.User', min_length=3, max_length=100)
|
||||||
|
name: Optional[str] = None
|
||||||
|
email: EmailStr
|
||||||
|
email_verified: Optional[bool] = None
|
||||||
|
enable: Optional[bool] = None
|
||||||
|
enable_from: Optional[datetime] = None
|
||||||
|
enable_to: Optional[datetime] = None
|
||||||
|
super: Optional[bool] = None
|
||||||
|
manager: Optional[bool] = None
|
||||||
|
administrator: Optional[bool] = None
|
||||||
|
verified: Optional[bool] = None
|
||||||
|
|
||||||
|
|
||||||
|
class UserIn(UserBase):
|
||||||
|
password: str = Field(None, example='My Difficult Password!', min_length=10)
|
||||||
|
|
||||||
|
|
||||||
|
class UserOut(UserBase):
|
||||||
|
password_set_on: Optional[datetime] = None
|
||||||
|
password_reset_token: Optional[str] = None
|
||||||
|
password_reset_expire_on: Optional[datetime] = None
|
||||||
|
logged_in_on: Optional[datetime] = None
|
||||||
|
last_activity_on: Optional[datetime] = None
|
||||||
|
created_on: datetime
|
||||||
|
update_on: Optional[datetime] = None
|
||||||
|
|
||||||
|
|
||||||
|
class UserInDB(UserBase):
|
||||||
|
hashed_password: str
|
||||||
|
password_set_on: Optional[datetime] = None
|
||||||
|
password_reset_token: Optional[str] = None
|
||||||
|
password_reset_expire_on: Optional[datetime] = None
|
||||||
|
logged_in_on: Optional[datetime] = None
|
||||||
|
last_activity_on: Optional[datetime] = None
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/",
|
||||||
|
response_model=UserOut,
|
||||||
|
summary='Create a new user account',
|
||||||
|
status_code=status.HTTP_201_CREATED
|
||||||
|
)
|
||||||
|
async def create_user(user: UserIn, x_account_id: str = Header(...)):
|
||||||
|
"""
|
||||||
|
Create a new user account
|
||||||
|
"""
|
||||||
|
|
||||||
|
user = {}
|
||||||
|
user['account_id_random'] = x_account_id
|
||||||
|
user['username'] = 'Scott.Idem'
|
||||||
|
user['name'] = 'Scott Idem'
|
||||||
|
user['email'] = 'Scott.Idem@oneskyit.com'
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
#@router.patch('/{id_random}', response_model=UserOut, dependencies=[Depends(get_account_header)])
|
||||||
|
#async def update_user(id_random: str, user: UserIn, x_account_id: str = Header(...)):
|
||||||
|
#async def update_user(id_random: str, user: UserIn):
|
||||||
|
@router.patch(
|
||||||
|
'/{id_random}',
|
||||||
|
response_model=UserOut,
|
||||||
|
summary='Update a user account'
|
||||||
|
)
|
||||||
|
async def update_user(id_random: str, user: UserIn, x_account_id: str = Depends(get_account_header)):
|
||||||
|
"""
|
||||||
|
Update a user account
|
||||||
|
"""
|
||||||
|
|
||||||
|
user = {}
|
||||||
|
user['id_random'] = id_random
|
||||||
|
user['account_id_random'] = x_account_id
|
||||||
|
user['username'] = 'Scott.Idem'
|
||||||
|
user['name'] = 'Scott Idem'
|
||||||
|
user['email'] = 'Scott.Idem@oneskyit.com'
|
||||||
|
user['created_on'] = datetime.now()
|
||||||
|
user['super'] = True
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete('/{id_random}', response_model=bool)
|
||||||
|
async def delete_user(id_random: str, x_account_id: str = Depends(get_account_header)):
|
||||||
|
"""
|
||||||
|
Delete a user account
|
||||||
|
"""
|
||||||
|
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/', response_model=List[UserOut])
|
||||||
|
@router.get('/list_all', response_model=List[UserOut])
|
||||||
|
async def list_users():
|
||||||
|
"""
|
||||||
|
Get a list of users
|
||||||
|
"""
|
||||||
|
|
||||||
|
print(settings.APP_NAME)
|
||||||
|
|
||||||
|
users = [{'username': 'test.user.1'}, {'username': 'test.user.2'}, {'username': 'Scott.Idem'}]
|
||||||
|
|
||||||
|
|
||||||
|
print('Getting all users...')
|
||||||
|
|
||||||
|
sql = """
|
||||||
|
SELECT *
|
||||||
|
FROM `user`
|
||||||
|
WHERE id=1
|
||||||
|
"""
|
||||||
|
|
||||||
|
records = sql_select(sql=sql, as_list=True)
|
||||||
|
|
||||||
|
#records = sql_select(table_name='user')
|
||||||
|
|
||||||
|
|
||||||
|
if records:
|
||||||
|
print('Got the user list')
|
||||||
|
return records
|
||||||
|
else:
|
||||||
|
print('No user records found')
|
||||||
|
raise HTTPException(status_code=404)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/{username}')
|
||||||
|
async def get_user_username(username: str, x_account_id: str = Header(...)):
|
||||||
|
return {'username': username}
|
||||||
|
|
||||||
|
|
||||||
|
#@router.get('/me')
|
||||||
|
#async def get_user_current():
|
||||||
|
#user_out: UserOut
|
||||||
|
|
||||||
|
#return {'username': 'test.user'}
|
||||||
18
app/routers/users_basic.py
Normal file
18
app/routers/users_basic.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/users/", tags=["users"])
|
||||||
|
async def read_users():
|
||||||
|
return [{"username": "Foo"}, {"username": "Bar"}]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/users/me", tags=["users"])
|
||||||
|
async def read_user_me():
|
||||||
|
return {"username": "fakecurrentuser"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/users/{username}", tags=["users"])
|
||||||
|
async def read_user(username: str):
|
||||||
|
return {"username": username}
|
||||||
83
app/routers/websockets.py
Normal file
83
app/routers/websockets.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
from fastapi import APIRouter, FastAPI, WebSocket, WebSocketDisconnect
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
html = """
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Chat</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>WebSocket Chat</h1>
|
||||||
|
<h2>Your ID: <span id="ws-id"></span></h2>
|
||||||
|
<form action="" onsubmit="sendMessage(event)">
|
||||||
|
<input type="text" id="messageText" autocomplete="off"/>
|
||||||
|
<button>Send</button>
|
||||||
|
</form>
|
||||||
|
<ul id='messages'>
|
||||||
|
</ul>
|
||||||
|
<script>
|
||||||
|
var client_id = Date.now()
|
||||||
|
document.querySelector("#ws-id").textContent = client_id;
|
||||||
|
var ws = new WebSocket(`ws://localhost:5005/ws/${client_id}`);
|
||||||
|
ws.onmessage = function(event) {
|
||||||
|
var messages = document.getElementById('messages')
|
||||||
|
var message = document.createElement('li')
|
||||||
|
var content = document.createTextNode(event.data)
|
||||||
|
message.appendChild(content)
|
||||||
|
messages.appendChild(message)
|
||||||
|
};
|
||||||
|
function sendMessage(event) {
|
||||||
|
var input = document.getElementById("messageText")
|
||||||
|
ws.send(input.value)
|
||||||
|
input.value = ''
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.active_connections: List[WebSocket] = []
|
||||||
|
|
||||||
|
async def connect(self, websocket: WebSocket):
|
||||||
|
await websocket.accept()
|
||||||
|
self.active_connections.append(websocket)
|
||||||
|
|
||||||
|
def disconnect(self, websocket: WebSocket):
|
||||||
|
self.active_connections.remove(websocket)
|
||||||
|
|
||||||
|
async def send_personal_message(self, message: str, websocket: WebSocket):
|
||||||
|
await websocket.send_text(message)
|
||||||
|
|
||||||
|
async def broadcast(self, message: str):
|
||||||
|
for connection in self.active_connections:
|
||||||
|
await connection.send_text(message)
|
||||||
|
|
||||||
|
|
||||||
|
manager = ConnectionManager()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/ws_test")
|
||||||
|
async def websocket_root():
|
||||||
|
return HTMLResponse(html)
|
||||||
|
|
||||||
|
|
||||||
|
@router.websocket("/ws/{client_id}")
|
||||||
|
async def websocket_endpoint(websocket: WebSocket, client_id: int):
|
||||||
|
await manager.connect(websocket)
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
data = await websocket.receive_text()
|
||||||
|
await manager.send_personal_message(f"You wrote: {data}", websocket)
|
||||||
|
await manager.broadcast(f"Client #{client_id} says: {data}")
|
||||||
|
except WebSocketDisconnect:
|
||||||
|
manager.disconnect(websocket)
|
||||||
|
await manager.broadcast(f"Client #{client_id} left the chat")
|
||||||
BIN
static/favicon.ico
Executable file
BIN
static/favicon.ico
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
1
static/test.txt
Normal file
1
static/test.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
Reference in New Issue
Block a user