Skip to content
This repository was archived by the owner on Jul 29, 2024. It is now read-only.

Commit a9bf116

Browse files
authored
chore: refactor app dir & add .devcontainer (#74)
* chore: refactor app dir & add .devcontainer * chore: update codes by code review * chore: update codes by code review
1 parent 6e484d4 commit a9bf116

File tree

20 files changed

+176
-157
lines changed

20 files changed

+176
-157
lines changed

.devcontainer/devcontainer.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "fastapi-app",
3+
"dockerComposeFile": ["../docker-compose.yml"],
4+
"service": "api",
5+
"workspaceFolder": "/app",
6+
"customizations": {
7+
"vscode": {
8+
"extensions": [
9+
"ms-python.python",
10+
"ms-python.flake8",
11+
"ms-python.black-formatter",
12+
"ms-vscode-remote.remote-containers"
13+
],
14+
"settings": {
15+
"files.encoding": "utf8",
16+
"files.eol": "\n",
17+
"editor.formatOnSave": true,
18+
"[python]": {
19+
"editor.defaultFormatter": "ms-python.black-formatter"
20+
},
21+
"flake8.args": ["--max-line-length=88", "--ignore=E203,W503,W504"]
22+
}
23+
}
24+
},
25+
"forwardPorts": [8000]
26+
}

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
11
## Description
22

33
-
4-
5-
## Related Issues
6-
7-
<!--
8-
Link to the issue that is fixed by this PR (if there is one)
9-
e.g. Fixes #1234, Addresses #1234, Related to #1234, etc.
10-
-->

.vscode/extensions.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"recommendations": [
33
"ms-python.python",
44
"ms-python.flake8",
5-
"ms-python.black-formatter"
5+
"ms-python.black-formatter",
6+
"ms-vscode-remote.remote-containers"
67
]
78
}

README.md

Lines changed: 44 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -8,58 +8,33 @@
88

99
---
1010

11-
## Setup development environment
11+
## Setup development environment (Docker compose)
1212

