Skip to content

Commit 515f5d0

Browse files
committed
Add step folders
1 parent 75b92bd commit 515f5d0

File tree

22 files changed

+561
-0
lines changed

22 files changed

+561
-0
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# FastAPI URL Shortener
2+
3+
## Install the Project
4+
5+
1. Create a Python virtual environment
6+
7+
```sh
8+
$ python -m venv venv
9+
$ source venv/bin/activate
10+
(venv) $
11+
```
12+
13+
2. Install the requirements
14+
15+
```
16+
(venv) $ python -m pip install -r requirements.txt
17+
```
18+
19+
## Run the Project
20+
21+
You can run the project with this command in your terminal:
22+
23+
```sh
24+
(venv) $ uvicorn shortener_app.main:app --reload
25+
```
26+
27+
Your server will reload automacially when you change a file.
28+
29+
## Verify Your Environment Variables
30+
31+
The project provides default environment settings in [`shortener_app/config.py`](shortener_app/config.py).
32+
While you can use the default settings, [it's recommended](https://12factor.net/config) to create a `.env` file to store your settings outside of your production code. E.g.:
33+
34+
```config
35+
# .env
36+
ENV_NAME="Development"
37+
BASE_URL="http://url.shortener"
38+
DB_URL="sqlite:///./test_database.db"
39+
```
40+
41+
With an `.env` file that contains the `ENV_NAME` variable with the value `"Development"` you can verify if your external `.env` file loads correctly:
42+
43+
```pycon
44+
>>> from shortener_app.config import get_settings
45+
>>> get_settings().env_name
46+
... loading Settings
47+
'Development'
48+
```
49+
50+
To get an overview of the environment variables you can set, check the [`shortener_app/config.py`](shortener_app/config.py) file.
51+
52+
> ☝️ **Note:** You should never add the `.env` file to your version control system.
53+
54+
## Visit the Documentation
55+
56+
When the project is running you can visit the documentation in your browser:
57+
58+
- http://127.0.0.1:8000/docs
59+
- http://127.0.0.1:8000/redoc
60+
61+
## About the Author
62+
63+
Philipp Acsany - Email: [email protected]
64+
65+
## License
66+
67+
Distributed under the MIT license. See `LICENSE` in the root directory of this `materials` repo for more information.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
anyio==3.6.0
2+
asgiref==3.5.1
3+
click==8.1.3
4+
decorator==5.1.1
5+
fastapi==0.75.0
6+
h11==0.13.0
7+
idna==3.3
8+
pydantic==1.9.0
9+
python-dotenv==0.19.2
10+
six==1.16.0
11+
sniffio==1.2.0
12+
SQLAlchemy==1.4.32
13+
starlette==0.17.1
14+
typing_extensions==4.2.0
15+
uvicorn==0.17.6
16+
validators==0.18.2

fastapi-url-shortener/source_code_step_1/shortener_app/__init__.py

Whitespace-only changes.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from functools import lru_cache
2+
3+
from pydantic import BaseSettings
4+
5+
6+
class Settings(BaseSettings):
7+
env_name: str = "Local"
8+
base_url: str = "http://localhost:8000"
9+
db_url: str = "sqlite:///./shortener.db"
10+
11+
class Config:
12+
env_file = ".env"
13+
14+
15+
@lru_cache
16+
def get_settings() -> Settings:
17+
settings = Settings()
18+
print(f"Loading settings for: {settings.env_name}")
19+
return settings
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# FastAPI URL Shortener
2+
3+
## Install the Project
4+
5+
1. Create a Python virtual environment
6+
7+
```sh
8+
$ python -m venv venv
9+
$ source venv/bin/activate
10+
(venv) $
11+
```
12+
13+
2. Install the requirements
14+
15+
```
16+
(venv) $ python -m pip install -r requirements.txt
17+
```
18+
19+
## Run the Project
20+
21+
You can run the project with this command in your terminal:
22+
23+
```sh
24+
(venv) $ uvicorn shortener_app.main:app --reload
25+
```
26+
27+
Your server will reload automacially when you change a file.
28+
29+
## Verify Your Environment Variables
30+
31+
The project provides default environment settings in [`shortener_app/config.py`](shortener_app/config.py).
32+
While you can use the default settings, [it's recommended](https://12factor.net/config) to create a `.env` file to store your settings outside of your production code. E.g.:
33+
34+
```config
35+
# .env
36+
ENV_NAME="Development"
37+
BASE_URL="http://url.shortener"
38+
DB_URL="sqlite:///./test_database.db"
39+
```
40+
41+
With an `.env` file that contains the `ENV_NAME` variable with the value `"Development"` you can verify if your external `.env` file loads correctly:
42+
43+
```pycon
44+
>>> from shortener_app.config import get_settings
45+
>>> get_settings().env_name
46+
... loading Settings
47+
'Development'
48+
```
49+
50+
To get an overview of the environment variables you can set, check the [`shortener_app/config.py`](shortener_app/config.py) file.
51+
52+
> ☝️ **Note:** You should never add the `.env` file to your version control system.
53+
54+
## Visit the Documentation
55+
56+
When the project is running you can visit the documentation in your browser:
57+
58+
- http://127.0.0.1:8000/docs
59+
- http://127.0.0.1:8000/redoc
60+
61+
## About the Author
62+
63+
Philipp Acsany - Email: [email protected]
64+
65+
## License
66+
67+
Distributed under the MIT license. See `LICENSE` in the root directory of this `materials` repo for more information.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
anyio==3.6.0
2+
asgiref==3.5.1
3+
click==8.1.3
4+
decorator==5.1.1
5+
fastapi==0.75.0
6+
h11==0.13.0
7+
idna==3.3
8+
pydantic==1.9.0
9+
python-dotenv==0.19.2
10+
six==1.16.0
11+
sniffio==1.2.0
12+
SQLAlchemy==1.4.32
13+
starlette==0.17.1
14+
typing_extensions==4.2.0
15+
uvicorn==0.17.6
16+
validators==0.18.2

fastapi-url-shortener/source_code_step_2/shortener_app/__init__.py

Whitespace-only changes.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from functools import lru_cache
2+
3+
from pydantic import BaseSettings
4+
5+
6+
class Settings(BaseSettings):
7+
env_name: str = "Local"
8+
base_url: str = "http://localhost:8000"
9+
db_url: str = "sqlite:///./shortener.db"
10+
11+
class Config:
12+
env_file = ".env"
13+
14+
15+
@lru_cache
16+
def get_settings() -> Settings:
17+
settings = Settings()
18+
print(f"Loading settings for: {settings.env_name}")
19+
return settings
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from sqlalchemy import create_engine
2+
from sqlalchemy.ext.declarative import declarative_base
3+
from sqlalchemy.orm import sessionmaker
4+
5+
from .config import get_settings
6+
7+
engine = create_engine(
8+
get_settings().db_url, connect_args={"check_same_thread": False}
9+
)
10+
SessionLocal = sessionmaker(
11+
autocommit=False, autoflush=False, bind=engine
12+
)
13+
Base = declarative_base()
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import secrets
2+
import validators
3+
4+
from fastapi import Depends, FastAPI, HTTPException, Request
5+
from fastapi.responses import RedirectResponse
6+
from sqlalchemy.orm import Session
7+
8+
from . import models, schemas
9+
from .database import SessionLocal, engine
10+
11+
app = FastAPI()
12+
models.Base.metadata.create_all(bind=engine)
13+
14+
15+
def get_db():
16+
db = SessionLocal()
17+
try:
18+
yield db
19+
finally:
20+
db.close()
21+
22+
23+
def raise_bad_request(message):
24+
raise HTTPException(status_code=400, detail=message)
25+
26+
27+
def raise_not_found(request):
28+
message = f"URL '{request.url}' doesn't exist"
29+
raise HTTPException(status_code=404, detail=message)
30+
31+
32+
@app.get("/")
33+
def read_root():
34+
return "Welcome to the URL shortener API :)"
35+
36+
37+
@app.post("/url", response_model=schemas.URLInfo)
38+
def create_url(url: schemas.URLBase, db: Session = Depends(get_db)):
39+
if not validators.url(url.target_url):
40+
raise_bad_request(message="Your provided URL is not valid")
41+
42+
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
43+
key = "".join(secrets.choice(chars) for _ in range(5))
44+
secret_key = "".join(secrets.choice(chars) for _ in range(8))
45+
db_url = models.URL(
46+
target_url=url.target_url, key=key, secret_key=secret_key
47+
)
48+
db.add(db_url)
49+
db.commit()
50+
db.refresh(db_url)
51+
db_url.url = key
52+
db_url.admin_url = secret_key
53+
54+
return db_url
55+
56+
57+
@app.get("/{url_key}")
58+
def forward_to_target_url(
59+
url_key: str, request: Request, db: Session = Depends(get_db)
60+
):
61+
db_url = (
62+
db.query(models.URL)
63+
.filter(models.URL.key == url_key, models.URL.is_active)
64+
.first()
65+
)
66+
if db_url:
67+
return RedirectResponse(db_url.target_url)
68+
else:
69+
raise_not_found(request)

0 commit comments

Comments
 (0)