├── .gitignore ├── README.md ├── api.py ├── config.py.sample ├── requirements.txt ├── sample_rawbody.txt └── sample_requests.txt /.gitignore: -------------------------------------------------------------------------------- 1 | config.py 2 | venv 3 | env 4 | __pycache__ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pre requirements 2 | - Setup Ubuntu ODBC Drivers\FreeTDS 3 | - Python 3.6 4 | - PIP 5 | - Python Virtual Enviroment: https://realpython.com/python-virtual-environments-a-primer/ 6 | - To install pip and Virtual Enviroment on Ubuntu 20.04 apt install python3-pip python3-venv 7 | 8 | 9 | # Setup 10 | 1. Go in to cloned directory 11 | 2. Create Python3 Virtual Enviroment 12 | - python3 -m venv env 13 | 3. Active the Virtaul Enviroment 14 | - source env/bin/activat 15 | - You should see (env) infront of your prompt 16 | 4. Install Setup tools and pip 17 | - pip3 install -U setuptools pip wheel 18 | 5. Install the required Python 3 Library's use the list below or use the requirements file 19 | - pip install FastAPI 20 | - pip install uvicorn 21 | - pip install pydantic 22 | - pip install pyodbc 23 | - #(Mysql) pip install mysql-connector-python 24 | 6. Move or copy config.py.sample to config.py 25 | 7. Run uvicorn web server 26 | - uvicorn api:app --reload --host=0.0.0.0 --port=9000 27 | 8. Make sure there are no uvicorn console errors 28 | 9. Run some RestAPI Querys to the server 29 | 10. Visit the swagger API doc page 30 | - Swagger: http://:9000/docs 31 | - ReDoc: http://:9000/redoc 32 | - OpenAPI: http://:9000/openapi.json 33 | 11. Learn more about FastAPI: https://fastapi.tiangolo.com/ 34 | 35 | 36 | -------------------------------------------------------------------------------- /api.py: -------------------------------------------------------------------------------- 1 | # Fast API's Tutorial: https://fastapi.tiangolo.com/tutorial/ 2 | 3 | from fastapi import FastAPI, Body, Query, Header, Depends, Request 4 | from pydantic import BaseModel, Field 5 | import pyodbc 6 | from config import db 7 | 8 | # Connect to the database 9 | # Using the Linux FreeTDS Driver\ODBC 10 | connect_string = f'DRIVER=FreeTDS;SERVER={db["server"]};PORT={db["port"]};DATABASE={db["database"]};TDS_Version=8.0;UID={db["user"]};PWD={db["password"]}' 11 | try: 12 | conn = pyodbc.connect(connect_string) 13 | except pyodbc.Error as e: 14 | print(e) 15 | exit() 16 | 17 | 18 | # Function to return the sql results as a dict. 19 | # It also maps the column names and values for the dict 20 | # Returns no results found if there are no records 21 | def mssql_result2dict(cursor): 22 | try: 23 | result = [] 24 | columns = [column[0] for column in cursor.description] 25 | for row in cursor.fetchall(): 26 | result.append(dict(zip(columns,row))) 27 | 28 | # print(result) 29 | 30 | #Check for results 31 | if len(result) > 0: 32 | ret = result 33 | else: 34 | ret = {"message": "no results found"} 35 | except pyodbc.Error as e: 36 | print(e) 37 | ret = { "message": "Internal Database Query Error"} 38 | 39 | return ret 40 | 41 | 42 | # CLRUD aka CRUD model to update your database 43 | # Create - Create record in the database 44 | # List - List all records 45 | # Read - Read one record 46 | # Update - Update one record 47 | # Delete - Delete one record 48 | class TestTableModel: 49 | # Database table 50 | table = "dbo.TestTable" 51 | 52 | # Incase you use sql views 53 | view = "dbo.TestTable" 54 | 55 | def create(self, data): 56 | sql = f'INSERT INTO {self.table} ([name],[value],[date], [comment]) OUTPUT INSERTED.id VALUES (?,?,?,?);' 57 | 58 | try: 59 | cursor = conn.cursor() 60 | row = cursor.execute(sql, data.name, data.value, data.date, data.comment).fetchone() 61 | conn.commit() 62 | ret = {"message": "created", "id": row[0]} 63 | except pyodbc.Error as e: 64 | print(f'Insert Failed') 65 | print(e) 66 | ret = {"message": "failed to create record"} 67 | 68 | return ret 69 | 70 | 71 | def list(self, id = None): 72 | sql = f'SELECT * FROM {self.view}' 73 | 74 | try: 75 | cursor = conn.cursor() 76 | cursor.execute(sql) 77 | ret = mssql_result2dict(cursor) 78 | conn.commit() 79 | except pyodbc.Error as e: 80 | print(f'SQL Query Failed: {e}') 81 | ret = {"message": "system error"} 82 | 83 | return ret 84 | 85 | def read(self, id = None): 86 | if not id: 87 | return {"message": "id not set"} 88 | 89 | sql = f'SELECT * FROM {self.view} WHERE id=?' 90 | 91 | try: 92 | cursor = conn.cursor() 93 | cursor.execute(sql, id) 94 | ret = mssql_result2dict(cursor) 95 | conn.commit() 96 | except pyodbc.Error as e: 97 | print(f'SQL Query Failed: {e}') 98 | ret = {"message": "system error"} 99 | 100 | return ret 101 | 102 | 103 | def update(self,id = None, data = None): 104 | if not id: 105 | return {"message": "id not set"} 106 | 107 | sql = f'UPDATE {self.table} set name=?, value=?, date=?, comment=? WHERE id=?' 108 | 109 | try: 110 | cursor = conn.cursor() 111 | cursor.execute(sql, data.name, data.value, data.date, data.comment, id) 112 | ret = {"message": "updated"} 113 | conn.commit() 114 | except pyodbc.Error as e: 115 | print(f'SQL Query Failed: {e}') 116 | ret = {"message": "system error"} 117 | 118 | return ret 119 | 120 | def delete(self,id = None): 121 | if not id: 122 | return {"message": "id not set"} 123 | 124 | sql = f'DELETE FROM {self.table} WHERE id=?' 125 | 126 | try: 127 | cursor = conn.cursor() 128 | cursor.execute(sql, id) 129 | ret = {"message": "deleted"} 130 | conn.commit() 131 | except pyodbc.Error as e: 132 | print(f'SQL Query Failed: {e}') 133 | ret = {"message": "system error"} 134 | 135 | return ret 136 | 137 | 138 | 139 | # Create the FastAPI App 140 | app = FastAPI( 141 | title="Fast API Example", 142 | description="Example API", 143 | version="1.0" 144 | ) 145 | 146 | 147 | # Class used to parse the body of POST and PUT Requests 148 | # This is pydantic 149 | class TestTable_Body(BaseModel): 150 | name: str = None 151 | value: str = None 152 | date: str = None 153 | comment: str = None 154 | 155 | 156 | # FastAPI Endpoints 157 | # tag groups api calls in the swagger UI 158 | # If you want to include query Paramaters: https://fastapi.tiangolo.com/tutorial/query-params/ 159 | @app.post("/testtable", tags=['testing']) 160 | async def testtable_create(data: TestTable_Body, request: Request): 161 | t = TestTableModel() 162 | return t.create(data) 163 | 164 | @app.get("/testtable/{id}", tags=['testing']) 165 | async def testtable_read(id: int = None): 166 | t = TestTableModel() 167 | return t.read(id) 168 | 169 | @app.get("/testtable", tags=['testing']) 170 | async def testtable_list(): 171 | t = TestTableModel() 172 | return t.list() 173 | 174 | @app.put("/testtable/{id}", tags=['testing']) 175 | async def testtable_update(data: TestTable_Body, id: int = None): 176 | t = TestTableModel() 177 | return t.update(id, data) 178 | 179 | @app.delete("/testtable/{id}", tags=['testing']) 180 | async def testtable_delete(id: int = None): 181 | t = TestTableModel() 182 | return t.delete(id) 183 | 184 | 185 | # How to get the raw body of a request 186 | # https://www.starlette.io/requests/ 187 | # If you don't want to use the pydantic body parser 188 | # You can get the raw requse body. 189 | # Make sure to include request: Request in the function 190 | @app.post("/rawbody", tags=['Raw Resquest Body']) 191 | async def rawbody(request: Request): 192 | # Parse the body as bytes 193 | # body = await request.body() 194 | 195 | # Parse the body as a form 196 | # body = await request.form() 197 | 198 | # Parse the body as a json request 199 | body = await request.json() 200 | 201 | # Print will print to the uvicorn console 202 | print(body) 203 | 204 | # Do something 205 | 206 | # Return a response 207 | return {"message": "success", "mybody": body} 208 | -------------------------------------------------------------------------------- /config.py.sample: -------------------------------------------------------------------------------- 1 | # Sampel Config file 2 | # Rename or copy to config.py 3 | 4 | db = { 5 | "server": "mssqldb.my.network", 6 | "port": "1433", 7 | "database": "MyDatabase", 8 | "user": "my_db_user", 9 | "password": "super_secret" 10 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click==7.1.2 2 | dataclasses==0.7 3 | fastapi==0.54.1 4 | h11==0.9.0 5 | httptools==0.1.1 6 | pkg-resources==0.0.0 7 | pydantic==1.5.1 8 | pyodbc==4.0.30 9 | starlette==0.13.2 10 | uvicorn==0.11.5 11 | uvloop==0.14.0 12 | websockets==8.1 13 | -------------------------------------------------------------------------------- /sample_rawbody.txt: -------------------------------------------------------------------------------- 1 | POST 172.19.1.85:9000/rawbody 2 | 3 | Request Body 4 | { 5 | "body": "this is my body", 6 | "similiar": "There are many like it", 7 | "unique": "but this one is mine" 8 | } 9 | 10 | Response Body 11 | { 12 | "message": "success", 13 | "mybody": { 14 | "body": "this is my body", 15 | "similiar": "There are many like it", 16 | "unique": "but this one is mine" 17 | } 18 | } 19 | 20 | 21 | uvicorn console shows the following 22 | {'body': 'this is my body', 'similiar': 'There are many like it', 'unique': 'but this one is mine'} 23 | INFO: 10.115.253.4:11032 - "POST /rawbody HTTP/1.1" 200 OK -------------------------------------------------------------------------------- /sample_requests.txt: -------------------------------------------------------------------------------- 1 | URL: 172.19.1.85:9000/testtable 2 | 3 | ============================================================================== 4 | GET 172.19.1.85:9000/testtable 5 | 6 | Response Body 7 | [ 8 | { 9 | "Id": 1, 10 | "name": "MyName", 11 | "value": "MyValue", 12 | "date": "2020-05-01T15:50:00", 13 | "comment": "My Super long comment" 14 | }, 15 | { 16 | "Id": 2, 17 | "name": "MyName2", 18 | "value": "MyValue2", 19 | "date": "2020-05-01T15:50:00", 20 | "comment": "My Super long comment.2222222222222" 21 | } 22 | ] 23 | 24 | ============================================================================== 25 | POST 172.19.1.85:9000/testtable 26 | 27 | Request Body 28 | { 29 | "name": "John Doe", 30 | "value": "I am Positive", 31 | "date": "2020-05-01 15:50:00", 32 | "comment": "Another Comment" 33 | } 34 | 35 | Response Body 36 | { 37 | "message": "created", 38 | "id": 27 39 | } 40 | 41 | ============================================================================== 42 | PUT 172.19.1.85:9000/testtable/27 43 | 44 | Request Body 45 | { 46 | "name": "John Doe Sr.", 47 | "value": "I am Positive", 48 | "date": "2020-05-01 20:50:00", 49 | "comment": "Another Comment" 50 | } 51 | 52 | Response Body 53 | { 54 | "message": "updated" 55 | } 56 | 57 | ============================================================================== 58 | GET 172.19.1.85:9000/testtable/27 59 | 60 | Response Body 61 | [ 62 | { 63 | "Id": 27, 64 | "name": "John Doe Sr.", 65 | "value": "I am Positive", 66 | "date": "2020-05-01T20:50:00", 67 | "comment": "Another Comment" 68 | } 69 | ] 70 | 71 | ============================================================================== 72 | DELETE 172.19.1.85:9000/testtable/27 73 | 74 | Response Body 75 | { 76 | "message": "deleted" 77 | } 78 | 79 | ============================================================================== 80 | GET 172.19.1.85:9000/testtable/27 81 | # Ran after record 27 was deleted, so there is no result 82 | 83 | Response Body 84 | { 85 | "message": "no results found" 86 | } --------------------------------------------------------------------------------