13-
Please install `Docker` and `Docker compose` first.
13+
Please install [`Docker` and `Docker compose`](https://www.docker.com/) first.
1414

15-
https://www.docker.com/
15+
## Manual setup
1616

1717
After installation, run the following command to create a local Docker container.
1818

19-
```bash
20-
docker-compose build
21-
docker-compose up -d
22-
```
23-
24-
If you want to check the log while Docker container is running, then try to use following command:
25-
26-
```bash
19+
```sh
2720
docker-compose up
2821
```
2922

3023
If Docker is running successfully, the API and DB server will be launched as shown in the following:
3124

3225
- API server: http://localhost:8000
26+
- API Docs: http://localhost:8000/v1/docs
3327
- DB server: http://localhost:3306
3428

3529
_Be careful, it won't work if the port is occupied by another application._
3630

37-
If you want to check docker is actually working, then you can check it with following command:
38-
39-
```bash
40-
docker ps
41-
```
42-
43-
If you want to go inside of docker container, then try to use following command:
44-
45-
```bash
46-
docker-compose exec mysql bash
47-
docker-compose exec api bash
48-
```
49-
50-
For shutdown of the docker instance, please use following command:
51-
52-
```bash
53-
docker-compose down
54-
```
55-
56-
## Need a front-end app?
31+
## Setup with the VS Code Dev Containers extension
5732

58-
If you need a front-end app for this server-side & DB server.
33+
The above setup can be used for development, but you can also setup dev env with using the [VS Code Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers).
5934

60-
You can clone the front-end template from:
61-
62-
- https://github.com/qlawmarq/nuxt3-tailwind-auth-app
35+
- Install VS code and the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers).
36+
- Run the `Dev Containers: Open Folder in Container...` command from the Command Palette or quick actions Status bar item, and select the project folder.
37+
- Wait until the building of the application is finished, then access the application url
6338

6439
---
6540

@@ -69,29 +44,39 @@ You can clone the front-end template from:
6944

7045
If you're [VS Code](https://code.visualstudio.com/) user, you can easily setup Python code formatter (black) and linter (flake8) by simply installing the extensions.
7146

72-
Automatic formatting settings have already been defined here:
73-
74-
`.vscode/settings.json`
47+
Automatic formatting settings have already been defined [`.vscode/settings.json`](./.vscode/settings.json).
7548

7649
Just install following:
7750

7851
- [Black Formatter](https://marketplace.visualstudio.com/items?itemName=ms-python.black-formatter)
7952
- [Flake8](https://marketplace.visualstudio.com/items?itemName=ms-python.flake8)
8053

54+
If you are using the Dev Container, this configuration is already done in [the Dev Container settings](./.devcontainer/devcontainer.json), so you can skip it.
55+
8156
### How to check the DB tables in container
8257

83-
You can check the DB data by actually executing a query using the following command:
58+
Use following command to go inside of docker container:
59+
60+
```sh
61+
docker-compose exec mysql sh
62+
```
63+
64+
Then use `mysql` command to execute a query:
8465

85-
```bash
86-
docker-compose exec mysql bash
66+
```sh
8767
mysql -u root -p
8868
mysql> USE fastapi_app;
8969
mysql> SHOW TABLES;
70+
mysql> SELECT * FROM user;
9071
```
9172

73+
Your initial MySQL password is defined in `mysql/local.env`.
74+
9275
### How to add a library
9376

94-
You may want to add libraries such as requests, in which case follow these steps:
77+
Python libraries used in this app are defined in `api/requirements.txt`.
78+
79+
Also you may want to add libraries such as requests, in which case follow these steps:
9580

9681
- Add the library to requirements.txt
9782

@@ -103,23 +88,15 @@ requests==2.30.0
10388

10489
Then try a re-build and see.
10590

106-
```
91+
```sh
92+
docker-compose down
10793
docker-compose build
10894
docker-compose up
10995
```
11096

111-
### Python library packages
112-
113-
Some of the Python packages used in this app are defined in `api/requirements.txt`.
114-
Also you can add other packages there.
115-
11697
### Environment variable
11798

118-
Some of environment variable, like a database name and user is defined in `docker-compose.yml`.
119-
You can customize it as you like.
120-
121-
If you will use docker, then please define your environment variable to `docker-compose.yml`.
122-
However, you will NOT use docker, then please create `.env` file for your API server.
99+
Some of environment variable, like a database name and user is defined in `docker-compose.yml` or `Dockerfile`.
123100

124101
### DB Migrations
125102

@@ -130,10 +107,21 @@ The sample table definition has already been created with the name `create_user_
130107

131108
### Save the local DB changes as a dump file
132109

133-
```bash
110+
If you need to share local DB changes with other developers, you can use `mysqldump` to create a backup and share it with them.
111+
112+
To create a `dump.sql', run the following command:
113+
114+
```sh
134115
docker-compose exec database mysqldump -u root -p fastapi_app > mysql/db/dump.sql
135116
```
136117

137-
### API documentation
118+
Then, to reinitialize the DB, remove the named volumes declared in the "volumes" section of the Compose file.
119+
120+
https://docs.docker.com/engine/reference/commandline/compose_down/
121+
122+
```sh
123+
docker-compose down -v
124+
```
138125

139-
http://localhost:8000/redoc
126+
Then, run `docker-compose up` to launch the development environment.
127+
And confirm that your DB changes are reflected.

api/auth/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +0,0 @@
1-
from .provider import *

api/auth/controllers.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from fastapi import HTTPException, status
2-
from database.query import query_put
2+
from database.connector import DatabaseConnector
33
from auth.provider import AuthProvider
44
from auth.models import SignUpRequestModel
55
from user.controllers import get_users_by_email
@@ -8,13 +8,14 @@
88

99

1010
def register_user(user_model: SignUpRequestModel):
11+
database = DatabaseConnector()
1112
user = get_users_by_email(user_model.email)
1213
if len(user) != 0:
1314
raise HTTPException(
1415
status_code=status.HTTP_409_CONFLICT, detail="Email already exists."
1516
)
1617
hashed_password = auth_handler.get_password_hash(user_model.password)
17-
query_put(
18+
database.query_put(
1819
"""
1920
INSERT INTO user (
2021
first_name,

api/auth/provider.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
from datetime import datetime, timedelta
22
from typing import Annotated
3-
from database import query_get
3+
from database.connector import DatabaseConnector
44
from fastapi import Depends, HTTPException, status
55
from fastapi.security import OAuth2PasswordBearer
66
from jose import JWTError, jwt
77
from passlib.context import CryptContext
88
from pydantic import BaseModel
99
import os
1010

11+
db_connector = DatabaseConnector()
12+
1113
OAUTH2_SCHEME = OAuth2PasswordBearer(tokenUrl="token")
1214

1315
CREDENTIALS_EXCEPTION = HTTPException(
@@ -122,7 +124,7 @@ async def get_current_user(
122124
return user
123125

124126
def get_user_by_email(self, user_email: str) -> AuthUser:
125-
user = query_get(
127+
user = db_connector.query_get(
126128
"""
127129
SELECT
128130
user.id,

api/auth/routers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
auth_handler = AuthProvider()
1818

1919

20-
@router.post("/v1/signup", response_model=UserAuthResponseModel)
20+
@router.post("/v1/auth/signup", response_model=UserAuthResponseModel)
2121
def signup_api(user_details: SignUpRequestModel):
2222
"""
2323
This sign-up API allow you to register your account, and return access token.
@@ -36,7 +36,7 @@ def signup_api(user_details: SignUpRequestModel):
3636
)
3737

3838

39-
@router.post("/v1/signin", response_model=UserAuthResponseModel)
39+
@router.post("/v1/auth/signin", response_model=UserAuthResponseModel)
4040
def signin_api(user_details: SignInRequestModel):
4141
"""
4242
This sign-in API allow you to obtain your access token.
@@ -55,7 +55,7 @@ def signin_api(user_details: SignInRequestModel):
5555
)
5656

5757

58-
@router.post("/v1/refresh-token", response_model=AccessTokenResponseModel)
58+
@router.post("/v1/auth/refresh-token", response_model=AccessTokenResponseModel)
5959
def refresh_token_api(refresh_token: str):
6060
"""
6161
This refresh-token API allow you to obtain new access token.

api/database/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +0,0 @@
1-
# database module
2-
from .query import query_get, query_put, query_update

api/database/connector.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from fastapi import HTTPException, status
2+
import os
3+
import pymysql.cursors
4+
from pymysql import converters
5+
6+
7+
class DatabaseConnector:
8+
def __init__(self):
9+
self.host = os.getenv("DATABASE_HOST")
10+
self.user = os.getenv("DATABASE_USERNAME")
11+
self.password = os.getenv("DATABASE_PASSWORD")
12+
self.database = os.getenv("DATABASE")
13+
self.port = int(os.getenv("DATABASE_PORT"))
14+
self.conversions = converters.conversions
15+
self.conversions[pymysql.FIELD_TYPE.BIT] = (
16+
lambda x: False if x == b"\x00" else True
17+
)
18+
if not self.host:
19+
raise EnvironmentError("DATABASE_HOST environment variable not found")
20+
if not self.user:
21+
raise EnvironmentError("DATABASE_USERNAME environment variable not found")
22+
if not self.password:
23+
raise EnvironmentError("DATABASE_PASSWORD environment variable not found")
24+
if not self.database:
25+
raise EnvironmentError("DATABASE environment variable not found")
26+
27+
def get_connection(self):
28+
connection = pymysql.connect(
29+
host=self.host,
30+
port=self.port,
31+
user=self.user,
32+
password=self.password,
33+
database=self.database,
34+
cursorclass=pymysql.cursors.DictCursor,
35+
conv=self.conversions,
36+
)
37+
return connection
38+
39+
def query_get(self, sql, param):
40+
try:
41+
connection = self.get_connection()
42+
with connection:
43+
with connection.cursor() as cursor:
44+
cursor.execute(sql, param)
45+
return cursor.fetchall()
46+
except Exception as e:
47+
raise HTTPException(
48+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
49+
detail="Database error: " + str(e),
50+
)
51+
52+
def query_put(self, sql, param):
53+
try:
54+
connection = self.get_connection()
55+
with connection:
56+
with connection.cursor() as cursor:
57+
cursor.execute(sql, param)
58+
connection.commit()
59+
return cursor.lastrowid
60+
except Exception as e:
61+
raise HTTPException(
62+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
63+
detail="Database error: " + str(e),
64+
)

0 commit comments

Comments
 (0)