├── part-07-database
├── app
│ ├── __init__.py
│ ├── db
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── session.py
│ │ └── base_class.py
│ ├── models
│ │ ├── __init__.py
│ │ ├── recipe.py
│ │ └── user.py
│ ├── crud
│ │ ├── __init__.py
│ │ ├── crud_recipe.py
│ │ └── crud_user.py
│ ├── schemas
│ │ ├── __init__.py
│ │ ├── user.py
│ │ └── recipe.py
│ ├── deps.py
│ ├── initial_data.py
│ ├── recipe_data.py
│ └── backend_pre_start.py
├── alembic
│ ├── README
│ └── script.py.mako
├── .flake8
├── run.sh
├── prestart.sh
├── prestart.py
├── pyproject.toml
└── README.md
├── part-09-async
├── app
│ ├── __init__.py
│ ├── api
│ │ ├── __init__.py
│ │ ├── api_v1
│ │ │ ├── __init__.py
│ │ │ ├── endpoints
│ │ │ │ └── __init__.py
│ │ │ └── api.py
│ │ └── deps.py
│ ├── db
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── session.py
│ │ └── base_class.py
│ ├── core
│ │ ├── __init__.py
│ │ └── config.py
│ ├── models
│ │ ├── __init__.py
│ │ ├── recipe.py
│ │ └── user.py
│ ├── crud
│ │ ├── __init__.py
│ │ ├── crud_recipe.py
│ │ └── crud_user.py
│ ├── schemas
│ │ ├── __init__.py
│ │ ├── user.py
│ │ └── recipe.py
│ ├── initial_data.py
│ ├── backend_pre_start.py
│ └── main.py
├── alembic
│ ├── README
│ └── script.py.mako
├── .flake8
├── run.sh
├── prestart.sh
├── prestart.py
├── pyproject.toml
└── README.md
├── part-10-jwt-auth
├── app
│ ├── __init__.py
│ ├── api
│ │ ├── __init__.py
│ │ └── api_v1
│ │ │ ├── __init__.py
│ │ │ ├── endpoints
│ │ │ └── __init__.py
│ │ │ └── api.py
│ ├── core
│ │ ├── __init__.py
│ │ ├── security.py
│ │ └── config.py
│ ├── db
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── session.py
│ │ └── base_class.py
│ ├── models
│ │ ├── __init__.py
│ │ ├── recipe.py
│ │ └── user.py
│ ├── crud
│ │ ├── __init__.py
│ │ ├── crud_recipe.py
│ │ └── crud_user.py
│ ├── schemas
│ │ ├── __init__.py
│ │ ├── recipe.py
│ │ └── user.py
│ ├── initial_data.py
│ ├── backend_pre_start.py
│ └── main.py
├── alembic
│ ├── README
│ └── script.py.mako
├── .flake8
├── run.sh
├── prestart.sh
├── prestart.py
├── pyproject.toml
└── README.md
├── part-01-hello-world
├── app
│ ├── __init__.py
│ └── main.py
├── run.sh
├── pyproject.toml
└── README.md
├── part-02-path-parameters
├── app
│ └── __init__.py
├── run.sh
├── pyproject.toml
└── README.md
├── part-03-query-parameters
├── app
│ └── __init__.py
├── run.sh
├── pyproject.toml
└── README.md
├── part-04-pydantic-schemas
├── app
│ ├── __init__.py
│ ├── schemas.py
│ └── recipe_data.py
├── run.sh
├── pyproject.toml
└── README.md
├── part-06-jinja-templates
├── app
│ ├── __init__.py
│ ├── schemas.py
│ └── recipe_data.py
├── run.sh
├── pyproject.toml
└── README.md
├── part-05-basic-error-handling
├── app
│ ├── __init__.py
│ ├── schemas.py
│ └── recipe_data.py
├── run.sh
├── pyproject.toml
└── README.md
├── part-06b-basic-deploy-linode
├── app
│ ├── __init__.py
│ ├── schemas.py
│ └── recipe_data.py
├── run.sh
├── README.md
├── pyproject.toml
├── nginx
│ └── default.conf
└── Makefile
├── part-08-structure-and-versioning
├── app
│ ├── __init__.py
│ ├── api
│ │ ├── __init__.py
│ │ ├── api_v1
│ │ │ ├── __init__.py
│ │ │ ├── endpoints
│ │ │ │ └── __init__.py
│ │ │ └── api.py
│ │ └── deps.py
│ ├── core
│ │ ├── __init__.py
│ │ └── config.py
│ ├── db
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── session.py
│ │ └── base_class.py
│ ├── models
│ │ ├── __init__.py
│ │ ├── recipe.py
│ │ └── user.py
│ ├── crud
│ │ ├── __init__.py
│ │ ├── crud_recipe.py
│ │ └── crud_user.py
│ ├── schemas
│ │ ├── __init__.py
│ │ ├── user.py
│ │ └── recipe.py
│ ├── initial_data.py
│ ├── backend_pre_start.py
│ └── main.py
├── alembic
│ ├── README
│ └── script.py.mako
├── .flake8
├── run.sh
├── prestart.sh
├── prestart.py
├── pyproject.toml
└── README.md
├── part-11-dependency-injection
├── app
│ ├── __init__.py
│ ├── api
│ │ ├── __init__.py
│ │ └── api_v1
│ │ │ ├── __init__.py
│ │ │ ├── endpoints
│ │ │ └── __init__.py
│ │ │ └── api.py
│ ├── db
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── session.py
│ │ └── base_class.py
│ ├── clients
│ │ └── __init__.py
│ ├── core
│ │ ├── __init__.py
│ │ ├── security.py
│ │ └── config.py
│ ├── models
│ │ ├── __init__.py
│ │ ├── recipe.py
│ │ └── user.py
│ ├── tests
│ │ ├── api
│ │ │ ├── __init__.py
│ │ │ └── test_recipe.py
│ │ └── conftest.py
│ ├── crud
│ │ ├── __init__.py
│ │ ├── crud_recipe.py
│ │ └── crud_user.py
│ ├── schemas
│ │ ├── __init__.py
│ │ ├── user.py
│ │ └── recipe.py
│ ├── initial_data.py
│ └── backend_pre_start.py
├── alembic
│ ├── README
│ └── script.py.mako
├── .flake8
├── run.sh
├── prestart.sh
├── di_demo
│ ├── main_with_di.py
│ ├── test_main.py
│ ├── main.py
│ ├── test_main_with_di.py
│ └── patterns
│ │ └── three_types.py
├── prestart.py
├── pyproject.toml
└── README.md
├── part-12-react-frontend
├── backend
│ ├── app
│ │ ├── __init__.py
│ │ ├── api
│ │ │ ├── __init__.py
│ │ │ └── api_v1
│ │ │ │ ├── __init__.py
│ │ │ │ ├── endpoints
│ │ │ │ └── __init__.py
│ │ │ │ └── api.py
│ │ ├── core
│ │ │ ├── __init__.py
│ │ │ └── security.py
│ │ ├── db
│ │ │ ├── __init__.py
│ │ │ ├── base.py
│ │ │ ├── session.py
│ │ │ └── base_class.py
│ │ ├── models
│ │ │ ├── __init__.py
│ │ │ ├── recipe.py
│ │ │ └── user.py
│ │ ├── clients
│ │ │ └── __init__.py
│ │ ├── tests
│ │ │ ├── api
│ │ │ │ ├── __init__.py
│ │ │ │ └── test_recipe.py
│ │ │ └── conftest.py
│ │ ├── crud
│ │ │ ├── __init__.py
│ │ │ ├── crud_recipe.py
│ │ │ └── crud_user.py
│ │ ├── schemas
│ │ │ ├── __init__.py
│ │ │ ├── user.py
│ │ │ └── recipe.py
│ │ ├── initial_data.py
│ │ └── backend_pre_start.py
│ ├── alembic
│ │ ├── README
│ │ └── script.py.mako
│ ├── .flake8
│ ├── run.sh
│ ├── prestart.sh
│ ├── prestart.py
│ └── pyproject.toml
├── frontend
│ ├── .env
│ ├── public
│ │ ├── robots.txt
│ │ ├── favicon.ico
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ └── manifest.json
│ ├── src
│ │ ├── index.js
│ │ ├── config.js
│ │ ├── components
│ │ │ ├── Loader.jsx
│ │ │ ├── Footer
│ │ │ │ └── index.jsx
│ │ │ ├── IdeaTable
│ │ │ │ └── index.jsx
│ │ │ ├── FormInput
│ │ │ │ └── FormInput.jsx
│ │ │ ├── Button
│ │ │ │ └── Button.jsx
│ │ │ └── Modal
│ │ │ │ └── PopupModal.jsx
│ │ ├── pages
│ │ │ ├── my-recipes
│ │ │ │ └── NotLoggedIn.jsx
│ │ │ └── error-page
│ │ │ │ └── index.jsx
│ │ ├── App.css
│ │ └── App.js
│ ├── .gitignore
│ ├── tailwind.config.js
│ └── .eslintrc.js
└── README.md
├── part-13-docker-deployment
├── backend
│ └── app
│ │ ├── app
│ │ ├── __init__.py
│ │ ├── api
│ │ │ ├── __init__.py
│ │ │ └── api_v1
│ │ │ │ ├── __init__.py
│ │ │ │ ├── endpoints
│ │ │ │ └── __init__.py
│ │ │ │ └── api.py
│ │ ├── core
│ │ │ ├── __init__.py
│ │ │ └── security.py
│ │ ├── db
│ │ │ ├── __init__.py
│ │ │ ├── base.py
│ │ │ ├── base_class.py
│ │ │ └── session.py
│ │ ├── clients
│ │ │ └── __init__.py
│ │ ├── models
│ │ │ ├── __init__.py
│ │ │ ├── recipe.py
│ │ │ └── user.py
│ │ ├── tests
│ │ │ └── api
│ │ │ │ ├── __init__.py
│ │ │ │ └── test_recipe.py
│ │ ├── crud
│ │ │ ├── __init__.py
│ │ │ ├── crud_recipe.py
│ │ │ └── crud_user.py
│ │ ├── schemas
│ │ │ ├── __init__.py
│ │ │ ├── user.py
│ │ │ └── recipe.py
│ │ ├── initial_data.py
│ │ └── backend_pre_start.py
│ │ ├── alembic
│ │ ├── README
│ │ └── script.py.mako
│ │ ├── .flake8
│ │ ├── prestart.sh
│ │ ├── prestart.py
│ │ ├── pyproject.toml
│ │ └── run.sh
├── frontend
│ ├── .env
│ ├── public
│ │ ├── robots.txt
│ │ ├── favicon.ico
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ └── manifest.json
│ ├── src
│ │ ├── index.js
│ │ ├── config.js
│ │ ├── components
│ │ │ ├── Loader.jsx
│ │ │ ├── Footer
│ │ │ │ └── index.jsx
│ │ │ ├── IdeaTable
│ │ │ │ └── index.jsx
│ │ │ ├── FormInput
│ │ │ │ └── FormInput.jsx
│ │ │ ├── Button
│ │ │ │ └── Button.jsx
│ │ │ └── Modal
│ │ │ │ └── PopupModal.jsx
│ │ ├── pages
│ │ │ ├── my-recipes
│ │ │ │ └── NotLoggedIn.jsx
│ │ │ └── error-page
│ │ │ │ └── index.jsx
│ │ ├── App.css
│ │ └── App.js
│ ├── .gitignore
│ ├── tailwind.config.js
│ └── .eslintrc.js
├── .dockerignore
├── README.md
└── Makefile
├── assignments
└── add-put-and-delete-endpoints
│ ├── app
│ ├── __init__.py
│ ├── schemas.py
│ └── recipe_data.py
│ ├── run.sh
│ ├── pyproject.toml
│ └── README.md
├── part-14-send-email-in-background
├── backend
│ └── app
│ │ ├── app
│ │ ├── __init__.py
│ │ ├── db
│ │ │ ├── __init__.py
│ │ │ ├── base.py
│ │ │ ├── base_class.py
│ │ │ └── session.py
│ │ ├── api
│ │ │ ├── __init__.py
│ │ │ └── api_v1
│ │ │ │ ├── __init__.py
│ │ │ │ ├── endpoints
│ │ │ │ └── __init__.py
│ │ │ │ └── api.py
│ │ ├── clients
│ │ │ └── __init__.py
│ │ ├── core
│ │ │ ├── __init__.py
│ │ │ ├── security.py
│ │ │ ├── email.py
│ │ │ └── logging.py
│ │ ├── models
│ │ │ ├── __init__.py
│ │ │ ├── recipe.py
│ │ │ └── user.py
│ │ ├── tests
│ │ │ └── api
│ │ │ │ ├── __init__.py
│ │ │ │ └── test_recipe.py
│ │ ├── crud
│ │ │ ├── __init__.py
│ │ │ ├── crud_recipe.py
│ │ │ └── crud_user.py
│ │ ├── schemas
│ │ │ ├── __init__.py
│ │ │ ├── user.py
│ │ │ └── recipe.py
│ │ ├── initial_data.py
│ │ └── backend_pre_start.py
│ │ ├── alembic
│ │ ├── README
│ │ └── script.py.mako
│ │ ├── .flake8
│ │ ├── prestart.sh
│ │ ├── prestart.py
│ │ ├── pyproject.toml
│ │ └── run.sh
├── frontend
│ ├── .env
│ ├── public
│ │ ├── robots.txt
│ │ ├── favicon.ico
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ └── manifest.json
│ ├── src
│ │ ├── index.js
│ │ ├── config.js
│ │ ├── components
│ │ │ ├── Loader.jsx
│ │ │ ├── Footer
│ │ │ │ └── index.jsx
│ │ │ ├── IdeaTable
│ │ │ │ └── index.jsx
│ │ │ ├── FormInput
│ │ │ │ └── FormInput.jsx
│ │ │ ├── Button
│ │ │ │ └── Button.jsx
│ │ │ └── Modal
│ │ │ │ └── PopupModal.jsx
│ │ ├── pages
│ │ │ ├── my-recipes
│ │ │ │ └── NotLoggedIn.jsx
│ │ │ └── error-page
│ │ │ │ └── index.jsx
│ │ ├── App.css
│ │ └── App.js
│ ├── .gitignore
│ ├── tailwind.config.js
│ └── .eslintrc.js
├── .dockerignore
├── Makefile
└── README.md
└── README.md
/part-07-database/app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-09-async/app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-09-async/app/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-09-async/app/db/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-01-hello-world/app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-07-database/app/db/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-09-async/app/core/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-09-async/app/models/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/core/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/db/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-02-path-parameters/app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-03-query-parameters/app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-04-pydantic-schemas/app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-06-jinja-templates/app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-07-database/app/models/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-09-async/app/api/api_v1/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/api/api_v1/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/models/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-05-basic-error-handling/app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-06b-basic-deploy-linode/app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/db/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/core/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/db/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-09-async/app/api/api_v1/endpoints/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/api/api_v1/endpoints/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/clients/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/core/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/models/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/core/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/db/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/models/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assignments/add-put-and-delete-endpoints/app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/models/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/api/api_v1/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/tests/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/api/api_v1/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/clients/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/tests/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/core/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/db/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/api/api_v1/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/clients/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/models/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/tests/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/db/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-09-async/alembic/README:
--------------------------------------------------------------------------------
1 | Generic single-database configuration.
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/api/api_v1/endpoints/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/api/api_v1/endpoints/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/api/api_v1/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/clients/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/core/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/models/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-07-database/alembic/README:
--------------------------------------------------------------------------------
1 | Generic single-database configuration.
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/api/api_v1/endpoints/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/alembic/README:
--------------------------------------------------------------------------------
1 | Generic single-database configuration.
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/api/api_v1/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/tests/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/api/api_v1/endpoints/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/alembic/README:
--------------------------------------------------------------------------------
1 | Generic single-database configuration.
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/alembic/README:
--------------------------------------------------------------------------------
1 | Generic single-database configuration.
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/api/api_v1/endpoints/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/alembic/README:
--------------------------------------------------------------------------------
1 | Generic single-database configuration.
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/alembic/README:
--------------------------------------------------------------------------------
1 | Generic single-database configuration.
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/alembic/README:
--------------------------------------------------------------------------------
1 | Generic single-database configuration.
--------------------------------------------------------------------------------
/part-09-async/app/crud/__init__.py:
--------------------------------------------------------------------------------
1 | from .crud_recipe import recipe
2 | from .crud_user import user
3 |
--------------------------------------------------------------------------------
/part-07-database/app/crud/__init__.py:
--------------------------------------------------------------------------------
1 | from .crud_recipe import recipe
2 | from .crud_user import user
3 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/crud/__init__.py:
--------------------------------------------------------------------------------
1 | from .crud_recipe import recipe
2 | from .crud_user import user
3 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/crud/__init__.py:
--------------------------------------------------------------------------------
1 | from .crud_recipe import recipe
2 | from .crud_user import user
3 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/crud/__init__.py:
--------------------------------------------------------------------------------
1 | from .crud_recipe import recipe
2 | from .crud_user import user
3 |
--------------------------------------------------------------------------------
/part-09-async/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 88
3 | exclude = .git,__pycache__,__init__.py,.mypy_cache,.pytest_cache
--------------------------------------------------------------------------------
/part-09-async/app/schemas/__init__.py:
--------------------------------------------------------------------------------
1 | from .recipe import Recipe, RecipeCreate
2 | from .user import User, UserCreate
3 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/crud/__init__.py:
--------------------------------------------------------------------------------
1 | from .crud_recipe import recipe
2 | from .crud_user import user
3 |
--------------------------------------------------------------------------------
/part-07-database/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 88
3 | exclude = .git,__pycache__,__init__.py,.mypy_cache,.pytest_cache
--------------------------------------------------------------------------------
/part-07-database/app/schemas/__init__.py:
--------------------------------------------------------------------------------
1 | from .recipe import Recipe, RecipeCreate
2 | from .user import User, UserCreate
3 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 88
3 | exclude = .git,__pycache__,__init__.py,.mypy_cache,.pytest_cache
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/schemas/__init__.py:
--------------------------------------------------------------------------------
1 | from .recipe import Recipe, RecipeCreate
2 | from .user import User, UserCreate
3 |
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/crud/__init__.py:
--------------------------------------------------------------------------------
1 | from .crud_recipe import recipe
2 | from .crud_user import user
3 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/crud/__init__.py:
--------------------------------------------------------------------------------
1 | from .crud_recipe import recipe
2 | from .crud_user import user
3 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 88
3 | exclude = .git,__pycache__,__init__.py,.mypy_cache,.pytest_cache
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 88
3 | exclude = .git,__pycache__,__init__.py,.mypy_cache,.pytest_cache
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/schemas/__init__.py:
--------------------------------------------------------------------------------
1 | from .recipe import Recipe, RecipeCreate
2 | from .user import User, UserCreate
3 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 88
3 | exclude = .git,__pycache__,__init__.py,.mypy_cache,.pytest_cache
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 88
3 | exclude = .git,__pycache__,__init__.py,.mypy_cache,.pytest_cache
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 88
3 | exclude = .git,__pycache__,__init__.py,.mypy_cache,.pytest_cache
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/schemas/__init__.py:
--------------------------------------------------------------------------------
1 | from .recipe import Recipe, RecipeCreate, RecipeUpdateRestricted, RecipeUpdate
2 | from .user import User, UserCreate
3 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/schemas/__init__.py:
--------------------------------------------------------------------------------
1 | from .recipe import Recipe, RecipeCreate, RecipeUpdateRestricted, RecipeUpdate
2 | from .user import User, UserCreate
3 |
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChristopherGS/ultimate-fastapi-tutorial/HEAD/part-12-react-frontend/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChristopherGS/ultimate-fastapi-tutorial/HEAD/part-12-react-frontend/frontend/public/logo192.png
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChristopherGS/ultimate-fastapi-tutorial/HEAD/part-12-react-frontend/frontend/public/logo512.png
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/schemas/__init__.py:
--------------------------------------------------------------------------------
1 | from .recipe import Recipe, RecipeCreate, RecipeUpdateRestricted, RecipeUpdate
2 | from .user import User, UserCreate
3 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChristopherGS/ultimate-fastapi-tutorial/HEAD/part-13-docker-deployment/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChristopherGS/ultimate-fastapi-tutorial/HEAD/part-13-docker-deployment/frontend/public/logo192.png
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChristopherGS/ultimate-fastapi-tutorial/HEAD/part-13-docker-deployment/frontend/public/logo512.png
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/schemas/__init__.py:
--------------------------------------------------------------------------------
1 | from .recipe import Recipe, RecipeCreate, RecipeUpdateRestricted, RecipeUpdate
2 | from .user import User, UserCreate
3 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChristopherGS/ultimate-fastapi-tutorial/HEAD/part-14-send-email-in-background/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChristopherGS/ultimate-fastapi-tutorial/HEAD/part-14-send-email-in-background/frontend/public/logo192.png
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChristopherGS/ultimate-fastapi-tutorial/HEAD/part-14-send-email-in-background/frontend/public/logo512.png
--------------------------------------------------------------------------------
/part-12-react-frontend/README.md:
--------------------------------------------------------------------------------
1 | ## New: Setup & Run Frontend (requires backend to be running)
2 |
3 | 1. Install nodejs (>=v14) & npm
4 | 2. `cd frontend`
5 | 3. `npm install`
6 | 4. `npm start`
7 |
--------------------------------------------------------------------------------
/part-09-async/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | export APP_MODULE=${APP_MODULE-app.main:app}
4 | export HOST=${HOST:-0.0.0.0}
5 | export PORT=${PORT:-8001}
6 |
7 | exec uvicorn --reload --host $HOST --port $PORT "$APP_MODULE"
--------------------------------------------------------------------------------
/part-01-hello-world/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | export APP_MODULE=${APP_MODULE-app.main:app}
4 | export HOST=${HOST:-0.0.0.0}
5 | export PORT=${PORT:-8001}
6 |
7 | exec uvicorn --reload --host $HOST --port $PORT "$APP_MODULE"
--------------------------------------------------------------------------------
/part-07-database/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | export APP_MODULE=${APP_MODULE-app.main:app}
4 | export HOST=${HOST:-0.0.0.0}
5 | export PORT=${PORT:-8001}
6 |
7 | exec uvicorn --reload --host $HOST --port $PORT "$APP_MODULE"
--------------------------------------------------------------------------------
/part-10-jwt-auth/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | export APP_MODULE=${APP_MODULE-app.main:app}
4 | export HOST=${HOST:-0.0.0.0}
5 | export PORT=${PORT:-8001}
6 |
7 | exec uvicorn --reload --host $HOST --port $PORT "$APP_MODULE"
--------------------------------------------------------------------------------
/part-02-path-parameters/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | export APP_MODULE=${APP_MODULE-app.main:app}
4 | export HOST=${HOST:-0.0.0.0}
5 | export PORT=${PORT:-8001}
6 |
7 | exec uvicorn --reload --host $HOST --port $PORT "$APP_MODULE"
--------------------------------------------------------------------------------
/part-03-query-parameters/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | export APP_MODULE=${APP_MODULE-app.main:app}
4 | export HOST=${HOST:-0.0.0.0}
5 | export PORT=${PORT:-8001}
6 |
7 | exec uvicorn --reload --host $HOST --port $PORT "$APP_MODULE"
--------------------------------------------------------------------------------
/part-04-pydantic-schemas/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | export APP_MODULE=${APP_MODULE-app.main:app}
4 | export HOST=${HOST:-0.0.0.0}
5 | export PORT=${PORT:-8001}
6 |
7 | exec uvicorn --reload --host $HOST --port $PORT "$APP_MODULE"
--------------------------------------------------------------------------------
/part-06-jinja-templates/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | export APP_MODULE=${APP_MODULE-app.main:app}
4 | export HOST=${HOST:-0.0.0.0}
5 | export PORT=${PORT:-8001}
6 |
7 | exec uvicorn --reload --host $HOST --port $PORT "$APP_MODULE"
--------------------------------------------------------------------------------
/part-05-basic-error-handling/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | export APP_MODULE=${APP_MODULE-app.main:app}
4 | export HOST=${HOST:-0.0.0.0}
5 | export PORT=${PORT:-8001}
6 |
7 | exec uvicorn --reload --host $HOST --port $PORT "$APP_MODULE"
--------------------------------------------------------------------------------
/part-06b-basic-deploy-linode/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | export APP_MODULE=${APP_MODULE-app.main:app}
4 | export HOST=${HOST:-0.0.0.0}
5 | export PORT=${PORT:-8001}
6 |
7 | exec uvicorn --reload --host $HOST --port $PORT "$APP_MODULE"
--------------------------------------------------------------------------------
/part-11-dependency-injection/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | export APP_MODULE=${APP_MODULE-app.main:app}
4 | export HOST=${HOST:-0.0.0.0}
5 | export PORT=${PORT:-8001}
6 |
7 | exec uvicorn --reload --host $HOST --port $PORT "$APP_MODULE"
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | export APP_MODULE=${APP_MODULE-app.main:app}
4 | export HOST=${HOST:-0.0.0.0}
5 | export PORT=${PORT:-8001}
6 |
7 | exec uvicorn --reload --host $HOST --port $PORT "$APP_MODULE"
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | export APP_MODULE=${APP_MODULE-app.main:app}
4 | export HOST=${HOST:-0.0.0.0}
5 | export PORT=${PORT:-8001}
6 |
7 | exec uvicorn --reload --host $HOST --port $PORT "$APP_MODULE"
--------------------------------------------------------------------------------
/part-13-docker-deployment/.dockerignore:
--------------------------------------------------------------------------------
1 | # Files ignored when building docker images:
2 | # https://docs.docker.com/engine/reference/builder/#dockerignore-file
3 | .venv
4 | venv
5 | env
6 | .dockerignore
7 | Dockerfile
8 | frontend/
9 |
--------------------------------------------------------------------------------
/part-09-async/app/api/api_v1/api.py:
--------------------------------------------------------------------------------
1 | from fastapi import APIRouter
2 |
3 | from app.api.api_v1.endpoints import recipe
4 |
5 |
6 | api_router = APIRouter()
7 | api_router.include_router(recipe.router, prefix="/recipes", tags=["recipes"])
8 |
--------------------------------------------------------------------------------
/part-09-async/prestart.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | # Let the DB start
4 | python ./app/backend_pre_start.py
5 |
6 | # Run migrations
7 | alembic upgrade head
8 |
9 | # Create initial data in DB
10 | python ./app/initial_data.py
--------------------------------------------------------------------------------
/part-14-send-email-in-background/.dockerignore:
--------------------------------------------------------------------------------
1 | # Files ignored when building docker images:
2 | # https://docs.docker.com/engine/reference/builder/#dockerignore-file
3 | .venv
4 | venv
5 | env
6 | .dockerignore
7 | Dockerfile
8 | frontend/
9 |
--------------------------------------------------------------------------------
/assignments/add-put-and-delete-endpoints/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | export APP_MODULE=${APP_MODULE-app.main:app}
4 | export HOST=${HOST:-0.0.0.0}
5 | export PORT=${PORT:-8001}
6 |
7 | exec uvicorn --reload --host $HOST --port $PORT "$APP_MODULE"
--------------------------------------------------------------------------------
/part-07-database/prestart.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | # Let the DB start
4 | python ./app/backend_pre_start.py
5 |
6 | # Run migrations
7 | alembic upgrade head
8 |
9 | # Create initial data in DB
10 | python ./app/initial_data.py
--------------------------------------------------------------------------------
/part-10-jwt-auth/prestart.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | # Let the DB start
4 | python ./app/backend_pre_start.py
5 |
6 | # Run migrations
7 | alembic upgrade head
8 |
9 | # Create initial data in DB
10 | python ./app/initial_data.py
--------------------------------------------------------------------------------
/part-11-dependency-injection/prestart.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | # Let the DB start
4 | python ./app/backend_pre_start.py
5 |
6 | # Run migrations
7 | alembic upgrade head
8 |
9 | # Create initial data in DB
10 | python ./app/initial_data.py
--------------------------------------------------------------------------------
/part-07-database/app/db/base.py:
--------------------------------------------------------------------------------
1 | # Import all the models, so that Base has them before being
2 | # imported by Alembic
3 | from app.db.base_class import Base # noqa
4 | from app.models.user import User # noqa
5 | from app.models.recipe import Recipe # noqa
6 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/api/api_v1/api.py:
--------------------------------------------------------------------------------
1 | from fastapi import APIRouter
2 |
3 | from app.api.api_v1.endpoints import recipe
4 |
5 |
6 | api_router = APIRouter()
7 | api_router.include_router(recipe.router, prefix="/recipes", tags=["recipes"])
8 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/prestart.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | # Let the DB start
4 | python ./app/backend_pre_start.py
5 |
6 | # Run migrations
7 | alembic upgrade head
8 |
9 | # Create initial data in DB
10 | python ./app/initial_data.py
--------------------------------------------------------------------------------
/part-09-async/app/db/base.py:
--------------------------------------------------------------------------------
1 | # Import all the models, so that Base has them before being
2 | # imported by Alembic
3 | from app.db.base_class import Base # noqa
4 | from app.models.user import User # noqa
5 | from app.models.recipe import Recipe # noqa
6 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/db/base.py:
--------------------------------------------------------------------------------
1 | # Import all the models, so that Base has them before being
2 | # imported by Alembic
3 | from app.db.base_class import Base # noqa
4 | from app.models.user import User # noqa
5 | from app.models.recipe import Recipe # noqa
6 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/prestart.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | # Let the DB start
4 | python ./app/backend_pre_start.py
5 |
6 | # Run migrations
7 | alembic upgrade head
8 |
9 | # Create initial data in DB
10 | python ./app/initial_data.py
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/prestart.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | # Let the DB start
4 | python ./app/backend_pre_start.py
5 |
6 | # Run migrations
7 | alembic upgrade head
8 |
9 | # Create initial data in DB
10 | python ./app/initial_data.py
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/prestart.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | # Let the DB start
4 | python ./app/backend_pre_start.py
5 |
6 | # Run migrations
7 | alembic upgrade head
8 |
9 | # Create initial data in DB
10 | python ./app/initial_data.py
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/db/base.py:
--------------------------------------------------------------------------------
1 | # Import all the models, so that Base has them before being
2 | # imported by Alembic
3 | from app.db.base_class import Base # noqa
4 | from app.models.user import User # noqa
5 | from app.models.recipe import Recipe # noqa
6 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/db/base.py:
--------------------------------------------------------------------------------
1 | # Import all the models, so that Base has them before being
2 | # imported by Alembic
3 | from app.db.base_class import Base # noqa
4 | from app.models.user import User # noqa
5 | from app.models.recipe import Recipe # noqa
6 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/db/base.py:
--------------------------------------------------------------------------------
1 | # Import all the models, so that Base has them before being
2 | # imported by Alembic
3 | from app.db.base_class import Base # noqa
4 | from app.models.user import User # noqa
5 | from app.models.recipe import Recipe # noqa
6 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/db/base.py:
--------------------------------------------------------------------------------
1 | # Import all the models, so that Base has them before being
2 | # imported by Alembic
3 | from app.db.base_class import Base # noqa
4 | from app.models.user import User # noqa
5 | from app.models.recipe import Recipe # noqa
6 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/db/base.py:
--------------------------------------------------------------------------------
1 | # Import all the models, so that Base has them before being
2 | # imported by Alembic
3 | from app.db.base_class import Base # noqa
4 | from app.models.user import User # noqa
5 | from app.models.recipe import Recipe # noqa
6 |
--------------------------------------------------------------------------------
/part-07-database/app/deps.py:
--------------------------------------------------------------------------------
1 | from typing import Generator
2 |
3 | from app.db.session import SessionLocal
4 |
5 |
6 | def get_db() -> Generator:
7 | db = SessionLocal()
8 | db.current_user_id = None
9 | try:
10 | yield db
11 | finally:
12 | db.close()
13 |
--------------------------------------------------------------------------------
/part-09-async/app/api/deps.py:
--------------------------------------------------------------------------------
1 | from typing import Generator
2 |
3 | from app.db.session import SessionLocal
4 |
5 |
6 | def get_db() -> Generator:
7 | db = SessionLocal()
8 | db.current_user_id = None
9 | try:
10 | yield db
11 | finally:
12 | db.close()
13 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/api/deps.py:
--------------------------------------------------------------------------------
1 | from typing import Generator
2 |
3 | from app.db.session import SessionLocal
4 |
5 |
6 | def get_db() -> Generator:
7 | db = SessionLocal()
8 | db.current_user_id = None
9 | try:
10 | yield db
11 | finally:
12 | db.close()
13 |
--------------------------------------------------------------------------------
/part-09-async/app/crud/crud_recipe.py:
--------------------------------------------------------------------------------
1 | from app.crud.base import CRUDBase
2 | from app.models.recipe import Recipe
3 | from app.schemas.recipe import RecipeCreate, RecipeUpdate
4 |
5 |
6 | class CRUDRecipe(CRUDBase[Recipe, RecipeCreate, RecipeUpdate]):
7 | ...
8 |
9 |
10 | recipe = CRUDRecipe(Recipe)
11 |
--------------------------------------------------------------------------------
/part-07-database/app/crud/crud_recipe.py:
--------------------------------------------------------------------------------
1 | from app.crud.base import CRUDBase
2 | from app.models.recipe import Recipe
3 | from app.schemas.recipe import RecipeCreate, RecipeUpdate
4 |
5 |
6 | class CRUDRecipe(CRUDBase[Recipe, RecipeCreate, RecipeUpdate]):
7 | ...
8 |
9 |
10 | recipe = CRUDRecipe(Recipe)
11 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/api/api_v1/api.py:
--------------------------------------------------------------------------------
1 | from fastapi import APIRouter
2 |
3 | from app.api.api_v1.endpoints import recipe, auth
4 |
5 |
6 | api_router = APIRouter()
7 | api_router.include_router(recipe.router, prefix="/recipes", tags=["recipes"])
8 | api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
9 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/crud/crud_recipe.py:
--------------------------------------------------------------------------------
1 | from app.crud.base import CRUDBase
2 | from app.models.recipe import Recipe
3 | from app.schemas.recipe import RecipeCreate, RecipeUpdate
4 |
5 |
6 | class CRUDRecipe(CRUDBase[Recipe, RecipeCreate, RecipeUpdate]):
7 | ...
8 |
9 |
10 | recipe = CRUDRecipe(Recipe)
11 |
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById('root'),
11 | );
12 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById('root'),
11 | );
12 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/api/api_v1/api.py:
--------------------------------------------------------------------------------
1 | from fastapi import APIRouter
2 |
3 | from app.api.api_v1.endpoints import recipe, auth
4 |
5 |
6 | api_router = APIRouter()
7 | api_router.include_router(recipe.router, prefix="/recipes", tags=["recipes"])
8 | api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
9 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById('root'),
11 | );
12 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/crud/crud_recipe.py:
--------------------------------------------------------------------------------
1 | from app.crud.base import CRUDBase
2 | from app.models.recipe import Recipe
3 | from app.schemas.recipe import RecipeCreate, RecipeUpdate
4 |
5 |
6 | class CRUDRecipe(CRUDBase[Recipe, RecipeCreate, RecipeUpdate]):
7 | ...
8 |
9 |
10 | recipe = CRUDRecipe(Recipe)
11 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/api/api_v1/api.py:
--------------------------------------------------------------------------------
1 | from fastapi import APIRouter
2 |
3 | from app.api.api_v1.endpoints import recipe, auth
4 |
5 |
6 | api_router = APIRouter()
7 | api_router.include_router(recipe.router, prefix="/recipes", tags=["recipes"])
8 | api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ultimate-fastapi-tutorial
2 | The Ultimate FastAPI Tutorial
3 |
4 | For detailed explanations and to follow along:
5 |
6 | - Read the [blog post series](https://christophergs.com/tutorials/ultimate-fastapi-tutorial-pt-1-hello-world/)
7 | - [Pre-order the course](https://academy.christophergs.com/courses/fastapi-for-busy-engineers/)
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/api/api_v1/api.py:
--------------------------------------------------------------------------------
1 | from fastapi import APIRouter
2 |
3 | from app.api.api_v1.endpoints import recipe, auth
4 |
5 |
6 | api_router = APIRouter()
7 | api_router.include_router(recipe.router, prefix="/recipes", tags=["recipes"])
8 | api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
9 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/api/api_v1/api.py:
--------------------------------------------------------------------------------
1 | from fastapi import APIRouter
2 |
3 | from app.api.api_v1.endpoints import recipe, auth
4 |
5 |
6 | api_router = APIRouter()
7 | api_router.include_router(recipe.router, prefix="/recipes", tags=["recipes"])
8 | api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
9 |
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/src/config.js:
--------------------------------------------------------------------------------
1 | import runtimeEnv from '@mars/heroku-js-runtime-env';
2 |
3 | const env = runtimeEnv();
4 | const config = {
5 | apiBasePath: env.REACT_APP_API_BASE_PATH || 'https://fastapi-recipe-app.herokuapp.com',
6 | reactAppMode: process.env.REACT_APP_MODE || 'dev',
7 | };
8 |
9 | export default config;
10 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/src/config.js:
--------------------------------------------------------------------------------
1 | import runtimeEnv from '@mars/heroku-js-runtime-env';
2 |
3 | const env = runtimeEnv();
4 | const config = {
5 | apiBasePath: env.REACT_APP_API_BASE_PATH || 'https://fastapi-recipe-app.herokuapp.com',
6 | reactAppMode: process.env.REACT_APP_MODE || 'dev',
7 | };
8 |
9 | export default config;
10 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/di_demo/main_with_di.py:
--------------------------------------------------------------------------------
1 | from reddit import RedditClient
2 |
3 |
4 | RECIPE_SUBREDDITS = ["recipes", "easyrecipes", "TopSecretRecipes"]
5 |
6 |
7 | def fetch_ideas(reddit_client: RedditClient) -> dict:
8 | return {
9 | key: reddit_client.get_reddit_top(subreddit=key) for key in RECIPE_SUBREDDITS
10 | }
11 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/src/config.js:
--------------------------------------------------------------------------------
1 | import runtimeEnv from '@mars/heroku-js-runtime-env';
2 |
3 | const env = runtimeEnv();
4 | const config = {
5 | apiBasePath: env.REACT_APP_API_BASE_PATH || 'https://fastapi-recipe-app.herokuapp.com',
6 | reactAppMode: process.env.REACT_APP_MODE || 'dev',
7 | };
8 |
9 | export default config;
10 |
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/src/components/Loader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function Loader() {
4 | return
5 |

6 |
;
7 | }
8 |
9 | export default Loader;
10 |
--------------------------------------------------------------------------------
/part-06b-basic-deploy-linode/README.md:
--------------------------------------------------------------------------------
1 | ## Part 6 Local Setup
2 |
3 | 1. `pip install poetry` (or safer, follow the instructions: https://python-poetry.org/docs/#installation)
4 | 2. Install dependencies `cd` into the directory where the `pyproject.toml` is located then `poetry install`
5 | 3. Run the FastAPI server via poetry `poetry run ./run.sh`
6 | 4. Open http://localhost:8001/
7 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/src/components/Loader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function Loader() {
4 | return
5 |

6 |
;
7 | }
8 |
9 | export default Loader;
10 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/src/components/Loader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function Loader() {
4 | return
5 |

6 |
;
7 | }
8 |
9 | export default Loader;
10 |
--------------------------------------------------------------------------------
/assignments/add-put-and-delete-endpoints/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "app"
3 | version = "0.0.1"
4 | description = "Ultimate FastAPI Tutorial Part 4"
5 | authors = ["ChristopherGS"]
6 |
7 | [tool.poetry.dependencies]
8 | python = "^3.8"
9 | uvicorn = "~0.11.3"
10 | fastapi = "~0.68.0"
11 | python-multipart = "~0.0.5"
12 | pydantic = {extras = ["email"], version = "~1.8.1"}
13 |
--------------------------------------------------------------------------------
/part-09-async/app/db/session.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import create_engine
2 | from sqlalchemy.orm import sessionmaker
3 |
4 | from app.core.config import settings
5 |
6 | engine = create_engine(
7 | settings.SQLALCHEMY_DATABASE_URI,
8 | # required for sqlite
9 | connect_args={"check_same_thread": False},
10 | )
11 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
12 |
--------------------------------------------------------------------------------
/part-01-hello-world/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "app"
3 | version = "0.0.1"
4 | description = "Ultimate FastAPI Tutorial Part 1"
5 | authors = ["ChristopherGS"]
6 |
7 | [tool.poetry.dependencies]
8 | python = ">=3.8,<3.12"
9 | uvicorn = {extras=["standard"], version="~0.23.0"}
10 | fastapi = "^0.104.0"
11 | python-multipart = "~0.0.5"
12 | pydantic = {extras = ["email"], version = ">=2.0"}
13 |
--------------------------------------------------------------------------------
/part-02-path-parameters/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "app"
3 | version = "0.0.1"
4 | description = "Ultimate FastAPI Tutorial Part 2"
5 | authors = ["ChristopherGS"]
6 |
7 | [tool.poetry.dependencies]
8 | python = ">=3.8,<3.12"
9 | uvicorn = {extras=["standard"], version="~0.23.0"}
10 | fastapi = "^0.104.0"
11 | python-multipart = "~0.0.5"
12 | pydantic = {extras = ["email"], version = ">=2.0"}
13 |
--------------------------------------------------------------------------------
/part-07-database/prestart.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import sys
3 |
4 | from alembic.config import Config
5 | from alembic import command
6 |
7 | from app.main import ROOT
8 |
9 |
10 | alembic_cfg = Config(ROOT / "alembic.ini")
11 |
12 | subprocess.run([sys.executable, "./app/backend_pre_start.py"])
13 | command.upgrade(alembic_cfg, "head")
14 | subprocess.run([sys.executable, "./app/initial_data.py"])
15 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/db/session.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import create_engine
2 | from sqlalchemy.orm import sessionmaker
3 |
4 | from app.core.config import settings
5 |
6 | engine = create_engine(
7 | settings.SQLALCHEMY_DATABASE_URI,
8 | # required for sqlite
9 | connect_args={"check_same_thread": False},
10 | )
11 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
12 |
--------------------------------------------------------------------------------
/part-04-pydantic-schemas/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "app"
3 | version = "0.0.1"
4 | description = "Ultimate FastAPI Tutorial Part 4"
5 | authors = ["ChristopherGS"]
6 |
7 | [tool.poetry.dependencies]
8 | python = ">=3.8,<3.12"
9 | uvicorn = {extras=["standard"], version="~0.23.0"}
10 | fastapi = "^0.104.0"
11 | python-multipart = "~0.0.5"
12 | pydantic = {extras = ["email"], version = ">=2.0"}
13 |
--------------------------------------------------------------------------------
/part-05-basic-error-handling/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "app"
3 | version = "0.0.1"
4 | description = "Ultimate FastAPI Tutorial Part 5"
5 | authors = ["ChristopherGS"]
6 |
7 | [tool.poetry.dependencies]
8 | python = ">=3.8,<3.12"
9 | uvicorn = {extras=["standard"], version="~0.23.0"}
10 | fastapi = "^0.104.0"
11 | python-multipart = "~0.0.5"
12 | pydantic = {extras = ["email"], version = ">=2.0"}
13 |
--------------------------------------------------------------------------------
/part-07-database/app/db/session.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import create_engine
2 | from sqlalchemy.orm import sessionmaker
3 |
4 | SQLALCHEMY_DATABASE_URI = "sqlite:///example.db"
5 |
6 |
7 | engine = create_engine(
8 | SQLALCHEMY_DATABASE_URI,
9 | # required for sqlite
10 | connect_args={"check_same_thread": False},
11 | )
12 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
13 |
--------------------------------------------------------------------------------
/part-03-query-parameters/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "app"
3 | version = "0.0.1"
4 | description = "Ultimate FastAPI Tutorial Part 3"
5 | authors = ["ChristopherGS"]
6 |
7 | [tool.poetry.dependencies]
8 | python = ">=3.8,<3.12"
9 | uvicorn = {extras=["standard"], version="~0.23.0"}
10 | fastapi = "^0.104.0"
11 | python-multipart = "~0.0.5"
12 | pydantic = {extras = ["email"], version = ">=2.0"}
13 |
14 |
--------------------------------------------------------------------------------
/part-09-async/prestart.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import sys
3 |
4 | from alembic.config import Config
5 | from alembic import command
6 |
7 | from app.core.config import ROOT
8 |
9 |
10 | alembic_cfg = Config(ROOT.parent / "alembic.ini")
11 |
12 | subprocess.run([sys.executable, "./app/backend_pre_start.py"])
13 | command.upgrade(alembic_cfg, "head")
14 | subprocess.run([sys.executable, "./app/initial_data.py"])
15 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/core/security.py:
--------------------------------------------------------------------------------
1 | from passlib.context import CryptContext
2 |
3 |
4 | PWD_CONTEXT = CryptContext(schemes=["bcrypt"], deprecated="auto")
5 |
6 |
7 | def verify_password(plain_password: str, hashed_password: str) -> bool:
8 | return PWD_CONTEXT.verify(plain_password, hashed_password)
9 |
10 |
11 | def get_password_hash(password: str) -> str:
12 | return PWD_CONTEXT.hash(password)
13 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/db/session.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import create_engine
2 | from sqlalchemy.orm import sessionmaker
3 |
4 | from app.core.config import settings
5 |
6 | engine = create_engine(
7 | settings.SQLALCHEMY_DATABASE_URI,
8 | # required for sqlite
9 | connect_args={"check_same_thread": False},
10 | )
11 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
12 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/db/session.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import create_engine
2 | from sqlalchemy.orm import sessionmaker
3 |
4 | from app.core.config import settings
5 |
6 | engine = create_engine(
7 | settings.SQLALCHEMY_DATABASE_URI,
8 | # required for sqlite
9 | connect_args={"check_same_thread": False},
10 | )
11 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
12 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/prestart.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import sys
3 | from alembic.config import Config
4 | from alembic import command
5 | from app.core.config import ROOT
6 |
7 | alembic_cfg = Config(ROOT.parent / "alembic.ini")
8 |
9 | subprocess.run([sys.executable, "./app/backend_pre_start.py"])
10 | command.upgrade(alembic_cfg, "head")
11 | subprocess.run([sys.executable, "./app/initial_data.py"])
12 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/prestart.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import sys
3 |
4 | from alembic.config import Config
5 | from alembic import command
6 |
7 | from app.core.config import ROOT
8 |
9 |
10 | alembic_cfg = Config(ROOT.parent / "alembic.ini")
11 |
12 | subprocess.run([sys.executable, "./app/backend_pre_start.py"])
13 | command.upgrade(alembic_cfg, "head")
14 | subprocess.run([sys.executable, "./app/initial_data.py"])
15 |
--------------------------------------------------------------------------------
/part-06-jinja-templates/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "app"
3 | version = "0.0.1"
4 | description = "Ultimate FastAPI Tutorial"
5 | authors = ["ChristopherGS"]
6 |
7 | [tool.poetry.dependencies]
8 | python = ">=3.8,<3.12"
9 | uvicorn = {extras=["standard"], version="~0.23.0"}
10 | fastapi = "^0.104.0"
11 | python-multipart = "~0.0.5"
12 | pydantic = {extras = ["email"], version = ">=2.0"}
13 | Jinja2 = "~3.0.3"
14 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/db/session.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import create_engine
2 | from sqlalchemy.orm import sessionmaker
3 |
4 | from app.core.config import settings
5 |
6 |
7 | engine = create_engine(
8 | settings.SQLALCHEMY_DATABASE_URI,
9 | # required for sqlite
10 | connect_args={"check_same_thread": False},
11 | )
12 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
13 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/core/security.py:
--------------------------------------------------------------------------------
1 | from passlib.context import CryptContext
2 |
3 |
4 | PWD_CONTEXT = CryptContext(schemes=["bcrypt"], deprecated="auto")
5 |
6 |
7 | def verify_password(plain_password: str, hashed_password: str) -> bool:
8 | return PWD_CONTEXT.verify(plain_password, hashed_password)
9 |
10 |
11 | def get_password_hash(password: str) -> str:
12 | return PWD_CONTEXT.hash(password)
13 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/core/security.py:
--------------------------------------------------------------------------------
1 | from passlib.context import CryptContext
2 |
3 |
4 | PWD_CONTEXT = CryptContext(schemes=["bcrypt"], deprecated="auto")
5 |
6 |
7 | def verify_password(plain_password: str, hashed_password: str) -> bool:
8 | return PWD_CONTEXT.verify(plain_password, hashed_password)
9 |
10 |
11 | def get_password_hash(password: str) -> str:
12 | return PWD_CONTEXT.hash(password)
13 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/prestart.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import sys
3 | from alembic.config import Config
4 | from alembic import command
5 | from app.core.config import ROOT
6 |
7 | alembic_cfg = Config(ROOT.parent / "alembic.ini")
8 |
9 | subprocess.run([sys.executable, "./app/backend_pre_start.py"])
10 | command.upgrade(alembic_cfg, "head")
11 | subprocess.run([sys.executable, "./app/initial_data.py"])
12 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/prestart.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import sys
3 |
4 | from alembic.config import Config
5 | from alembic import command
6 |
7 | from app.core.config import ROOT
8 |
9 |
10 | alembic_cfg = Config(ROOT.parent / "alembic.ini")
11 |
12 | subprocess.run([sys.executable, "./app/backend_pre_start.py"])
13 | command.upgrade(alembic_cfg, "head")
14 | subprocess.run([sys.executable, "./app/initial_data.py"])
--------------------------------------------------------------------------------
/part-11-dependency-injection/prestart.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import sys
3 |
4 | from alembic.config import Config
5 | from alembic import command
6 |
7 | from app.core.config import ROOT
8 |
9 |
10 | alembic_cfg = Config(ROOT.parent / "alembic.ini")
11 |
12 | subprocess.run([sys.executable, "./app/backend_pre_start.py"])
13 | command.upgrade(alembic_cfg, "head")
14 | subprocess.run([sys.executable, "./app/initial_data.py"])
15 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/prestart.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import sys
3 | from alembic.config import Config
4 | from alembic import command
5 | from app.core.config import ROOT
6 |
7 | alembic_cfg = Config(ROOT.parent / "alembic.ini")
8 |
9 | subprocess.run([sys.executable, "./app/backend_pre_start.py"])
10 | command.upgrade(alembic_cfg, "head")
11 | subprocess.run([sys.executable, "./app/initial_data.py"])
12 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/tests/api/test_recipe.py:
--------------------------------------------------------------------------------
1 | from app.core.config import settings
2 |
3 |
4 | def test_fetch_ideas_reddit_sync(client):
5 | # When
6 | response = client.get(f"{settings.API_V1_STR}/recipes/ideas/")
7 | data = response.json()
8 |
9 | # Then
10 | assert response.status_code == 200
11 | for key in data.keys():
12 | assert key in ["recipes", "easyrecipes", "TopSecretRecipes"]
13 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/core/security.py:
--------------------------------------------------------------------------------
1 | from passlib.context import CryptContext
2 |
3 |
4 | PWD_CONTEXT = CryptContext(schemes=["bcrypt"], deprecated="auto")
5 |
6 |
7 | def verify_password(plain_password: str, hashed_password: str) -> bool:
8 | return PWD_CONTEXT.verify(plain_password, hashed_password)
9 |
10 |
11 | def get_password_hash(password: str) -> str:
12 | return PWD_CONTEXT.hash(password)
13 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/tests/api/test_recipe.py:
--------------------------------------------------------------------------------
1 | from app.core.config import settings
2 |
3 |
4 | def test_fetch_ideas_reddit_sync(client):
5 | # When
6 | response = client.get(f"{settings.API_V1_STR}/recipes/ideas/")
7 | data = response.json()
8 |
9 | # Then
10 | assert response.status_code == 200
11 | for key in data.keys():
12 | assert key in ["recipes", "easyrecipes", "TopSecretRecipes"]
13 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/core/security.py:
--------------------------------------------------------------------------------
1 | from passlib.context import CryptContext
2 |
3 |
4 | PWD_CONTEXT = CryptContext(schemes=["bcrypt"], deprecated="auto")
5 |
6 |
7 | def verify_password(plain_password: str, hashed_password: str) -> bool:
8 | return PWD_CONTEXT.verify(plain_password, hashed_password)
9 |
10 |
11 | def get_password_hash(password: str) -> str:
12 | return PWD_CONTEXT.hash(password)
13 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/tests/api/test_recipe.py:
--------------------------------------------------------------------------------
1 | from app.core.config import settings
2 |
3 |
4 | def test_fetch_ideas_reddit_sync(client):
5 | # When
6 | response = client.get(f"{settings.API_V1_STR}/recipes/ideas/")
7 | data = response.json()
8 |
9 | # Then
10 | assert response.status_code == 200
11 | for key in data.keys():
12 | assert key in ["recipes", "easyrecipes", "TopSecretRecipes"]
13 |
--------------------------------------------------------------------------------
/part-09-async/app/db/base_class.py:
--------------------------------------------------------------------------------
1 | import typing as t
2 |
3 | from sqlalchemy.ext.declarative import as_declarative, declared_attr
4 |
5 |
6 | class_registry: t.Dict = {}
7 |
8 |
9 | @as_declarative(class_registry=class_registry)
10 | class Base:
11 | id: t.Any
12 | __name__: str
13 |
14 | # Generate __tablename__ automatically
15 | @declared_attr
16 | def __tablename__(cls) -> str:
17 | return cls.__name__.lower()
18 |
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/tests/api/test_recipe.py:
--------------------------------------------------------------------------------
1 | from app.core.config import settings
2 |
3 |
4 | def test_fetch_ideas_reddit_sync(client):
5 | # When
6 | response = client.get(f"{settings.API_V1_STR}/recipes/ideas/")
7 | data = response.json()
8 |
9 | # Then
10 | assert response.status_code == 200
11 | for key in data.keys():
12 | assert key in ["recipes", "easyrecipes", "TopSecretRecipes"]
13 |
--------------------------------------------------------------------------------
/part-06-jinja-templates/app/schemas.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel, HttpUrl
2 |
3 | from typing import Sequence
4 |
5 |
6 | class Recipe(BaseModel):
7 | id: int
8 | label: str
9 | source: str
10 | url: HttpUrl
11 |
12 |
13 | class RecipeSearchResults(BaseModel):
14 | results: Sequence[Recipe]
15 |
16 |
17 | class RecipeCreate(BaseModel):
18 | label: str
19 | source: str
20 | url: HttpUrl
21 | submitter_id: int
22 |
--------------------------------------------------------------------------------
/part-06b-basic-deploy-linode/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "app"
3 | version = "0.0.1"
4 | description = "Ultimate FastAPI Tutorial"
5 | authors = ["ChristopherGS"]
6 |
7 | [tool.poetry.dependencies]
8 | python = ">=3.8,<3.12"
9 | uvicorn = {extras=["standard"], version="~0.23.0"}
10 | fastapi = "^0.104.0"
11 | python-multipart = "~0.0.5"
12 | pydantic = {extras = ["email"], version = ">=2.0"}
13 | Jinja2 = "^3.0.1"
14 | gunicorn = "^20.1.0"
15 |
--------------------------------------------------------------------------------
/part-07-database/app/db/base_class.py:
--------------------------------------------------------------------------------
1 | import typing as t
2 |
3 | from sqlalchemy.ext.declarative import as_declarative, declared_attr
4 |
5 |
6 | class_registry: t.Dict = {}
7 |
8 |
9 | @as_declarative(class_registry=class_registry)
10 | class Base:
11 | id: t.Any
12 | __name__: str
13 |
14 | # Generate __tablename__ automatically
15 | @declared_attr
16 | def __tablename__(cls) -> str:
17 | return cls.__name__.lower()
18 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/db/base_class.py:
--------------------------------------------------------------------------------
1 | import typing as t
2 |
3 | from sqlalchemy.ext.declarative import as_declarative, declared_attr
4 |
5 |
6 | class_registry: t.Dict = {}
7 |
8 |
9 | @as_declarative(class_registry=class_registry)
10 | class Base:
11 | id: t.Any
12 | __name__: str
13 |
14 | # Generate __tablename__ automatically
15 | @declared_attr
16 | def __tablename__(cls) -> str:
17 | return cls.__name__.lower()
18 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/part-04-pydantic-schemas/app/schemas.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel, HttpUrl
2 |
3 | from typing import Sequence
4 |
5 |
6 | class Recipe(BaseModel):
7 | id: int
8 | label: str
9 | source: str
10 | url: HttpUrl
11 |
12 |
13 | class RecipeSearchResults(BaseModel):
14 | results: Sequence[Recipe]
15 |
16 |
17 | class RecipeCreate(BaseModel):
18 | label: str
19 | source: str
20 | url: HttpUrl
21 | submitter_id: int
22 |
--------------------------------------------------------------------------------
/part-05-basic-error-handling/app/schemas.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel, HttpUrl
2 |
3 | from typing import Sequence
4 |
5 |
6 | class Recipe(BaseModel):
7 | id: int
8 | label: str
9 | source: str
10 | url: HttpUrl
11 |
12 |
13 | class RecipeSearchResults(BaseModel):
14 | results: Sequence[Recipe]
15 |
16 |
17 | class RecipeCreate(BaseModel):
18 | label: str
19 | source: str
20 | url: HttpUrl
21 | submitter_id: int
22 |
--------------------------------------------------------------------------------
/part-06b-basic-deploy-linode/app/schemas.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel, HttpUrl
2 |
3 | from typing import Sequence
4 |
5 |
6 | class Recipe(BaseModel):
7 | id: int
8 | label: str
9 | source: str
10 | url: HttpUrl
11 |
12 |
13 | class RecipeSearchResults(BaseModel):
14 | results: Sequence[Recipe]
15 |
16 |
17 | class RecipeCreate(BaseModel):
18 | label: str
19 | source: str
20 | url: HttpUrl
21 | submitter_id: int
22 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/db/base_class.py:
--------------------------------------------------------------------------------
1 | import typing as t
2 |
3 | from sqlalchemy.ext.declarative import as_declarative, declared_attr
4 |
5 |
6 | class_registry: t.Dict = {}
7 |
8 |
9 | @as_declarative(class_registry=class_registry)
10 | class Base:
11 | id: t.Any
12 | __name__: str
13 |
14 | # Generate __tablename__ automatically
15 | @declared_attr
16 | def __tablename__(cls) -> str:
17 | return cls.__name__.lower()
18 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/db/base_class.py:
--------------------------------------------------------------------------------
1 | import typing as t
2 |
3 | from sqlalchemy.ext.declarative import as_declarative, declared_attr
4 |
5 |
6 | class_registry: t.Dict = {}
7 |
8 |
9 | @as_declarative(class_registry=class_registry)
10 | class Base:
11 | id: t.Any
12 | __name__: str
13 |
14 | # Generate __tablename__ automatically
15 | @declared_attr
16 | def __tablename__(cls) -> str:
17 | return cls.__name__.lower()
18 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/db/base_class.py:
--------------------------------------------------------------------------------
1 | import typing as t
2 |
3 | from sqlalchemy.ext.declarative import as_declarative, declared_attr
4 |
5 |
6 | class_registry: t.Dict = {}
7 |
8 |
9 | @as_declarative(class_registry=class_registry)
10 | class Base:
11 | id: t.Any
12 | __name__: str
13 |
14 | # Generate __tablename__ automatically
15 | @declared_attr
16 | def __tablename__(cls) -> str:
17 | return cls.__name__.lower()
18 |
--------------------------------------------------------------------------------
/part-09-async/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "app"
3 | version = "0.0.1"
4 | description = "Ultimate FastAPI Tutorial"
5 | authors = ["ChristopherGS"]
6 |
7 | [tool.poetry.dependencies]
8 | python = "^3.8"
9 | uvicorn = "~0.11.3"
10 | fastapi = "~0.68.0"
11 | python-multipart = "~0.0.5"
12 | pydantic = {extras = ["email"], version = "~1.8.1"}
13 | Jinja2 = "^3.0.1"
14 | SQLAlchemy = "^1.4.3"
15 | alembic = "^1.6.5"
16 | tenacity = "^8.0.1"
17 | httpx = "0.18.1"
18 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/db/base_class.py:
--------------------------------------------------------------------------------
1 | import typing as t
2 |
3 | from sqlalchemy.ext.declarative import as_declarative, declared_attr
4 |
5 |
6 | class_registry: t.Dict = {}
7 |
8 |
9 | @as_declarative(class_registry=class_registry)
10 | class Base:
11 | id: t.Any
12 | __name__: str
13 |
14 | # Generate __tablename__ automatically
15 | @declared_attr
16 | def __tablename__(cls) -> str:
17 | return cls.__name__.lower()
18 |
--------------------------------------------------------------------------------
/part-07-database/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "app"
3 | version = "0.0.1"
4 | description = "Ultimate FastAPI Tutorial"
5 | authors = ["ChristopherGS"]
6 |
7 | [tool.poetry.dependencies]
8 | python = "^3.8"
9 | uvicorn = "~0.11.3"
10 | fastapi = "~0.68.0"
11 | python-multipart = "~0.0.5"
12 | pydantic = {extras = ["email"], version = "~1.8.1"}
13 | Jinja2 = "^3.0.1"
14 | SQLAlchemy = "^1.4.22"
15 | alembic = "^1.6.5"
16 | tenacity = "^8.0.1"
17 | greenlet = "^1.1.2"
18 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/db/base_class.py:
--------------------------------------------------------------------------------
1 | import typing as t
2 |
3 | from sqlalchemy.ext.declarative import as_declarative, declared_attr
4 |
5 |
6 | class_registry: t.Dict = {}
7 |
8 |
9 | @as_declarative(class_registry=class_registry)
10 | class Base:
11 | id: t.Any
12 | __name__: str
13 |
14 | # Generate __tablename__ automatically
15 | @declared_attr
16 | def __tablename__(cls) -> str:
17 | return cls.__name__.lower()
18 |
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/src/components/Footer/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function Footer() {
4 |
5 | return (
6 |
12 | );
13 | }
14 |
15 | export default Footer;
16 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/src/components/Footer/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function Footer() {
4 |
5 | return (
6 |
12 | );
13 | }
14 |
15 | export default Footer;
16 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "app"
3 | version = "0.0.1"
4 | description = "Ultimate FastAPI Tutorial"
5 | authors = ["ChristopherGS"]
6 |
7 | [tool.poetry.dependencies]
8 | python = "^3.8"
9 | uvicorn = "~0.11.3"
10 | fastapi = "~0.68.0"
11 | python-multipart = "~0.0.5"
12 | pydantic = {extras = ["email"], version = "~1.8.1"}
13 | Jinja2 = "^3.0.1"
14 | SQLAlchemy = "^1.4.22"
15 | alembic = "^1.6.5"
16 | tenacity = "^8.0.1"
17 | greenlet = "^1.1.2"
18 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/src/components/Footer/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function Footer() {
4 |
5 | return (
6 |
12 | );
13 | }
14 |
15 | export default Footer;
16 |
--------------------------------------------------------------------------------
/part-09-async/app/initial_data.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from app.db.init_db import init_db
4 | from app.db.session import SessionLocal
5 |
6 | logging.basicConfig(level=logging.INFO)
7 | logger = logging.getLogger(__name__)
8 |
9 |
10 | def init() -> None:
11 | db = SessionLocal()
12 | init_db(db)
13 |
14 |
15 | def main() -> None:
16 | logger.info("Creating initial data")
17 | init()
18 | logger.info("Initial data created")
19 |
20 |
21 | if __name__ == "__main__":
22 | main()
23 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/initial_data.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from app.db.init_db import init_db
4 | from app.db.session import SessionLocal
5 |
6 | logging.basicConfig(level=logging.INFO)
7 | logger = logging.getLogger(__name__)
8 |
9 |
10 | def init() -> None:
11 | db = SessionLocal()
12 | init_db(db)
13 |
14 |
15 | def main() -> None:
16 | logger.info("Creating initial data")
17 | init()
18 | logger.info("Initial data created")
19 |
20 |
21 | if __name__ == "__main__":
22 | main()
23 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/initial_data.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from app.db.init_db import init_db
4 | from app.db.session import SessionLocal
5 |
6 | logging.basicConfig(level=logging.INFO)
7 | logger = logging.getLogger(__name__)
8 |
9 |
10 | def init() -> None:
11 | db = SessionLocal()
12 | init_db(db)
13 |
14 |
15 | def main() -> None:
16 | logger.info("Creating initial data")
17 | init()
18 | logger.info("Initial data created")
19 |
20 |
21 | if __name__ == "__main__":
22 | main()
23 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/initial_data.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from app.db.init_db import init_db
4 | from app.db.session import SessionLocal
5 |
6 | logging.basicConfig(level=logging.INFO)
7 | logger = logging.getLogger(__name__)
8 |
9 |
10 | def init() -> None:
11 | db = SessionLocal()
12 | init_db(db)
13 |
14 |
15 | def main() -> None:
16 | logger.info("Creating initial data")
17 | init()
18 | logger.info("Initial data created")
19 |
20 |
21 | if __name__ == "__main__":
22 | main()
23 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/initial_data.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from app.db.init_db import init_db
4 | from app.db.session import SessionLocal
5 |
6 | logging.basicConfig(level=logging.INFO)
7 | logger = logging.getLogger(__name__)
8 |
9 |
10 | def init() -> None:
11 | db = SessionLocal()
12 | init_db(db)
13 |
14 |
15 | def main() -> None:
16 | logger.info("Creating initial data")
17 | init()
18 | logger.info("Initial data created")
19 |
20 |
21 | if __name__ == "__main__":
22 | main()
23 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/initial_data.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from app.db.init_db import init_db
4 | from app.db.session import SessionLocal
5 |
6 | logging.basicConfig(level=logging.INFO)
7 | logger = logging.getLogger(__name__)
8 |
9 |
10 | def init() -> None:
11 | db = SessionLocal()
12 | init_db(db)
13 |
14 |
15 | def main() -> None:
16 | logger.info("Creating initial data")
17 | init()
18 | logger.info("Initial data created")
19 |
20 |
21 | if __name__ == "__main__":
22 | main()
23 |
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/src/pages/my-recipes/NotLoggedIn.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import {Link} from "react-router-dom"
3 |
4 | export const NotLoggedIn = () =>
5 |
6 |
7 |
8 |
9 |
Oops!
10 | Login To Access The Page
11 |
12 |
Go TO LOGIN
13 |
14 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/src/pages/my-recipes/NotLoggedIn.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import {Link} from "react-router-dom"
3 |
4 | export const NotLoggedIn = () =>
5 |
6 |
7 |
8 |
9 |
Oops!
10 | Login To Access The Page
11 |
12 |
Go TO LOGIN
13 |
14 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/initial_data.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from app.db.init_db import init_db
4 | from app.db.session import SessionLocal
5 |
6 | logging.basicConfig(level=logging.INFO)
7 | logger = logging.getLogger(__name__)
8 |
9 |
10 | def init() -> None:
11 | db = SessionLocal()
12 | init_db(db)
13 |
14 |
15 | def main() -> None:
16 | logger.info("Creating initial data")
17 | init()
18 | logger.info("Initial data created")
19 |
20 |
21 | if __name__ == "__main__":
22 | main()
23 |
--------------------------------------------------------------------------------
/part-07-database/app/initial_data.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from app.db.base import Base
4 | from app.db.init_db import init_db
5 | from app.db.session import SessionLocal
6 |
7 | logging.basicConfig(level=logging.INFO)
8 | logger = logging.getLogger(__name__)
9 |
10 |
11 | def init() -> None:
12 | db = SessionLocal()
13 | init_db(db)
14 |
15 |
16 | def main() -> None:
17 | logger.info("Creating initial data")
18 | init()
19 | logger.info("Initial data created")
20 |
21 |
22 | if __name__ == "__main__":
23 | main()
24 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/src/pages/my-recipes/NotLoggedIn.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import {Link} from "react-router-dom"
3 |
4 | export const NotLoggedIn = () =>
5 |
6 |
7 |
8 |
9 |
Oops!
10 | Login To Access The Page
11 |
12 |
Go TO LOGIN
13 |
14 |
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: [
3 | "./src/**/*.{js,jsx}",
4 | ],
5 | theme: {
6 | extend: {
7 | keyframes: {
8 | 'fade-in-down': {
9 | '0%': {
10 | opacity: '0',
11 | },
12 | '100%': {
13 | opacity: '1',
14 | },
15 | }
16 | },
17 | animation: {
18 | 'fade-in-down': 'fade-in-down 0.5s ease-out'
19 | }
20 | },
21 | },
22 | plugins: [],
23 | }
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: [
3 | "./src/**/*.{js,jsx}",
4 | ],
5 | theme: {
6 | extend: {
7 | keyframes: {
8 | 'fade-in-down': {
9 | '0%': {
10 | opacity: '0',
11 | },
12 | '100%': {
13 | opacity: '1',
14 | },
15 | }
16 | },
17 | animation: {
18 | 'fade-in-down': 'fade-in-down 0.5s ease-out'
19 | }
20 | },
21 | },
22 | plugins: [],
23 | }
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: [
3 | "./src/**/*.{js,jsx}",
4 | ],
5 | theme: {
6 | extend: {
7 | keyframes: {
8 | 'fade-in-down': {
9 | '0%': {
10 | opacity: '0',
11 | },
12 | '100%': {
13 | opacity: '1',
14 | },
15 | }
16 | },
17 | animation: {
18 | 'fade-in-down': 'fade-in-down 0.5s ease-out'
19 | }
20 | },
21 | },
22 | plugins: [],
23 | }
--------------------------------------------------------------------------------
/part-01-hello-world/app/main.py:
--------------------------------------------------------------------------------
1 | from fastapi import FastAPI, APIRouter
2 |
3 |
4 | app = FastAPI(title="Recipe API", openapi_url="/openapi.json")
5 |
6 | api_router = APIRouter()
7 |
8 |
9 | @api_router.get("/", status_code=200)
10 | def root() -> dict:
11 | """
12 | Root GET
13 | """
14 | return {"msg": "Hello, World!"}
15 |
16 |
17 | app.include_router(api_router)
18 |
19 |
20 | if __name__ == "__main__":
21 | # Use this for debugging purposes only
22 | import uvicorn
23 |
24 | uvicorn.run(app, host="0.0.0.0", port=8001, log_level="debug")
25 |
--------------------------------------------------------------------------------
/part-09-async/app/models/recipe.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Column, Integer, String, ForeignKey
2 | from sqlalchemy.orm import relationship
3 |
4 | from app.db.base_class import Base
5 |
6 |
7 | class Recipe(Base):
8 | id = Column(Integer, primary_key=True, index=True)
9 | label = Column(String(256), nullable=False)
10 | url = Column(String(256), index=True, nullable=True)
11 | source = Column(String(256), nullable=True)
12 | submitter_id = Column(Integer, ForeignKey("user.id"), nullable=True)
13 | submitter = relationship("User", back_populates="recipes")
14 |
--------------------------------------------------------------------------------
/part-07-database/app/models/recipe.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Column, Integer, String, ForeignKey
2 | from sqlalchemy.orm import relationship
3 |
4 | from app.db.base_class import Base
5 |
6 |
7 | class Recipe(Base):
8 | id = Column(Integer, primary_key=True, index=True)
9 | label = Column(String(256), nullable=False)
10 | url = Column(String(256), index=True, nullable=True)
11 | source = Column(String(256), nullable=True)
12 | submitter_id = Column(Integer, ForeignKey("user.id"), nullable=True)
13 | submitter = relationship("User", back_populates="recipes")
14 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/models/recipe.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Column, Integer, String, ForeignKey
2 | from sqlalchemy.orm import relationship
3 |
4 | from app.db.base_class import Base
5 |
6 |
7 | class Recipe(Base):
8 | id = Column(Integer, primary_key=True, index=True)
9 | label = Column(String(256), nullable=False)
10 | url = Column(String(256), index=True, nullable=True)
11 | source = Column(String(256), nullable=True)
12 | submitter_id = Column(Integer, ForeignKey("user.id"), nullable=True)
13 | submitter = relationship("User", back_populates="recipes")
14 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/models/recipe.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Column, Integer, String, ForeignKey
2 | from sqlalchemy.orm import relationship
3 |
4 | from app.db.base_class import Base
5 |
6 |
7 | class Recipe(Base):
8 | id = Column(Integer, primary_key=True, index=True)
9 | label = Column(String(256), nullable=False)
10 | url = Column(String(256), index=True, nullable=True)
11 | source = Column(String(256), nullable=True)
12 | submitter_id = Column(Integer, ForeignKey("user.id"), nullable=True)
13 | submitter = relationship("User", back_populates="recipes")
14 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/models/recipe.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Column, Integer, String, ForeignKey
2 | from sqlalchemy.orm import relationship
3 |
4 | from app.db.base_class import Base
5 |
6 |
7 | class Recipe(Base):
8 | id = Column(Integer, primary_key=True, index=True)
9 | label = Column(String(256), nullable=False)
10 | url = Column(String(256), index=True, nullable=True)
11 | source = Column(String(256), nullable=True)
12 | submitter_id = Column(Integer, ForeignKey("user.id"), nullable=True)
13 | submitter = relationship("User", back_populates="recipes")
14 |
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/src/components/IdeaTable/index.jsx:
--------------------------------------------------------------------------------
1 | import React, {useState} from "react";
2 | import Idea from "../Idea";
3 |
4 | const IdeaTable = ({ideas}) => {
5 |
6 | return (
7 | <>
8 |
9 | {ideas.length && (
10 | ideas.map((idea, i) => (
11 |
12 | ))
13 | )}
14 | {!ideas.length && (
15 |
No Ideas found!
16 | )}
17 |
18 | >
19 | )
20 | }
21 |
22 | export default IdeaTable;
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/models/recipe.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Column, Integer, String, ForeignKey
2 | from sqlalchemy.orm import relationship
3 |
4 | from app.db.base_class import Base
5 |
6 |
7 | class Recipe(Base):
8 | id = Column(Integer, primary_key=True, index=True)
9 | label = Column(String(256), nullable=False)
10 | url = Column(String(256), index=True, nullable=True)
11 | source = Column(String(256), nullable=True)
12 | submitter_id = Column(Integer, ForeignKey("user.id"), nullable=True)
13 | submitter = relationship("User", back_populates="recipes")
14 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/src/components/IdeaTable/index.jsx:
--------------------------------------------------------------------------------
1 | import React, {useState} from "react";
2 | import Idea from "../Idea";
3 |
4 | const IdeaTable = ({ideas}) => {
5 |
6 | return (
7 | <>
8 |
9 | {ideas.length && (
10 | ideas.map((idea, i) => (
11 |
12 | ))
13 | )}
14 | {!ideas.length && (
15 |
No Ideas found!
16 | )}
17 |
18 | >
19 | )
20 | }
21 |
22 | export default IdeaTable;
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/models/recipe.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Column, Integer, String, ForeignKey
2 | from sqlalchemy.orm import relationship
3 |
4 | from app.db.base_class import Base
5 |
6 |
7 | class Recipe(Base):
8 | id = Column(Integer, primary_key=True, index=True)
9 | label = Column(String(256), nullable=False)
10 | url = Column(String(256), index=True, nullable=True)
11 | source = Column(String(256), nullable=True)
12 | submitter_id = Column(Integer, ForeignKey("user.id"), nullable=True)
13 | submitter = relationship("User", back_populates="recipes")
14 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/src/components/IdeaTable/index.jsx:
--------------------------------------------------------------------------------
1 | import React, {useState} from "react";
2 | import Idea from "../Idea";
3 |
4 | const IdeaTable = ({ideas}) => {
5 |
6 | return (
7 | <>
8 |
9 | {ideas.length && (
10 | ideas.map((idea, i) => (
11 |
12 | ))
13 | )}
14 | {!ideas.length && (
15 |
No Ideas found!
16 | )}
17 |
18 | >
19 | )
20 | }
21 |
22 | export default IdeaTable;
--------------------------------------------------------------------------------
/part-05-basic-error-handling/README.md:
--------------------------------------------------------------------------------
1 | ## Part 5 Local Setup
2 |
3 | 1. `pip install poetry` (or safer, follow the instructions: https://python-poetry.org/docs/#installation)
4 | 2. Install dependencies `cd` into the directory where the `pyproject.toml` is located then `poetry install`
5 | 3. [UNIX]: Run the FastAPI server via poetry with the bash script: `poetry run ./run.sh`
6 | 4. [WINDOWS]: Run the FastAPI server via poetry with the Python command: `poetry run python app/main.py`
7 | 5. Open http://localhost:8001/
8 |
9 | If you get stuck, checkout the [troubleshooting readme](../troubleshooting/README.md)
10 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/models/recipe.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Column, Integer, String, ForeignKey
2 | from sqlalchemy.orm import relationship
3 |
4 | from app.db.base_class import Base
5 |
6 |
7 | class Recipe(Base):
8 | id = Column(Integer, primary_key=True, index=True)
9 | label = Column(String(256), nullable=False)
10 | url = Column(String(256), index=True, nullable=True)
11 | source = Column(String(256), nullable=True)
12 | submitter_id = Column(Integer, ForeignKey("user.id"), nullable=True)
13 | submitter = relationship("User", back_populates="recipes")
14 |
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'env': {
3 | 'browser': true,
4 | 'node': true,
5 | 'es2021': true,
6 | },
7 | 'extends': [
8 | 'plugin:react/recommended',
9 | 'eslint:recommended',
10 | ],
11 | 'parserOptions': {
12 | 'ecmaFeatures': {
13 | 'jsx': true,
14 | },
15 | 'ecmaVersion': 'latest',
16 | 'sourceType': 'module',
17 | },
18 | 'plugins': [
19 | 'react',
20 | ],
21 | 'rules': {
22 | 'react/jsx-uses-react': 'error',
23 | 'react/jsx-uses-vars': 'error',
24 | "react/prop-types": "off"
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'env': {
3 | 'browser': true,
4 | 'node': true,
5 | 'es2021': true,
6 | },
7 | 'extends': [
8 | 'plugin:react/recommended',
9 | 'eslint:recommended',
10 | ],
11 | 'parserOptions': {
12 | 'ecmaFeatures': {
13 | 'jsx': true,
14 | },
15 | 'ecmaVersion': 'latest',
16 | 'sourceType': 'module',
17 | },
18 | 'plugins': [
19 | 'react',
20 | ],
21 | 'rules': {
22 | 'react/jsx-uses-react': 'error',
23 | 'react/jsx-uses-vars': 'error',
24 | "react/prop-types": "off"
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/assignments/add-put-and-delete-endpoints/app/schemas.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel, HttpUrl
2 |
3 | from typing import Sequence
4 |
5 |
6 | class Recipe(BaseModel):
7 | id: int
8 | label: str
9 | source: str
10 | url: HttpUrl
11 |
12 |
13 | class RecipeSearchResults(BaseModel):
14 | results: Sequence[Recipe]
15 |
16 |
17 | class RecipeCreate(BaseModel):
18 | label: str
19 | source: str
20 | url: HttpUrl
21 | submitter_id: int
22 |
23 |
24 | class RecipeUpdateRestricted(BaseModel):
25 | id: int
26 |
27 | # We decide to only allow label updates.
28 | label: str
29 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "app"
3 | version = "0.0.1"
4 | description = "Ultimate FastAPI Tutorial"
5 | authors = ["ChristopherGS"]
6 |
7 | [tool.poetry.dependencies]
8 | python = "^3.8"
9 | uvicorn = "~0.11.3"
10 | fastapi = "~0.68.0"
11 | python-multipart = "~0.0.5"
12 | pydantic = {extras = ["email"], version = "~1.8.1"}
13 | Jinja2 = "~3.0.1"
14 | SQLAlchemy = "~1.4.3"
15 | alembic = "~1.6.5"
16 | tenacity = "~8.0.1"
17 | httpx = "~0.18.1"
18 | passlib = {extras = ["bcrypt"], version = "^1.7.2"}
19 | python-jose = {extras = ["cryptography"], version = "^3.3.0"}
20 | cffi = "^1.15.0"
21 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'env': {
3 | 'browser': true,
4 | 'node': true,
5 | 'es2021': true,
6 | },
7 | 'extends': [
8 | 'plugin:react/recommended',
9 | 'eslint:recommended',
10 | ],
11 | 'parserOptions': {
12 | 'ecmaFeatures': {
13 | 'jsx': true,
14 | },
15 | 'ecmaVersion': 'latest',
16 | 'sourceType': 'module',
17 | },
18 | 'plugins': [
19 | 'react',
20 | ],
21 | 'rules': {
22 | 'react/jsx-uses-react': 'error',
23 | 'react/jsx-uses-vars': 'error',
24 | "react/prop-types": "off"
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/part-09-async/alembic/script.py.mako:
--------------------------------------------------------------------------------
1 | """${message}
2 |
3 | Revision ID: ${up_revision}
4 | Revises: ${down_revision | comma,n}
5 | Create Date: ${create_date}
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 | ${imports if imports else ""}
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = ${repr(up_revision)}
14 | down_revision = ${repr(down_revision)}
15 | branch_labels = ${repr(branch_labels)}
16 | depends_on = ${repr(depends_on)}
17 |
18 |
19 | def upgrade():
20 | ${upgrades if upgrades else "pass"}
21 |
22 |
23 | def downgrade():
24 | ${downgrades if downgrades else "pass"}
25 |
--------------------------------------------------------------------------------
/part-07-database/alembic/script.py.mako:
--------------------------------------------------------------------------------
1 | """${message}
2 |
3 | Revision ID: ${up_revision}
4 | Revises: ${down_revision | comma,n}
5 | Create Date: ${create_date}
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 | ${imports if imports else ""}
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = ${repr(up_revision)}
14 | down_revision = ${repr(down_revision)}
15 | branch_labels = ${repr(branch_labels)}
16 | depends_on = ${repr(depends_on)}
17 |
18 |
19 | def upgrade():
20 | ${upgrades if upgrades else "pass"}
21 |
22 |
23 | def downgrade():
24 | ${downgrades if downgrades else "pass"}
25 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/alembic/script.py.mako:
--------------------------------------------------------------------------------
1 | """${message}
2 |
3 | Revision ID: ${up_revision}
4 | Revises: ${down_revision | comma,n}
5 | Create Date: ${create_date}
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 | ${imports if imports else ""}
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = ${repr(up_revision)}
14 | down_revision = ${repr(down_revision)}
15 | branch_labels = ${repr(branch_labels)}
16 | depends_on = ${repr(depends_on)}
17 |
18 |
19 | def upgrade():
20 | ${upgrades if upgrades else "pass"}
21 |
22 |
23 | def downgrade():
24 | ${downgrades if downgrades else "pass"}
25 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/db/session.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import create_engine
2 | from sqlalchemy.orm import sessionmaker
3 |
4 | from app.core.config import settings
5 |
6 |
7 | # Heroku workaround: https://help.heroku.com/ZKNTJQSK/why-is-sqlalchemy-1-4-x-not-connecting-to-heroku-postgres
8 | connection_uri = settings.SQLALCHEMY_DATABASE_URI
9 | if connection_uri.startswith("postgres://"):
10 | connection_uri = connection_uri.replace("postgres://", "postgresql://", 1)
11 |
12 | engine = create_engine(
13 | connection_uri,
14 | )
15 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
16 |
--------------------------------------------------------------------------------
/part-01-hello-world/README.md:
--------------------------------------------------------------------------------
1 | ## Part 1 Local Setup
2 |
3 | 1. `pip install poetry` (or safer, follow the instructions: https://python-poetry.org/docs/#installation)
4 | 2. Install dependencies `cd` into the directory where the `pyproject.toml` is located then `poetry install`
5 | 3. [UNIX]: Run the FastAPI server via poetry with the bash script: `poetry run ./run.sh`
6 | 4. [WINDOWS]: Run the FastAPI server via poetry with the Python command: `poetry run python app/main.py`
7 | 5. Open http://localhost:8001/
8 |
9 | To stop the server, press CTRL+C
10 |
11 | If you get stuck, checkout the [troubleshooting readme](../troubleshooting/README.md)
--------------------------------------------------------------------------------
/part-11-dependency-injection/alembic/script.py.mako:
--------------------------------------------------------------------------------
1 | """${message}
2 |
3 | Revision ID: ${up_revision}
4 | Revises: ${down_revision | comma,n}
5 | Create Date: ${create_date}
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 | ${imports if imports else ""}
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = ${repr(up_revision)}
14 | down_revision = ${repr(down_revision)}
15 | branch_labels = ${repr(branch_labels)}
16 | depends_on = ${repr(depends_on)}
17 |
18 |
19 | def upgrade():
20 | ${upgrades if upgrades else "pass"}
21 |
22 |
23 | def downgrade():
24 | ${downgrades if downgrades else "pass"}
25 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/alembic/script.py.mako:
--------------------------------------------------------------------------------
1 | """${message}
2 |
3 | Revision ID: ${up_revision}
4 | Revises: ${down_revision | comma,n}
5 | Create Date: ${create_date}
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 | ${imports if imports else ""}
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = ${repr(up_revision)}
14 | down_revision = ${repr(down_revision)}
15 | branch_labels = ${repr(branch_labels)}
16 | depends_on = ${repr(depends_on)}
17 |
18 |
19 | def upgrade():
20 | ${upgrades if upgrades else "pass"}
21 |
22 |
23 | def downgrade():
24 | ${downgrades if downgrades else "pass"}
25 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/db/session.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import create_engine
2 | from sqlalchemy.orm import sessionmaker
3 |
4 | from app.core.config import settings
5 |
6 |
7 | # Heroku workaround: https://help.heroku.com/ZKNTJQSK/why-is-sqlalchemy-1-4-x-not-connecting-to-heroku-postgres
8 | connection_uri = settings.db.SQLALCHEMY_DATABASE_URI
9 | if connection_uri.startswith("postgres://"):
10 | connection_uri = connection_uri.replace("postgres://", "postgresql://", 1)
11 |
12 | engine = create_engine(
13 | connection_uri,
14 | )
15 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
16 |
--------------------------------------------------------------------------------
/part-02-path-parameters/README.md:
--------------------------------------------------------------------------------
1 | ## Part 2 Local Setup
2 |
3 | 1. `pip install poetry` (or safer, follow the instructions: https://python-poetry.org/docs/#installation)
4 | 2. Install dependencies `cd` into the directory where the `pyproject.toml` is located then `poetry install`
5 | 3. [UNIX]: Run the FastAPI server via poetry with the bash script: `poetry run ./run.sh`
6 | 4. [WINDOWS]: Run the FastAPI server via poetry with the Python command: `poetry run python app/main.py`
7 | 5. Open http://localhost:8001/
8 |
9 | To stop the server, press CTRL+C
10 |
11 | If you get stuck, checkout the [troubleshooting readme](../troubleshooting/README.md)
12 |
--------------------------------------------------------------------------------
/part-03-query-parameters/README.md:
--------------------------------------------------------------------------------
1 | ## Part 3 Local Setup
2 |
3 | 1. `pip install poetry` (or safer, follow the instructions: https://python-poetry.org/docs/#installation)
4 | 2. Install dependencies `cd` into the directory where the `pyproject.toml` is located then `poetry install`
5 | 3. [UNIX]: Run the FastAPI server via poetry with the bash script: `poetry run ./run.sh`
6 | 4. [WINDOWS]: Run the FastAPI server via poetry with the Python command: `poetry run python app/main.py`
7 | 5. Open http://localhost:8001/
8 |
9 | To stop the server, press CTRL+C
10 |
11 | If you get stuck, checkout the [troubleshooting readme](../troubleshooting/README.md)
12 |
--------------------------------------------------------------------------------
/part-04-pydantic-schemas/README.md:
--------------------------------------------------------------------------------
1 | ## Part 4 Local Setup
2 |
3 | 1. `pip install poetry` (or safer, follow the instructions: https://python-poetry.org/docs/#installation)
4 | 2. Install dependencies `cd` into the directory where the `pyproject.toml` is located then `poetry install`
5 | 3. [UNIX]: Run the FastAPI server via poetry with the bash script: `poetry run ./run.sh`
6 | 4. [WINDOWS]: Run the FastAPI server via poetry with the Python command: `poetry run python app/main.py`
7 | 5. Open http://localhost:8001/
8 |
9 | To stop the server, press CTRL+C
10 |
11 | If you get stuck, checkout the [troubleshooting readme](../troubleshooting/README.md)
12 |
--------------------------------------------------------------------------------
/part-06-jinja-templates/README.md:
--------------------------------------------------------------------------------
1 | ## Part 6 Local Setup
2 |
3 | 1. `pip install poetry` (or safer, follow the instructions: https://python-poetry.org/docs/#installation)
4 | 2. Install dependencies `cd` into the directory where the `pyproject.toml` is located then `poetry install`
5 | 3. [UNIX]: Run the FastAPI server via poetry with the bash script: `poetry run ./run.sh`
6 | 4. [WINDOWS]: Run the FastAPI server via poetry with the Python command: `poetry run python app/main.py`
7 | 5. Open http://localhost:8001/
8 |
9 | To stop the server, press CTRL+C
10 |
11 | If you get stuck, checkout the [troubleshooting readme](../troubleshooting/README.md)
12 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/alembic/script.py.mako:
--------------------------------------------------------------------------------
1 | """${message}
2 |
3 | Revision ID: ${up_revision}
4 | Revises: ${down_revision | comma,n}
5 | Create Date: ${create_date}
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 | ${imports if imports else ""}
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = ${repr(up_revision)}
14 | down_revision = ${repr(down_revision)}
15 | branch_labels = ${repr(branch_labels)}
16 | depends_on = ${repr(depends_on)}
17 |
18 |
19 | def upgrade():
20 | ${upgrades if upgrades else "pass"}
21 |
22 |
23 | def downgrade():
24 | ${downgrades if downgrades else "pass"}
25 |
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/part-07-database/README.md:
--------------------------------------------------------------------------------
1 | ## Part 7 Local Setup
2 |
3 | 1. `pip install poetry` (or safer, follow the instructions: https://python-poetry.org/docs/#installation)
4 | 2. Install dependencies `cd` into the directory where the `pyproject.toml` is located then `poetry install`
5 | 3. Run the DB migrations via poetry `poetry run python app/prestart.py` (only required once) (Unix users can use
6 | the bash script if preferred)
7 | 4. [UNIX]: Run the FastAPI server via poetry with the bash script: `poetry run ./run.sh`
8 | 5. [WINDOWS]: Run the FastAPI server via poetry with the Python command: `poetry run python app/main.py`
9 | 6. Open http://localhost:8001/
10 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/README.md:
--------------------------------------------------------------------------------
1 | ## Part 8 Local Setup
2 |
3 | 1. `pip install poetry` (or safer, follow the instructions: https://python-poetry.org/docs/#installation)
4 | 2. Install dependencies `cd` into the directory where the `pyproject.toml` is located then `poetry install`
5 | 3. Run the DB migrations via poetry `poetry run python app/prestart.py` (only required once) (Unix users can use
6 | the bash script if preferred)
7 | 4. [UNIX]: Run the FastAPI server via poetry with the bash script: `poetry run ./run.sh`
8 | 5. [WINDOWS]: Run the FastAPI server via poetry with the Python command: `poetry run python app/main.py`
9 | 6. Open http://localhost:8001/
--------------------------------------------------------------------------------
/part-09-async/README.md:
--------------------------------------------------------------------------------
1 | ## Part 9 Local Setup
2 |
3 | 1. `pip install poetry` (or safer, follow the instructions: https://python-poetry.org/docs/#installation)
4 | 2. Install dependencies `cd` into the directory where the `pyproject.toml` is located then `poetry install`
5 | 3. Run the DB migrations via poetry `poetry run python app/prestart.py` (only required once) (Unix users can use
6 | the bash script if preferred)
7 | 4. [UNIX]: Run the FastAPI server via poetry with the bash script: `poetry run ./run.sh`
8 | 5. [WINDOWS]: Run the FastAPI server via poetry with the Python command: `poetry run python app/main.py`
9 | 6. Open http://localhost:8001/
10 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/alembic/script.py.mako:
--------------------------------------------------------------------------------
1 | """${message}
2 |
3 | Revision ID: ${up_revision}
4 | Revises: ${down_revision | comma,n}
5 | Create Date: ${create_date}
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 | ${imports if imports else ""}
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = ${repr(up_revision)}
14 | down_revision = ${repr(down_revision)}
15 | branch_labels = ${repr(branch_labels)}
16 | depends_on = ${repr(depends_on)}
17 |
18 |
19 | def upgrade():
20 | ${upgrades if upgrades else "pass"}
21 |
22 |
23 | def downgrade():
24 | ${downgrades if downgrades else "pass"}
25 |
--------------------------------------------------------------------------------
/part-09-async/app/models/user.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Integer, String, Column, Boolean
2 | from sqlalchemy.orm import relationship
3 |
4 | from app.db.base_class import Base
5 |
6 |
7 | class User(Base):
8 | id = Column(Integer, primary_key=True, index=True)
9 | first_name = Column(String(256), nullable=True)
10 | surname = Column(String(256), nullable=True)
11 | email = Column(String, index=True, nullable=False)
12 | is_superuser = Column(Boolean, default=False)
13 | recipes = relationship(
14 | "Recipe",
15 | cascade="all,delete-orphan",
16 | back_populates="submitter",
17 | uselist=True,
18 | )
19 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/alembic/script.py.mako:
--------------------------------------------------------------------------------
1 | """${message}
2 |
3 | Revision ID: ${up_revision}
4 | Revises: ${down_revision | comma,n}
5 | Create Date: ${create_date}
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 | ${imports if imports else ""}
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = ${repr(up_revision)}
14 | down_revision = ${repr(down_revision)}
15 | branch_labels = ${repr(branch_labels)}
16 | depends_on = ${repr(depends_on)}
17 |
18 |
19 | def upgrade():
20 | ${upgrades if upgrades else "pass"}
21 |
22 |
23 | def downgrade():
24 | ${downgrades if downgrades else "pass"}
25 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/part-07-database/app/models/user.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Integer, String, Column, Boolean
2 | from sqlalchemy.orm import relationship
3 |
4 | from app.db.base_class import Base
5 |
6 |
7 | class User(Base):
8 | id = Column(Integer, primary_key=True, index=True)
9 | first_name = Column(String(256), nullable=True)
10 | surname = Column(String(256), nullable=True)
11 | email = Column(String, index=True, nullable=False)
12 | is_superuser = Column(Boolean, default=False)
13 | recipes = relationship(
14 | "Recipe",
15 | cascade="all,delete-orphan",
16 | back_populates="submitter",
17 | uselist=True,
18 | )
19 |
--------------------------------------------------------------------------------
/assignments/add-put-and-delete-endpoints/README.md:
--------------------------------------------------------------------------------
1 | ## Part 4 Local Setup
2 |
3 | 1. `pip install poetry` (or safer, follow the instructions: https://python-poetry.org/docs/#installation)
4 | 2. Install dependencies `cd` into the directory where the `pyproject.toml` is located then `poetry install`
5 | 3. [UNIX]: Run the FastAPI server via poetry with the bash script: `poetry run ./run.sh`
6 | 4. [WINDOWS]: Run the FastAPI server via poetry with the Python command: `poetry run python app/main.py`
7 | 5. Open http://localhost:8001/
8 |
9 | To stop the server, press CTRL+C
10 |
11 | If you get stuck, checkout the [troubleshooting readme](../troubleshooting/README.md)
12 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/models/user.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Integer, String, Column, Boolean
2 | from sqlalchemy.orm import relationship
3 |
4 | from app.db.base_class import Base
5 |
6 |
7 | class User(Base):
8 | id = Column(Integer, primary_key=True, index=True)
9 | first_name = Column(String(256), nullable=True)
10 | surname = Column(String(256), nullable=True)
11 | email = Column(String, index=True, nullable=False)
12 | is_superuser = Column(Boolean, default=False)
13 | recipes = relationship(
14 | "Recipe",
15 | cascade="all,delete-orphan",
16 | back_populates="submitter",
17 | uselist=True,
18 | )
19 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "app"
3 | version = "0.0.1"
4 | description = "Ultimate FastAPI Tutorial"
5 | authors = ["ChristopherGS"]
6 |
7 | [tool.poetry.dependencies]
8 | python = "^3.8"
9 | uvicorn = "~0.11.3"
10 | fastapi = "~0.68.0"
11 | python-multipart = "~0.0.5"
12 | pydantic = {extras = ["email"], version = "~1.8.1"}
13 | Jinja2 = "~3.0.1"
14 | SQLAlchemy = "~1.4.3"
15 | alembic = "~1.6.5"
16 | tenacity = "~8.0.1"
17 | httpx = "~0.18.1"
18 | passlib = {extras = ["bcrypt"], version = "^1.7.2"}
19 | python-jose = {extras = ["cryptography"], version = "^3.3.0"}
20 |
21 | [tool.poetry.dev-dependencies]
22 | pytest = "^6.2.5"
23 | requests = "^2.26.0"
24 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/crud/crud_recipe.py:
--------------------------------------------------------------------------------
1 | from typing import Union
2 |
3 | from sqlalchemy.orm import Session
4 |
5 | from app.crud.base import CRUDBase
6 | from app.models.recipe import Recipe
7 | from app.schemas.recipe import RecipeCreate, RecipeUpdateRestricted, RecipeUpdate
8 |
9 |
10 | class CRUDRecipe(CRUDBase[Recipe, RecipeCreate, RecipeUpdate]):
11 | def update(
12 | self,
13 | db: Session,
14 | *,
15 | db_obj: Recipe,
16 | obj_in: Union[RecipeUpdate, RecipeUpdateRestricted]
17 | ) -> Recipe:
18 | db_obj = super().update(db, db_obj=db_obj, obj_in=obj_in)
19 | return db_obj
20 |
21 |
22 | recipe = CRUDRecipe(Recipe)
23 |
--------------------------------------------------------------------------------
/part-04-pydantic-schemas/app/recipe_data.py:
--------------------------------------------------------------------------------
1 | RECIPES = [
2 | {
3 | "id": 1,
4 | "label": "Chicken Vesuvio",
5 | "source": "Serious Eats",
6 | "url": "http://www.seriouseats.com/recipes/2011/12/chicken-vesuvio-recipe.html",
7 | },
8 | {
9 | "id": 2,
10 | "label": "Chicken Paprikash",
11 | "source": "No Recipes",
12 | "url": "http://norecipes.com/recipe/chicken-paprikash/",
13 | },
14 | {
15 | "id": 3,
16 | "label": "Cauliflower and Tofu Curry Recipe",
17 | "source": "Serious Eats",
18 | "url": "http://www.seriouseats.com/recipes/2011/02/cauliflower-and-tofu-curry-recipe.html",
19 | },
20 | ]
21 |
--------------------------------------------------------------------------------
/part-06-jinja-templates/app/recipe_data.py:
--------------------------------------------------------------------------------
1 | RECIPES = [
2 | {
3 | "id": 1,
4 | "label": "Chicken Vesuvio",
5 | "source": "Serious Eats",
6 | "url": "http://www.seriouseats.com/recipes/2011/12/chicken-vesuvio-recipe.html",
7 | },
8 | {
9 | "id": 2,
10 | "label": "Chicken Paprikash",
11 | "source": "No Recipes",
12 | "url": "http://norecipes.com/recipe/chicken-paprikash/",
13 | },
14 | {
15 | "id": 3,
16 | "label": "Cauliflower and Tofu Curry Recipe",
17 | "source": "Serious Eats",
18 | "url": "http://www.seriouseats.com/recipes/2011/02/cauliflower-and-tofu-curry-recipe.html",
19 | },
20 | ]
21 |
--------------------------------------------------------------------------------
/part-07-database/app/recipe_data.py:
--------------------------------------------------------------------------------
1 | RECIPES = [
2 | {
3 | "id": 1,
4 | "label": "Chicken Vesuvio",
5 | "source": "Serious Eats",
6 | "url": "http://www.seriouseats.com/recipes/2011/12/chicken-vesuvio-recipe.html",
7 | },
8 | {
9 | "id": 2,
10 | "label": "Chicken Paprikash",
11 | "source": "No Recipes",
12 | "url": "http://norecipes.com/recipe/chicken-paprikash/",
13 | },
14 | {
15 | "id": 3,
16 | "label": "Cauliflower and Tofu Curry Recipe",
17 | "source": "Serious Eats",
18 | "url": "http://www.seriouseats.com/recipes/2011/02/cauliflower-and-tofu-curry-recipe.html", # noqa
19 | },
20 | ]
21 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "app"
3 | version = "0.0.1"
4 | description = "Ultimate FastAPI Tutorial Part 5"
5 | authors = ["ChristopherGS"]
6 |
7 | [tool.poetry.dependencies]
8 | python = "^3.8"
9 | uvicorn = "~0.11.3"
10 | fastapi = "~0.68.0"
11 | python-multipart = "~0.0.5"
12 | pydantic = {extras = ["email"], version = "~1.8.1"}
13 | Jinja2 = "~3.0.1"
14 | SQLAlchemy = "~1.4.3"
15 | alembic = "~1.6.5"
16 | tenacity = "~8.0.1"
17 | httpx = "~0.18.1"
18 | passlib = {extras = ["bcrypt"], version = "^1.7.2"}
19 | python-jose = {extras = ["cryptography"], version = "^3.3.0"}
20 |
21 | [tool.poetry.dev-dependencies]
22 | pytest = "^6.2.5"
23 | requests = "^2.26.0"
24 |
--------------------------------------------------------------------------------
/part-05-basic-error-handling/app/recipe_data.py:
--------------------------------------------------------------------------------
1 | RECIPES = [
2 | {
3 | "id": 1,
4 | "label": "Chicken Vesuvio",
5 | "source": "Serious Eats",
6 | "url": "http://www.seriouseats.com/recipes/2011/12/chicken-vesuvio-recipe.html",
7 | },
8 | {
9 | "id": 2,
10 | "label": "Chicken Paprikash",
11 | "source": "No Recipes",
12 | "url": "http://norecipes.com/recipe/chicken-paprikash/",
13 | },
14 | {
15 | "id": 3,
16 | "label": "Cauliflower and Tofu Curry Recipe",
17 | "source": "Serious Eats",
18 | "url": "http://www.seriouseats.com/recipes/2011/02/cauliflower-and-tofu-curry-recipe.html",
19 | },
20 | ]
21 |
--------------------------------------------------------------------------------
/part-06b-basic-deploy-linode/app/recipe_data.py:
--------------------------------------------------------------------------------
1 | RECIPES = [
2 | {
3 | "id": 1,
4 | "label": "Chicken Vesuvio",
5 | "source": "Serious Eats",
6 | "url": "http://www.seriouseats.com/recipes/2011/12/chicken-vesuvio-recipe.html",
7 | },
8 | {
9 | "id": 2,
10 | "label": "Chicken Paprikash",
11 | "source": "No Recipes",
12 | "url": "http://norecipes.com/recipe/chicken-paprikash/",
13 | },
14 | {
15 | "id": 3,
16 | "label": "Cauliflower and Tofu Curry Recipe",
17 | "source": "Serious Eats",
18 | "url": "http://www.seriouseats.com/recipes/2011/02/cauliflower-and-tofu-curry-recipe.html",
19 | },
20 | ]
21 |
--------------------------------------------------------------------------------
/assignments/add-put-and-delete-endpoints/app/recipe_data.py:
--------------------------------------------------------------------------------
1 | RECIPES = [
2 | {
3 | "id": 1,
4 | "label": "Chicken Vesuvio",
5 | "source": "Serious Eats",
6 | "url": "http://www.seriouseats.com/recipes/2011/12/chicken-vesuvio-recipe.html",
7 | },
8 | {
9 | "id": 2,
10 | "label": "Chicken Paprikash",
11 | "source": "No Recipes",
12 | "url": "http://norecipes.com/recipe/chicken-paprikash/",
13 | },
14 | {
15 | "id": 3,
16 | "label": "Cauliflower and Tofu Curry Recipe",
17 | "source": "Serious Eats",
18 | "url": "http://www.seriouseats.com/recipes/2011/02/cauliflower-and-tofu-curry-recipe.html",
19 | },
20 | ]
21 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/crud/crud_recipe.py:
--------------------------------------------------------------------------------
1 | from typing import Union
2 |
3 | from sqlalchemy.orm import Session
4 |
5 | from app.crud.base import CRUDBase
6 | from app.models.recipe import Recipe
7 | from app.models.user import User
8 | from app.schemas.recipe import RecipeCreate, RecipeUpdateRestricted, RecipeUpdate
9 |
10 |
11 | class CRUDRecipe(CRUDBase[Recipe, RecipeCreate, RecipeUpdate]):
12 | def update(
13 | self,
14 | db: Session,
15 | *,
16 | db_obj: User,
17 | obj_in: Union[RecipeUpdate, RecipeUpdateRestricted]
18 | ) -> Recipe:
19 | db_obj = super().update(db, db_obj=db_obj, obj_in=obj_in)
20 | return db_obj
21 |
22 |
23 | recipe = CRUDRecipe(Recipe)
24 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/crud/crud_recipe.py:
--------------------------------------------------------------------------------
1 | from typing import Union
2 |
3 | from sqlalchemy.orm import Session
4 |
5 | from app.crud.base import CRUDBase
6 | from app.models.recipe import Recipe
7 | from app.models.user import User
8 | from app.schemas.recipe import RecipeCreate, RecipeUpdateRestricted, RecipeUpdate
9 |
10 |
11 | class CRUDRecipe(CRUDBase[Recipe, RecipeCreate, RecipeUpdate]):
12 | def update(
13 | self,
14 | db: Session,
15 | *,
16 | db_obj: User,
17 | obj_in: Union[RecipeUpdate, RecipeUpdateRestricted]
18 | ) -> Recipe:
19 | db_obj = super().update(db, db_obj=db_obj, obj_in=obj_in)
20 | return db_obj
21 |
22 |
23 | recipe = CRUDRecipe(Recipe)
24 |
--------------------------------------------------------------------------------
/part-07-database/app/schemas/user.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 | from pydantic import BaseModel, EmailStr
4 |
5 |
6 | class UserBase(BaseModel):
7 | first_name: Optional[str]
8 | surname: Optional[str]
9 | email: Optional[EmailStr] = None
10 | is_superuser: bool = False
11 |
12 |
13 | # Properties to receive via API on creation
14 | class UserCreate(UserBase):
15 | email: EmailStr
16 |
17 |
18 | # Properties to receive via API on update
19 | class UserUpdate(UserBase):
20 | ...
21 |
22 |
23 | class UserInDBBase(UserBase):
24 | id: Optional[int] = None
25 |
26 | class Config:
27 | orm_mode = True
28 |
29 |
30 | # Additional properties to return via API
31 | class User(UserInDBBase):
32 | pass
33 |
--------------------------------------------------------------------------------
/part-09-async/app/schemas/user.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 | from pydantic import BaseModel, EmailStr
4 |
5 |
6 | class UserBase(BaseModel):
7 | first_name: Optional[str]
8 | surname: Optional[str]
9 | email: Optional[EmailStr] = None
10 | is_superuser: bool = False
11 |
12 |
13 | # Properties to receive via API on creation
14 | class UserCreate(UserBase):
15 | email: EmailStr
16 |
17 |
18 | # Properties to receive via API on update
19 | class UserUpdate(UserBase):
20 | ...
21 |
22 |
23 | class UserInDBBase(UserBase):
24 | id: Optional[int] = None
25 |
26 | class Config:
27 | orm_mode = True
28 |
29 |
30 | # Additional properties to return via API
31 | class User(UserInDBBase):
32 | pass
33 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/models/user.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Integer, String, Column, Boolean
2 | from sqlalchemy.orm import relationship
3 |
4 | from app.db.base_class import Base
5 |
6 |
7 | class User(Base):
8 | id = Column(Integer, primary_key=True, index=True)
9 | first_name = Column(String(256), nullable=True)
10 | surname = Column(String(256), nullable=True)
11 | email = Column(String, index=True, nullable=False)
12 | is_superuser = Column(Boolean, default=False)
13 | recipes = relationship(
14 | "Recipe",
15 | cascade="all,delete-orphan",
16 | back_populates="submitter",
17 | uselist=True,
18 | )
19 |
20 | # New addition
21 | hashed_password = Column(String, nullable=False)
22 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/crud/crud_recipe.py:
--------------------------------------------------------------------------------
1 | from typing import Union
2 |
3 | from sqlalchemy.orm import Session
4 |
5 | from app.crud.base import CRUDBase
6 | from app.models.recipe import Recipe
7 | from app.models.user import User
8 | from app.schemas.recipe import RecipeCreate, RecipeUpdateRestricted, RecipeUpdate
9 |
10 |
11 | class CRUDRecipe(CRUDBase[Recipe, RecipeCreate, RecipeUpdate]):
12 | def update(
13 | self,
14 | db: Session,
15 | *,
16 | db_obj: User,
17 | obj_in: Union[RecipeUpdate, RecipeUpdateRestricted]
18 | ) -> Recipe:
19 | db_obj = super().update(db, db_obj=db_obj, obj_in=obj_in)
20 | return db_obj
21 |
22 |
23 | recipe = CRUDRecipe(Recipe)
24 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/models/user.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Integer, String, Column, Boolean
2 | from sqlalchemy.orm import relationship
3 |
4 | from app.db.base_class import Base
5 |
6 |
7 | class User(Base):
8 | id = Column(Integer, primary_key=True, index=True)
9 | first_name = Column(String(256), nullable=True)
10 | surname = Column(String(256), nullable=True)
11 | email = Column(String, index=True, nullable=False)
12 | is_superuser = Column(Boolean, default=False)
13 | recipes = relationship(
14 | "Recipe",
15 | cascade="all,delete-orphan",
16 | back_populates="submitter",
17 | uselist=True,
18 | )
19 |
20 | # New addition
21 | hashed_password = Column(String, nullable=False)
22 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/models/user.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Integer, String, Column, Boolean
2 | from sqlalchemy.orm import relationship
3 |
4 | from app.db.base_class import Base
5 |
6 |
7 | class User(Base):
8 | id = Column(Integer, primary_key=True, index=True)
9 | first_name = Column(String(256), nullable=True)
10 | surname = Column(String(256), nullable=True)
11 | email = Column(String, index=True, nullable=False)
12 | is_superuser = Column(Boolean, default=False)
13 | recipes = relationship(
14 | "Recipe",
15 | cascade="all,delete-orphan",
16 | back_populates="submitter",
17 | uselist=True,
18 | )
19 |
20 | # New addition
21 | hashed_password = Column(String, nullable=False)
22 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/schemas/user.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 | from pydantic import BaseModel, EmailStr
4 |
5 |
6 | class UserBase(BaseModel):
7 | first_name: Optional[str]
8 | surname: Optional[str]
9 | email: Optional[EmailStr] = None
10 | is_superuser: bool = False
11 |
12 |
13 | # Properties to receive via API on creation
14 | class UserCreate(UserBase):
15 | email: EmailStr
16 |
17 |
18 | # Properties to receive via API on update
19 | class UserUpdate(UserBase):
20 | ...
21 |
22 |
23 | class UserInDBBase(UserBase):
24 | id: Optional[int] = None
25 |
26 | class Config:
27 | orm_mode = True
28 |
29 |
30 | # Additional properties to return via API
31 | class User(UserInDBBase):
32 | pass
33 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/models/user.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Integer, String, Column, Boolean
2 | from sqlalchemy.orm import relationship
3 |
4 | from app.db.base_class import Base
5 |
6 |
7 | class User(Base):
8 | id = Column(Integer, primary_key=True, index=True)
9 | first_name = Column(String(256), nullable=True)
10 | surname = Column(String(256), nullable=True)
11 | email = Column(String, index=True, nullable=False)
12 | is_superuser = Column(Boolean, default=False)
13 | recipes = relationship(
14 | "Recipe",
15 | cascade="all,delete-orphan",
16 | back_populates="submitter",
17 | uselist=True,
18 | )
19 |
20 | # New addition
21 | hashed_password = Column(String, nullable=False)
22 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "app"
3 | version = "0.0.1"
4 | description = "Ultimate FastAPI Tutorial Part 5"
5 | authors = ["ChristopherGS"]
6 |
7 | [tool.poetry.dependencies]
8 | python = "^3.8"
9 | uvicorn = "~0.11.3"
10 | fastapi = "~0.68.0"
11 | python-multipart = "~0.0.5"
12 | pydantic = {extras = ["email"], version = "~1.8.1"}
13 | Jinja2 = "~3.0.1"
14 | SQLAlchemy = "~1.4.3"
15 | alembic = "~1.6.5"
16 | tenacity = "~8.0.1"
17 | httpx = "~0.18.1"
18 | passlib = {extras = ["bcrypt"], version = "^1.7.2"}
19 | python-jose = {extras = ["cryptography"], version = "^3.3.0"}
20 | psycopg2-binary = "^2.9.3"
21 | gunicorn = "^20.1.0"
22 |
23 | [tool.poetry.dev-dependencies]
24 | pytest = "^6.2.5"
25 | requests = "^2.26.0"
26 |
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/src/components/FormInput/FormInput.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function FormInput({label, name, error, value, onChange, type = "text"}) {
4 | return
5 |
6 |
13 | {error && {error}}
14 |
15 | }
16 |
17 | export default FormInput;
18 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/src/components/FormInput/FormInput.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function FormInput({label, name, error, value, onChange, type = "text"}) {
4 | return
5 |
6 |
13 | {error && {error}}
14 |
15 | }
16 |
17 | export default FormInput;
18 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/models/user.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Integer, String, Column, Boolean
2 | from sqlalchemy.orm import relationship
3 |
4 | from app.db.base_class import Base
5 |
6 |
7 | class User(Base):
8 | id = Column(Integer, primary_key=True, index=True)
9 | first_name = Column(String(256), nullable=True)
10 | surname = Column(String(256), nullable=True)
11 | email = Column(String, index=True, nullable=False)
12 | is_superuser = Column(Boolean, default=False)
13 | recipes = relationship(
14 | "Recipe",
15 | cascade="all,delete-orphan",
16 | back_populates="submitter",
17 | uselist=True,
18 | )
19 |
20 | # New addition
21 | hashed_password = Column(String, nullable=False)
22 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/src/components/FormInput/FormInput.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function FormInput({label, name, error, value, onChange, type = "text"}) {
4 | return
5 |
6 |
13 | {error && {error}}
14 |
15 | }
16 |
17 | export default FormInput;
18 |
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | height: 100%;
4 | }
5 |
6 | .App-logo {
7 | height: 40vmin;
8 | pointer-events: none;
9 | }
10 |
11 | @media (prefers-reduced-motion: no-preference) {
12 | .App-logo {
13 | animation: App-logo-spin infinite 20s linear;
14 | }
15 | }
16 |
17 | .App-header {
18 | background-color: #282c34;
19 | min-height: 100vh;
20 | display: flex;
21 | flex-direction: column;
22 | align-items: center;
23 | justify-content: center;
24 | font-size: calc(10px + 2vmin);
25 | color: white;
26 | }
27 |
28 | .App-link {
29 | color: #61dafb;
30 | }
31 |
32 | @keyframes App-logo-spin {
33 | from {
34 | transform: rotate(0deg);
35 | }
36 |
37 | to {
38 | transform: rotate(360deg);
39 | }
40 | }
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | height: 100%;
4 | }
5 |
6 | .App-logo {
7 | height: 40vmin;
8 | pointer-events: none;
9 | }
10 |
11 | @media (prefers-reduced-motion: no-preference) {
12 | .App-logo {
13 | animation: App-logo-spin infinite 20s linear;
14 | }
15 | }
16 |
17 | .App-header {
18 | background-color: #282c34;
19 | min-height: 100vh;
20 | display: flex;
21 | flex-direction: column;
22 | align-items: center;
23 | justify-content: center;
24 | font-size: calc(10px + 2vmin);
25 | color: white;
26 | }
27 |
28 | .App-link {
29 | color: #61dafb;
30 | }
31 |
32 | @keyframes App-logo-spin {
33 | from {
34 | transform: rotate(0deg);
35 | }
36 |
37 | to {
38 | transform: rotate(360deg);
39 | }
40 | }
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "app"
3 | version = "0.0.1"
4 | description = "Ultimate FastAPI Tutorial"
5 | authors = ["ChristopherGS"]
6 |
7 | [tool.poetry.dependencies]
8 | python = "^3.8"
9 | uvicorn = "~0.11.3"
10 | fastapi = "~0.68.0"
11 | python-multipart = "~0.0.5"
12 | pydantic = {extras = ["dotenv", "email"], version = "^1.9.0"}
13 | Jinja2 = "~3.0.1"
14 | SQLAlchemy = "~1.4.3"
15 | alembic = "~1.6.5"
16 | tenacity = "~8.0.1"
17 | httpx = "~0.18.1"
18 | passlib = {extras = ["bcrypt"], version = "^1.7.2"}
19 | python-jose = {extras = ["cryptography"], version = "^3.3.0"}
20 | psycopg2-binary = "^2.9.3"
21 | gunicorn = "^20.1.0"
22 | loguru = "^0.6.0"
23 |
24 | [tool.poetry.dev-dependencies]
25 | pytest = "^6.2.5"
26 | requests = "^2.26.0"
27 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | height: 100%;
4 | }
5 |
6 | .App-logo {
7 | height: 40vmin;
8 | pointer-events: none;
9 | }
10 |
11 | @media (prefers-reduced-motion: no-preference) {
12 | .App-logo {
13 | animation: App-logo-spin infinite 20s linear;
14 | }
15 | }
16 |
17 | .App-header {
18 | background-color: #282c34;
19 | min-height: 100vh;
20 | display: flex;
21 | flex-direction: column;
22 | align-items: center;
23 | justify-content: center;
24 | font-size: calc(10px + 2vmin);
25 | color: white;
26 | }
27 |
28 | .App-link {
29 | color: #61dafb;
30 | }
31 |
32 | @keyframes App-logo-spin {
33 | from {
34 | transform: rotate(0deg);
35 | }
36 |
37 | to {
38 | transform: rotate(360deg);
39 | }
40 | }
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Heroku postgres addon
4 | export SQLALCHEMY_DATABASE_URI=${DATABASE_URL}
5 |
6 | # If there's a prestart.sh script in the /app directory or other path specified, run it before starting
7 | PRE_START_PATH=${PRE_START_PATH:-/app/prestart.sh}
8 | echo "Checking for script in $PRE_START_PATH"
9 | if [ -f $PRE_START_PATH ] ; then
10 | echo "Running script $PRE_START_PATH"
11 | . "$PRE_START_PATH"
12 | else
13 | echo "There is no script $PRE_START_PATH"
14 | fi
15 |
16 | export APP_MODULE=${APP_MODULE-app.main:app}
17 | export HOST=${HOST:-0.0.0.0}
18 | export PORT=${PORT:-8001}
19 | export BACKEND_CORS_ORIGINS=${BACKEND_CORS_ORIGINS}
20 |
21 |
22 | # run gunicorn
23 | exec gunicorn --bind $HOST:$PORT "$APP_MODULE" -k uvicorn.workers.UvicornWorker
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Heroku postgres addon
4 | export SQLALCHEMY_DATABASE_URI=${DATABASE_URL}
5 |
6 | # If there's a prestart.sh script in the /app directory or other path specified, run it before starting
7 | PRE_START_PATH=${PRE_START_PATH:-/app/prestart.sh}
8 | echo "Checking for script in $PRE_START_PATH"
9 | if [ -f $PRE_START_PATH ] ; then
10 | echo "Running script $PRE_START_PATH"
11 | . "$PRE_START_PATH"
12 | else
13 | echo "There is no script $PRE_START_PATH"
14 | fi
15 |
16 | export APP_MODULE=${APP_MODULE-app.main:app}
17 | export HOST=${HOST:-0.0.0.0}
18 | export PORT=${PORT:-8001}
19 | export BACKEND_CORS_ORIGINS=${BACKEND_CORS_ORIGINS}
20 |
21 |
22 | # run gunicorn
23 | exec gunicorn --bind $HOST:$PORT "$APP_MODULE" -k uvicorn.workers.UvicornWorker
--------------------------------------------------------------------------------
/part-11-dependency-injection/di_demo/test_main.py:
--------------------------------------------------------------------------------
1 | from main import fetch_ideas
2 |
3 | from unittest.mock import patch
4 |
5 | REDDIT_RESPONSE_DATA = {
6 | "recipes": [
7 | "2825: Air Fryer Juicy Steak Bites (https://i.redd.it/6zdbb0zvrag81.jpg)"
8 | ],
9 | "easyrecipes": [
10 | "189: Meals for when you have absolutely no energy to cook? (https://www.reddit.com/r/easyrecipes/comments/smbbr5/meals_for_when_you_have_absolutely_no_energy_to/)"
11 | ],
12 | }
13 |
14 |
15 | def test_fetch_ideas():
16 | # Given
17 | with patch("main.get_reddit_top") as mocked_get_reddit:
18 | mocked_get_reddit.return_value = REDDIT_RESPONSE_DATA
19 |
20 | # When
21 | subject = fetch_ideas()
22 |
23 | # Then
24 | assert subject["recipes"]
25 | assert subject["easyrecipes"]
26 |
--------------------------------------------------------------------------------
/part-06b-basic-deploy-linode/nginx/default.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | client_max_body_size 4G;
4 |
5 | server_name example.com;
6 |
7 | location / {
8 | proxy_set_header Host $http_host;
9 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
10 | proxy_set_header X-Forwarded-Proto $scheme;
11 | proxy_set_header Upgrade $http_upgrade;
12 | proxy_set_header Connection $connection_upgrade;
13 | proxy_redirect off;
14 | proxy_buffering off;
15 | proxy_pass http://uvicorn;
16 | }
17 |
18 | location /static {
19 | # path for static files
20 | root /path/to/app/static;
21 | }
22 | }
23 |
24 | map $http_upgrade $connection_upgrade {
25 | default upgrade;
26 | '' close;
27 | }
28 |
29 | upstream uvicorn {
30 | server unix:/tmp/uvicorn.sock;
31 | }
--------------------------------------------------------------------------------
/part-11-dependency-injection/di_demo/main.py:
--------------------------------------------------------------------------------
1 | import httpx
2 |
3 | RECIPE_SUBREDDITS = ["recipes", "easyrecipes", "TopSecretRecipes"]
4 |
5 |
6 | def get_reddit_top(subreddit: str) -> None:
7 | response = httpx.get(
8 | f"https://www.reddit.com/r/{subreddit}/top.json?sort=top&t=day&limit=5",
9 | headers={"User-agent": "recipe bot 0.1"},
10 | )
11 | subreddit_recipes = response.json()
12 | subreddit_data = []
13 | for entry in subreddit_recipes["data"]["children"]:
14 | score = entry["data"]["score"]
15 | title = entry["data"]["title"]
16 | link = entry["data"]["url"]
17 | subreddit_data.append(f"{str(score)}: {title} ({link})")
18 | return subreddit_data
19 |
20 |
21 | def fetch_ideas() -> dict:
22 | return {key: get_reddit_top(subreddit=key) for key in RECIPE_SUBREDDITS}
23 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/README.md:
--------------------------------------------------------------------------------
1 | ## Part 10 Local Setup
2 |
3 | 1. `pip install poetry` (or safer, follow the instructions: https://python-poetry.org/docs/#installation)
4 | 2. Install dependencies `cd` into the directory where the `pyproject.toml` is located then `poetry install`
5 | 3. If continuing from a previous part of the series, delete your current project database because we
6 | have made breaking DB migration changes. `rm example.db`. If you're starting here, you can ignore this step.
7 | 4. Run the DB migrations via poetry `poetry run python app/prestart.py` (only required once) (Unix users can use
8 | the bash script if preferred)
9 | 5. [UNIX]: Run the FastAPI server via poetry with the bash script: `poetry run ./run.sh`
10 | 6. [WINDOWS]: Run the FastAPI server via poetry with the Python command: `poetry run python app/main.py`
11 | 7. Open http://localhost:8001/
--------------------------------------------------------------------------------
/part-11-dependency-injection/README.md:
--------------------------------------------------------------------------------
1 | ## Part 11 Local Setup
2 |
3 | 1. `pip install poetry` (or safer, follow the instructions: https://python-poetry.org/docs/#installation)
4 | 2. Install dependencies `cd` into the directory where the `pyproject.toml` is located then `poetry install`
5 | 3. If continuing from a previous part of the series, delete your current project database because we
6 | have made breaking DB migration changes. `rm example.db`. If you're starting here, you can ignore this step.
7 | 4. Run the DB migrations via poetry `poetry run python app/prestart.py` (only required once) (Unix users can use
8 | the bash script if preferred)
9 | 5. [UNIX]: Run the FastAPI server via poetry with the bash script: `poetry run ./run.sh`
10 | 6. [WINDOWS]: Run the FastAPI server via poetry with the Python command: `poetry run python app/main.py`
11 | 7. Open http://localhost:8001/
12 |
--------------------------------------------------------------------------------
/part-07-database/app/schemas/recipe.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel, HttpUrl
2 |
3 | from typing import Sequence
4 |
5 |
6 | class RecipeBase(BaseModel):
7 | label: str
8 | source: str
9 | url: HttpUrl
10 |
11 |
12 | class RecipeCreate(RecipeBase):
13 | label: str
14 | source: str
15 | url: HttpUrl
16 | submitter_id: int
17 |
18 |
19 | class RecipeUpdate(RecipeBase):
20 | label: str
21 |
22 |
23 | # Properties shared by models stored in DB
24 | class RecipeInDBBase(RecipeBase):
25 | id: int
26 | submitter_id: int
27 |
28 | class Config:
29 | orm_mode = True
30 |
31 |
32 | # Properties to return to client
33 | class Recipe(RecipeInDBBase):
34 | pass
35 |
36 |
37 | # Properties properties stored in DB
38 | class RecipeInDB(RecipeInDBBase):
39 | pass
40 |
41 |
42 | class RecipeSearchResults(BaseModel):
43 | results: Sequence[Recipe]
44 |
--------------------------------------------------------------------------------
/part-09-async/app/schemas/recipe.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel, HttpUrl
2 |
3 | from typing import Sequence
4 |
5 |
6 | class RecipeBase(BaseModel):
7 | label: str
8 | source: str
9 | url: HttpUrl
10 |
11 |
12 | class RecipeCreate(RecipeBase):
13 | label: str
14 | source: str
15 | url: HttpUrl
16 | submitter_id: int
17 |
18 |
19 | class RecipeUpdate(RecipeBase):
20 | label: str
21 |
22 |
23 | # Properties shared by models stored in DB
24 | class RecipeInDBBase(RecipeBase):
25 | id: int
26 | submitter_id: int
27 |
28 | class Config:
29 | orm_mode = True
30 |
31 |
32 | # Properties to return to client
33 | class Recipe(RecipeInDBBase):
34 | pass
35 |
36 |
37 | # Properties properties stored in DB
38 | class RecipeInDB(RecipeInDBBase):
39 | pass
40 |
41 |
42 | class RecipeSearchResults(BaseModel):
43 | results: Sequence[Recipe]
44 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/schemas/recipe.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel, HttpUrl
2 |
3 | from typing import Sequence
4 |
5 |
6 | class RecipeBase(BaseModel):
7 | label: str
8 | source: str
9 | url: HttpUrl
10 |
11 |
12 | class RecipeCreate(RecipeBase):
13 | label: str
14 | source: str
15 | url: HttpUrl
16 | submitter_id: int
17 |
18 |
19 | class RecipeUpdate(RecipeBase):
20 | label: str
21 |
22 |
23 | # Properties shared by models stored in DB
24 | class RecipeInDBBase(RecipeBase):
25 | id: int
26 | submitter_id: int
27 |
28 | class Config:
29 | orm_mode = True
30 |
31 |
32 | # Properties to return to client
33 | class Recipe(RecipeInDBBase):
34 | pass
35 |
36 |
37 | # Properties properties stored in DB
38 | class RecipeInDB(RecipeInDBBase):
39 | pass
40 |
41 |
42 | class RecipeSearchResults(BaseModel):
43 | results: Sequence[Recipe]
44 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/schemas/user.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 | from pydantic import BaseModel, EmailStr
4 |
5 |
6 | class UserBase(BaseModel):
7 | first_name: Optional[str]
8 | surname: Optional[str]
9 | email: Optional[EmailStr] = None
10 | is_superuser: bool = False
11 |
12 |
13 | # Properties to receive via API on creation
14 | class UserCreate(UserBase):
15 | email: EmailStr
16 | password: str
17 |
18 |
19 | # Properties to receive via API on update
20 | class UserUpdate(UserBase):
21 | ...
22 |
23 |
24 | class UserInDBBase(UserBase):
25 | id: Optional[int] = None
26 |
27 | class Config:
28 | orm_mode = True
29 |
30 |
31 | # Additional properties stored in DB but not returned by API
32 | class UserInDB(UserInDBBase):
33 | hashed_password: str
34 |
35 |
36 | # Additional properties to return via API
37 | class User(UserInDBBase):
38 | ...
39 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/schemas/recipe.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel, HttpUrl
2 |
3 | from typing import Sequence
4 |
5 |
6 | class RecipeBase(BaseModel):
7 | label: str
8 | source: str
9 | url: HttpUrl
10 |
11 |
12 | class RecipeCreate(RecipeBase):
13 | label: str
14 | source: str
15 | url: HttpUrl
16 | submitter_id: int
17 |
18 |
19 | class RecipeUpdate(RecipeBase):
20 | label: str
21 |
22 |
23 | # Properties shared by models stored in DB
24 | class RecipeInDBBase(RecipeBase):
25 | id: int
26 | submitter_id: int
27 |
28 | class Config:
29 | orm_mode = True
30 |
31 |
32 | # Properties to return to client
33 | class Recipe(RecipeInDBBase):
34 | pass
35 |
36 |
37 | # Properties properties stored in DB
38 | class RecipeInDB(RecipeInDBBase):
39 | pass
40 |
41 |
42 | class RecipeSearchResults(BaseModel):
43 | results: Sequence[Recipe]
44 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/schemas/user.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 | from pydantic import BaseModel, EmailStr
4 |
5 |
6 | class UserBase(BaseModel):
7 | first_name: Optional[str]
8 | surname: Optional[str]
9 | email: Optional[EmailStr] = None
10 | is_superuser: bool = False
11 |
12 |
13 | # Properties to receive via API on creation
14 | class UserCreate(UserBase):
15 | email: EmailStr
16 | password: str
17 |
18 |
19 | # Properties to receive via API on update
20 | class UserUpdate(UserBase):
21 | ...
22 |
23 |
24 | class UserInDBBase(UserBase):
25 | id: Optional[int] = None
26 |
27 | class Config:
28 | orm_mode = True
29 |
30 |
31 | # Additional properties stored in DB but not returned by API
32 | class UserInDB(UserInDBBase):
33 | hashed_password: str
34 |
35 |
36 | # Additional properties to return via API
37 | class User(UserInDBBase):
38 | ...
39 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/schemas/user.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 | from pydantic import BaseModel, EmailStr
4 |
5 |
6 | class UserBase(BaseModel):
7 | first_name: Optional[str]
8 | surname: Optional[str]
9 | email: Optional[EmailStr] = None
10 | is_superuser: bool = False
11 |
12 |
13 | # Properties to receive via API on creation
14 | class UserCreate(UserBase):
15 | email: EmailStr
16 | password: str
17 |
18 |
19 | # Properties to receive via API on update
20 | class UserUpdate(UserBase):
21 | ...
22 |
23 |
24 | class UserInDBBase(UserBase):
25 | id: Optional[int] = None
26 |
27 | class Config:
28 | orm_mode = True
29 |
30 |
31 | # Additional properties stored in DB but not returned by API
32 | class UserInDB(UserInDBBase):
33 | hashed_password: str
34 |
35 |
36 | # Additional properties to return via API
37 | class User(UserInDBBase):
38 | ...
39 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/core/email.py:
--------------------------------------------------------------------------------
1 | from loguru import logger
2 |
3 | from app.clients.email import EmailClient, MailGunSendData
4 | from app.schemas.user import UserCreate
5 | from app.models.user import User
6 | from app.core.config import settings
7 |
8 |
9 | def send_registration_confirmed_email(client: EmailClient, user: User) -> None:
10 | send_data = MailGunSendData(
11 | from_=f"admin@{settings.email.MAILGUN_DOMAIN_NAME}",
12 | to=[str(user.email)],
13 | subject="Registration Confirmed",
14 | text=f"Hi {user.first_name}, \nYour registration is confirmed!",
15 | )
16 | response = client.send_email(data=send_data)
17 |
18 | if response.status_code > 299:
19 | logger.error(
20 | f"Failed to send confirmation email for user: {user.email}, error: {response.error}"
21 | )
22 | else:
23 | logger.info(response.text)
24 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/schemas/user.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 | from pydantic import BaseModel, EmailStr
4 |
5 |
6 | class UserBase(BaseModel):
7 | first_name: Optional[str]
8 | surname: Optional[str]
9 | email: Optional[EmailStr] = None
10 | is_superuser: bool = False
11 |
12 |
13 | # Properties to receive via API on creation
14 | class UserCreate(UserBase):
15 | email: EmailStr
16 | password: str
17 |
18 |
19 | # Properties to receive via API on update
20 | class UserUpdate(UserBase):
21 | ...
22 |
23 |
24 | class UserInDBBase(UserBase):
25 | id: Optional[int] = None
26 |
27 | class Config:
28 | orm_mode = True
29 |
30 |
31 | # Additional properties stored in DB but not returned by API
32 | class UserInDB(UserInDBBase):
33 | hashed_password: str
34 |
35 |
36 | # Additional properties to return via API
37 | class User(UserInDBBase):
38 | ...
39 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/di_demo/test_main_with_di.py:
--------------------------------------------------------------------------------
1 | from main_with_di import fetch_ideas
2 |
3 | import pytest
4 |
5 |
6 | class FakeClient:
7 | def get_reddit_top(self, subreddit: str) -> dict:
8 | return {
9 | "recipes": [
10 | "2825: Air Fryer Juicy Steak Bites (https://i.redd.it/6zdbb0zvrag81.jpg)"
11 | ],
12 | "easyrecipes": [
13 | "189: Meals for when you have absolutely no energy to cook? (https://www.reddit.com/r/easyrecipes/comments/smbbr5/meals_for_when_you_have_absolutely_no_energy_to/)"
14 | ],
15 | }
16 |
17 |
18 | @pytest.fixture
19 | def fake_reddit_client():
20 | return FakeClient()
21 |
22 |
23 | def test_fetch_ideas(fake_reddit_client):
24 | # When
25 | subject = fetch_ideas(reddit_client=fake_reddit_client)
26 |
27 | # Then
28 | assert subject["recipes"]
29 | assert subject["easyrecipes"]
30 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/schemas/user.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 | from pydantic import BaseModel, EmailStr
4 |
5 |
6 | class UserBase(BaseModel):
7 | first_name: Optional[str]
8 | surname: Optional[str]
9 | email: Optional[EmailStr] = None
10 | is_superuser: bool = False
11 |
12 |
13 | # Properties to receive via API on creation
14 | class UserCreate(UserBase):
15 | email: EmailStr
16 | password: str
17 |
18 |
19 | # Properties to receive via API on update
20 | class UserUpdate(UserBase):
21 | ...
22 |
23 |
24 | class UserInDBBase(UserBase):
25 | id: Optional[int] = None
26 |
27 | class Config:
28 | orm_mode = True
29 |
30 |
31 | # Additional properties stored in DB but not returned by API
32 | class UserInDB(UserInDBBase):
33 | hashed_password: str
34 |
35 |
36 | # Additional properties to return via API
37 | class User(UserInDBBase):
38 | ...
39 |
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './App.css';
3 | import {BrowserRouter, Routes, Route} from 'react-router-dom';
4 | import Login from './pages/login';
5 | import SignUp from './pages/sign-up';
6 | import Home from './pages/home';
7 | import RecipeDashboard from './pages/my-recipes';
8 | import ErrorPage from './pages/error-page';
9 |
10 | const App = () => {
11 | return (
12 |
13 |
14 |
15 | } />
16 | } />
17 | } />
18 | } />
19 | } />
20 |
21 |
22 |
23 | );
24 | };
25 |
26 | export default App;
27 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/core/logging.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from types import FrameType
3 | from typing import cast
4 |
5 | from loguru import logger
6 |
7 |
8 | class InterceptHandler(logging.Handler):
9 | def emit(self, record: logging.LogRecord) -> None: # pragma: no cover
10 | # Get corresponding Loguru level if it exists
11 | try:
12 | level = logger.level(record.levelname).name
13 | except ValueError:
14 | level = str(record.levelno)
15 |
16 | # Find caller from where originated the logged message
17 | frame, depth = logging.currentframe(), 2
18 | while frame.f_code.co_filename == logging.__file__: # noqa: WPS609
19 | frame = cast(FrameType, frame.f_back)
20 | depth += 1
21 |
22 | logger.opt(depth=depth, exception=record.exc_info).log(
23 | level,
24 | record.getMessage(),
25 | )
26 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './App.css';
3 | import {BrowserRouter, Routes, Route} from 'react-router-dom';
4 | import Login from './pages/login';
5 | import SignUp from './pages/sign-up';
6 | import Home from './pages/home';
7 | import RecipeDashboard from './pages/my-recipes';
8 | import ErrorPage from './pages/error-page';
9 |
10 | const App = () => {
11 | return (
12 |
13 |
14 |
15 | } />
16 | } />
17 | } />
18 | } />
19 | } />
20 |
21 |
22 |
23 | );
24 | };
25 |
26 | export default App;
27 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './App.css';
3 | import {BrowserRouter, Routes, Route} from 'react-router-dom';
4 | import Login from './pages/login';
5 | import SignUp from './pages/sign-up';
6 | import Home from './pages/home';
7 | import RecipeDashboard from './pages/my-recipes';
8 | import ErrorPage from './pages/error-page';
9 |
10 | const App = () => {
11 | return (
12 |
13 |
14 |
15 | } />
16 | } />
17 | } />
18 | } />
19 | } />
20 |
21 |
22 |
23 | );
24 | };
25 |
26 | export default App;
27 |
--------------------------------------------------------------------------------
/part-09-async/app/crud/crud_user.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Dict, Optional, Union
2 |
3 | from sqlalchemy.orm import Session
4 |
5 | from app.crud.base import CRUDBase
6 | from app.models.user import User
7 | from app.schemas.user import UserCreate, UserUpdate
8 |
9 |
10 | class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
11 | def get_by_email(self, db: Session, *, email: str) -> Optional[User]:
12 | return db.query(User).filter(User.email == email).first()
13 |
14 | def update(
15 | self, db: Session, *, db_obj: User, obj_in: Union[UserUpdate, Dict[str, Any]]
16 | ) -> User:
17 | if isinstance(obj_in, dict):
18 | update_data = obj_in
19 | else:
20 | update_data = obj_in.dict(exclude_unset=True)
21 |
22 | return super().update(db, db_obj=db_obj, obj_in=update_data)
23 |
24 | def is_superuser(self, user: User) -> bool:
25 | return user.is_superuser
26 |
27 |
28 | user = CRUDUser(User)
29 |
--------------------------------------------------------------------------------
/part-07-database/app/crud/crud_user.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Dict, Optional, Union
2 |
3 | from sqlalchemy.orm import Session
4 |
5 | from app.crud.base import CRUDBase
6 | from app.models.user import User
7 | from app.schemas.user import UserCreate, UserUpdate
8 |
9 |
10 | class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
11 | def get_by_email(self, db: Session, *, email: str) -> Optional[User]:
12 | return db.query(User).filter(User.email == email).first()
13 |
14 | def update(
15 | self, db: Session, *, db_obj: User, obj_in: Union[UserUpdate, Dict[str, Any]]
16 | ) -> User:
17 | if isinstance(obj_in, dict):
18 | update_data = obj_in
19 | else:
20 | update_data = obj_in.dict(exclude_unset=True)
21 |
22 | return super().update(db, db_obj=db_obj, obj_in=update_data)
23 |
24 | def is_superuser(self, user: User) -> bool:
25 | return user.is_superuser
26 |
27 |
28 | user = CRUDUser(User)
29 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/crud/crud_user.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Dict, Optional, Union
2 |
3 | from sqlalchemy.orm import Session
4 |
5 | from app.crud.base import CRUDBase
6 | from app.models.user import User
7 | from app.schemas.user import UserCreate, UserUpdate
8 |
9 |
10 | class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
11 | def get_by_email(self, db: Session, *, email: str) -> Optional[User]:
12 | return db.query(User).filter(User.email == email).first()
13 |
14 | def update(
15 | self, db: Session, *, db_obj: User, obj_in: Union[UserUpdate, Dict[str, Any]]
16 | ) -> User:
17 | if isinstance(obj_in, dict):
18 | update_data = obj_in
19 | else:
20 | update_data = obj_in.dict(exclude_unset=True)
21 |
22 | return super().update(db, db_obj=db_obj, obj_in=update_data)
23 |
24 | def is_superuser(self, user: User) -> bool:
25 | return user.is_superuser
26 |
27 |
28 | user = CRUDUser(User)
29 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/di_demo/patterns/three_types.py:
--------------------------------------------------------------------------------
1 | from ..reddit import RedditClient
2 |
3 | RECIPE_SUBREDDITS = ["recipes", "easyrecipes", "TopSecretRecipes"]
4 |
5 | # 1. Constructor Injection
6 | class Ideas:
7 | def __init__(self, reddit_client: RedditClient):
8 | self.reddit_client = reddit_client
9 |
10 | def fetch_ideas(self) -> dict:
11 | return {
12 | key: self.reddit_client.get_reddit_top(subreddit=key)
13 | for key in RECIPE_SUBREDDITS
14 | }
15 |
16 |
17 | # 2. Setter Injection
18 | class Ideas:
19 | _client = None
20 |
21 | def fetch_ideas(self) -> dict:
22 | return {
23 | key: self.client.get_reddit_top(subreddit=key) for key in RECIPE_SUBREDDITS
24 | }
25 |
26 | @property
27 | def client(self):
28 | return self._client
29 |
30 | @client.setter
31 | def client(self, value: RedditClient):
32 | self._client = value
33 |
34 |
35 | # Interface Injection
36 |
--------------------------------------------------------------------------------
/part-07-database/app/backend_pre_start.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed
4 |
5 | from app.db.session import SessionLocal
6 |
7 | logging.basicConfig(level=logging.INFO)
8 | logger = logging.getLogger(__name__)
9 |
10 | max_tries = 60 * 5 # 5 minutes
11 | wait_seconds = 1
12 |
13 |
14 | @retry(
15 | stop=stop_after_attempt(max_tries),
16 | wait=wait_fixed(wait_seconds),
17 | before=before_log(logger, logging.INFO),
18 | after=after_log(logger, logging.WARN),
19 | )
20 | def init() -> None:
21 | try:
22 | db = SessionLocal()
23 | # Try to create session to check if DB is awake
24 | db.execute("SELECT 1")
25 | except Exception as e:
26 | logger.error(e)
27 | raise e
28 |
29 |
30 | def main() -> None:
31 | logger.info("Initializing service")
32 | init()
33 | logger.info("Service finished initializing")
34 |
35 |
36 | if __name__ == "__main__":
37 | main()
38 |
--------------------------------------------------------------------------------
/part-09-async/app/backend_pre_start.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed
4 |
5 | from app.db.session import SessionLocal
6 |
7 | logging.basicConfig(level=logging.INFO)
8 | logger = logging.getLogger(__name__)
9 |
10 | max_tries = 60 * 5 # 5 minutes
11 | wait_seconds = 1
12 |
13 |
14 | @retry(
15 | stop=stop_after_attempt(max_tries),
16 | wait=wait_fixed(wait_seconds),
17 | before=before_log(logger, logging.INFO),
18 | after=after_log(logger, logging.WARN),
19 | )
20 | def init() -> None:
21 | try:
22 | db = SessionLocal()
23 | # Try to create session to check if DB is awake
24 | db.execute("SELECT 1")
25 | except Exception as e:
26 | logger.error(e)
27 | raise e
28 |
29 |
30 | def main() -> None:
31 | logger.info("Initializing service")
32 | init()
33 | logger.info("Service finished initializing")
34 |
35 |
36 | if __name__ == "__main__":
37 | main()
38 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/backend_pre_start.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed
4 |
5 | from app.db.session import SessionLocal
6 |
7 | logging.basicConfig(level=logging.INFO)
8 | logger = logging.getLogger(__name__)
9 |
10 | max_tries = 60 * 5 # 5 minutes
11 | wait_seconds = 1
12 |
13 |
14 | @retry(
15 | stop=stop_after_attempt(max_tries),
16 | wait=wait_fixed(wait_seconds),
17 | before=before_log(logger, logging.INFO),
18 | after=after_log(logger, logging.WARN),
19 | )
20 | def init() -> None:
21 | try:
22 | db = SessionLocal()
23 | # Try to create session to check if DB is awake
24 | db.execute("SELECT 1")
25 | except Exception as e:
26 | logger.error(e)
27 | raise e
28 |
29 |
30 | def main() -> None:
31 | logger.info("Initializing service")
32 | init()
33 | logger.info("Service finished initializing")
34 |
35 |
36 | if __name__ == "__main__":
37 | main()
38 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/schemas/recipe.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel, HttpUrl
2 |
3 | from typing import Sequence
4 |
5 |
6 | class RecipeBase(BaseModel):
7 | label: str
8 | source: str
9 | url: HttpUrl
10 |
11 |
12 | class RecipeCreate(RecipeBase):
13 | label: str
14 | source: str
15 | url: HttpUrl
16 | submitter_id: int
17 |
18 |
19 | class RecipeUpdate(RecipeBase):
20 | id: int
21 |
22 |
23 | class RecipeUpdateRestricted(BaseModel):
24 | id: int
25 | label: str
26 |
27 |
28 | # Properties shared by models stored in DB
29 | class RecipeInDBBase(RecipeBase):
30 | id: int
31 | submitter_id: int
32 |
33 | class Config:
34 | orm_mode = True
35 |
36 |
37 | # Properties to return to client
38 | class Recipe(RecipeInDBBase):
39 | pass
40 |
41 |
42 | # Properties properties stored in DB
43 | class RecipeInDB(RecipeInDBBase):
44 | pass
45 |
46 |
47 | class RecipeSearchResults(BaseModel):
48 | results: Sequence[Recipe]
49 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/schemas/recipe.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel, HttpUrl
2 |
3 | from typing import Sequence
4 |
5 |
6 | class RecipeBase(BaseModel):
7 | label: str
8 | source: str
9 | url: HttpUrl
10 |
11 |
12 | class RecipeCreate(RecipeBase):
13 | label: str
14 | source: str
15 | url: HttpUrl
16 | submitter_id: int
17 |
18 |
19 | class RecipeUpdate(RecipeBase):
20 | id: int
21 |
22 |
23 | class RecipeUpdateRestricted(BaseModel):
24 | id: int
25 | label: str
26 |
27 |
28 | # Properties shared by models stored in DB
29 | class RecipeInDBBase(RecipeBase):
30 | id: int
31 | submitter_id: int
32 |
33 | class Config:
34 | orm_mode = True
35 |
36 |
37 | # Properties to return to client
38 | class Recipe(RecipeInDBBase):
39 | pass
40 |
41 |
42 | # Properties properties stored in DB
43 | class RecipeInDB(RecipeInDBBase):
44 | pass
45 |
46 |
47 | class RecipeSearchResults(BaseModel):
48 | results: Sequence[Recipe]
49 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/schemas/recipe.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel, HttpUrl
2 |
3 | from typing import Sequence
4 |
5 |
6 | class RecipeBase(BaseModel):
7 | label: str
8 | source: str
9 | url: HttpUrl
10 |
11 |
12 | class RecipeCreate(RecipeBase):
13 | label: str
14 | source: str
15 | url: HttpUrl
16 | submitter_id: int
17 |
18 |
19 | class RecipeUpdate(RecipeBase):
20 | id: int
21 |
22 |
23 | class RecipeUpdateRestricted(BaseModel):
24 | id: int
25 | label: str
26 |
27 |
28 | # Properties shared by models stored in DB
29 | class RecipeInDBBase(RecipeBase):
30 | id: int
31 | submitter_id: int
32 |
33 | class Config:
34 | orm_mode = True
35 |
36 |
37 | # Properties to return to client
38 | class Recipe(RecipeInDBBase):
39 | pass
40 |
41 |
42 | # Properties properties stored in DB
43 | class RecipeInDB(RecipeInDBBase):
44 | pass
45 |
46 |
47 | class RecipeSearchResults(BaseModel):
48 | results: Sequence[Recipe]
49 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/backend_pre_start.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed
4 |
5 | from app.db.session import SessionLocal
6 |
7 | logging.basicConfig(level=logging.INFO)
8 | logger = logging.getLogger(__name__)
9 |
10 | max_tries = 60 * 5 # 5 minutes
11 | wait_seconds = 1
12 |
13 |
14 | @retry(
15 | stop=stop_after_attempt(max_tries),
16 | wait=wait_fixed(wait_seconds),
17 | before=before_log(logger, logging.INFO),
18 | after=after_log(logger, logging.WARN),
19 | )
20 | def init() -> None:
21 | try:
22 | db = SessionLocal()
23 | # Try to create session to check if DB is awake
24 | db.execute("SELECT 1")
25 | except Exception as e:
26 | logger.error(e)
27 | raise e
28 |
29 |
30 | def main() -> None:
31 | logger.info("Initializing service")
32 | init()
33 | logger.info("Service finished initializing")
34 |
35 |
36 | if __name__ == "__main__":
37 | main()
38 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/backend_pre_start.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed
4 |
5 | from app.db.session import SessionLocal
6 |
7 | logging.basicConfig(level=logging.INFO)
8 | logger = logging.getLogger(__name__)
9 |
10 | max_tries = 60 * 5 # 5 minutes
11 | wait_seconds = 1
12 |
13 |
14 | @retry(
15 | stop=stop_after_attempt(max_tries),
16 | wait=wait_fixed(wait_seconds),
17 | before=before_log(logger, logging.INFO),
18 | after=after_log(logger, logging.WARN),
19 | )
20 | def init() -> None:
21 | try:
22 | db = SessionLocal()
23 | # Try to create session to check if DB is awake
24 | db.execute("SELECT 1")
25 | except Exception as e:
26 | logger.error(e)
27 | raise e
28 |
29 |
30 | def main() -> None:
31 | logger.info("Initializing service")
32 | init()
33 | logger.info("Service finished initializing")
34 |
35 |
36 | if __name__ == "__main__":
37 | main()
38 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/backend_pre_start.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed
4 |
5 | from app.db.session import SessionLocal
6 |
7 | logging.basicConfig(level=logging.INFO)
8 | logger = logging.getLogger(__name__)
9 |
10 | max_tries = 60 * 5 # 5 minutes
11 | wait_seconds = 1
12 |
13 |
14 | @retry(
15 | stop=stop_after_attempt(max_tries),
16 | wait=wait_fixed(wait_seconds),
17 | before=before_log(logger, logging.INFO),
18 | after=after_log(logger, logging.WARN),
19 | )
20 | def init() -> None:
21 | try:
22 | db = SessionLocal()
23 | # Try to create session to check if DB is awake
24 | db.execute("SELECT 1")
25 | except Exception as e:
26 | logger.error(e)
27 | raise e
28 |
29 |
30 | def main() -> None:
31 | logger.info("Initializing service")
32 | init()
33 | logger.info("Service finished initializing")
34 |
35 |
36 | if __name__ == "__main__":
37 | main()
38 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/schemas/recipe.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel, HttpUrl
2 |
3 | from typing import Sequence
4 |
5 |
6 | class RecipeBase(BaseModel):
7 | label: str
8 | source: str
9 | url: HttpUrl
10 |
11 |
12 | class RecipeCreate(RecipeBase):
13 | label: str
14 | source: str
15 | url: HttpUrl
16 | submitter_id: int
17 |
18 |
19 | class RecipeUpdate(RecipeBase):
20 | id: int
21 |
22 |
23 | class RecipeUpdateRestricted(BaseModel):
24 | id: int
25 | label: str
26 |
27 |
28 | # Properties shared by models stored in DB
29 | class RecipeInDBBase(RecipeBase):
30 | id: int
31 | submitter_id: int
32 |
33 | class Config:
34 | orm_mode = True
35 |
36 |
37 | # Properties to return to client
38 | class Recipe(RecipeInDBBase):
39 | pass
40 |
41 |
42 | # Properties properties stored in DB
43 | class RecipeInDB(RecipeInDBBase):
44 | pass
45 |
46 |
47 | class RecipeSearchResults(BaseModel):
48 | results: Sequence[Recipe]
49 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/backend_pre_start.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed
4 |
5 | from app.db.session import SessionLocal
6 |
7 | logging.basicConfig(level=logging.INFO)
8 | logger = logging.getLogger(__name__)
9 |
10 | max_tries = 60 * 5 # 5 minutes
11 | wait_seconds = 1
12 |
13 |
14 | @retry(
15 | stop=stop_after_attempt(max_tries),
16 | wait=wait_fixed(wait_seconds),
17 | before=before_log(logger, logging.INFO),
18 | after=after_log(logger, logging.WARN),
19 | )
20 | def init() -> None:
21 | try:
22 | db = SessionLocal()
23 | # Try to create session to check if DB is awake
24 | db.execute("SELECT 1")
25 | except Exception as e:
26 | logger.error(e)
27 | raise e
28 |
29 |
30 | def main() -> None:
31 | logger.info("Initializing service")
32 | init()
33 | logger.info("Service finished initializing")
34 |
35 |
36 | if __name__ == "__main__":
37 | main()
38 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/README.md:
--------------------------------------------------------------------------------
1 | ## New: Run with Docker (see previous sections for running without Docker)
2 |
3 | Make sure you have Docker and [Docker Compose](https://docs.docker.com/compose/install/) installed.
4 |
5 | 1. Ensure you are in the `part-13-docker-deployment` directory
6 | 2. Run `docker-compose -f docker-compose.local.yml up -d` (this will download the postgres
7 | image and build the image for the recipe app - takes about 5 mins)
8 | 3. Visit `http://localhost:8001/docs`
9 |
10 |
11 | Windows Users: Having problems getting the volume to work properly? Review the following resources:
12 |
13 | - [Docker on Windows - Mounting Host Directories](https://rominirani.com/docker-on-windows-mounting-host-directories-d96f3f056a2c?gi=324e01b3473a)
14 | - [Configuring Docker for Windows Shared Drives](https://docs.microsoft.com/en-gb/archive/blogs/stevelasker/configuring-docker-for-windows-volumes)
15 | - You also may need to add `COMPOSE_CONVERT_WINDOWS_PATHS=1` to the environment portion of your Docker Compose file.
16 |
17 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/backend_pre_start.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed
4 |
5 | from app.db.session import SessionLocal
6 |
7 | logging.basicConfig(level=logging.INFO)
8 | logger = logging.getLogger(__name__)
9 |
10 | max_tries = 60 * 5 # 5 minutes
11 | wait_seconds = 1
12 |
13 |
14 | @retry(
15 | stop=stop_after_attempt(max_tries),
16 | wait=wait_fixed(wait_seconds),
17 | before=before_log(logger, logging.INFO),
18 | after=after_log(logger, logging.WARN),
19 | )
20 | def init() -> None:
21 | try:
22 | db = SessionLocal()
23 | # Try to create session to check if DB is awake
24 | db.execute("SELECT 1")
25 | except Exception as e:
26 | logger.error(e)
27 | raise e
28 |
29 |
30 | def main() -> None:
31 | logger.info("Initializing service")
32 | init()
33 | logger.info("Service finished initializing")
34 |
35 |
36 | if __name__ == "__main__":
37 | main()
38 |
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/src/components/Button/Button.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function Button({loading, title, error}) {
4 | return
5 |
17 |
18 | }
19 |
20 | export default Button;
21 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/src/components/Button/Button.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function Button({loading, title, error}) {
4 | return
5 |
17 |
18 | }
19 |
20 | export default Button;
21 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/src/components/Button/Button.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function Button({loading, title, error}) {
4 | return
5 |
17 |
18 | }
19 |
20 | export default Button;
21 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/Makefile:
--------------------------------------------------------------------------------
1 | # Update the Heroku names to match yours
2 | # Add the HEROKU_API_KEY environment variable to your system
3 | # (and to your CI tool env vars if running in CI)
4 | HEROKU_APP_NAME=fastapi-recipe-app
5 | HEROKU_FRONTEND_APP_NAME=fastapi-recipe-app-frontend
6 | COMMIT_ID=$(shell git rev-parse HEAD)
7 |
8 |
9 | heroku-login:
10 | HEROKU_API_KEY=${HEROKU_API_KEY} heroku auth:token
11 |
12 | heroku-container-login:
13 | HEROKU_API_KEY=${HEROKU_API_KEY} heroku container:login
14 |
15 | build-app-heroku: heroku-container-login
16 | docker build -t registry.heroku.com/$(HEROKU_APP_NAME)/web ./backend
17 |
18 | push-app-heroku: heroku-container-login
19 | docker push registry.heroku.com/$(HEROKU_APP_NAME)/web
20 |
21 | release-heroku: heroku-container-login
22 | heroku container:release web --app $(HEROKU_APP_NAME)
23 |
24 | deploy-frontend-heroku: heroku-login
25 | cd .. && git subtree push --prefix part-13-docker-deployment/frontend https://heroku:${HEROKU_API_KEY}@git.heroku.com/$(HEROKU_FRONTEND_APP_NAME).git main
26 |
27 |
28 | .PHONY: heroku-login heroku-container-login build-app-heroku push-app-heroku deploy-frontend-heroku
29 |
--------------------------------------------------------------------------------
/part-06b-basic-deploy-linode/Makefile:
--------------------------------------------------------------------------------
1 | linode_install:
2 | # sudo apt update
3 | # sudo apt install make
4 | sudo apt -y upgrade
5 | sudo apt -y install python3-pip
6 | pip install poetry
7 | poetry install
8 | sudo apt -y install nginx
9 | sudo cp nginx/default.conf /etc/nginx/sites-available/fastapi_app
10 | # Disable the NGINX’s default configuration file by removing its symlink
11 | sudo unlink /etc/nginx/sites-enabled/default
12 | sudo ln -s /etc/nginx/sites-available/fastapi_app /etc/nginx/sites-enabled/
13 |
14 | linode_run:
15 | # Reload the NGINX configuration file:
16 | sudo nginx -s reload
17 | # restart the Nginx service
18 | sudo systemctl restart nginx.service
19 | # The recommended configuration for proxying from Nginx is to use a UNIX domain
20 | # socket between Nginx and whatever the process manager that is being used to run
21 | # Uvicorn. Note that when doing this you will need run Uvicorn with --forwarded-allow-ips='*'
22 | # to ensure that the domain socket is trusted as a source from which to proxy headers.
23 | poetry run gunicorn --bind=unix:///tmp/uvicorn.sock -w 2 --forwarded-allow-ips='*' -k uvicorn.workers.UvicornWorker app.main:app
24 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/Makefile:
--------------------------------------------------------------------------------
1 | # Update the Heroku names to match yours
2 | # Add the HEROKU_API_KEY environment variable to your system
3 | # (and to your CI tool env vars if running in CI)
4 | HEROKU_APP_NAME=fastapi-recipe-app
5 | HEROKU_FRONTEND_APP_NAME=fastapi-recipe-app-frontend
6 | COMMIT_ID=$(shell git rev-parse HEAD)
7 |
8 |
9 | heroku-login:
10 | HEROKU_API_KEY=${HEROKU_API_KEY} heroku auth:token
11 |
12 | heroku-container-login:
13 | HEROKU_API_KEY=${HEROKU_API_KEY} heroku container:login
14 |
15 | build-app-heroku: heroku-container-login
16 | docker build -t registry.heroku.com/$(HEROKU_APP_NAME)/web ./backend
17 |
18 | push-app-heroku: heroku-container-login
19 | docker push registry.heroku.com/$(HEROKU_APP_NAME)/web
20 |
21 | release-heroku: heroku-container-login
22 | heroku container:release web --app $(HEROKU_APP_NAME)
23 |
24 | deploy-frontend-heroku: heroku-login
25 | cd .. && git subtree push --prefix part-14-send-email-in-background/frontend https://heroku:${HEROKU_API_KEY}@git.heroku.com/$(HEROKU_FRONTEND_APP_NAME).git main
26 |
27 |
28 | .PHONY: heroku-login heroku-container-login build-app-heroku push-app-heroku deploy-frontend-heroku
29 |
--------------------------------------------------------------------------------
/part-09-async/app/core/config.py:
--------------------------------------------------------------------------------
1 | import pathlib
2 |
3 | from pydantic import AnyHttpUrl, BaseSettings, EmailStr, validator
4 | from typing import List, Optional, Union
5 |
6 |
7 | # Project Directories
8 | ROOT = pathlib.Path(__file__).resolve().parent.parent
9 |
10 |
11 | class Settings(BaseSettings):
12 | API_V1_STR: str = "/api/v1"
13 | # BACKEND_CORS_ORIGINS is a JSON-formatted list of origins
14 | # e.g: '["http://localhost", "http://localhost:4200", "http://localhost:3000", \
15 | # "http://localhost:8080", "http://local.dockertoolbox.tiangolo.com"]'
16 | BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = []
17 |
18 | @validator("BACKEND_CORS_ORIGINS", pre=True)
19 | def assemble_cors_origins(cls, v: Union[str, List[str]]) -> Union[List[str], str]:
20 | if isinstance(v, str) and not v.startswith("["):
21 | return [i.strip() for i in v.split(",")]
22 | elif isinstance(v, (list, str)):
23 | return v
24 | raise ValueError(v)
25 |
26 | SQLALCHEMY_DATABASE_URI: Optional[str] = "sqlite:///example.db"
27 | FIRST_SUPERUSER: EmailStr = "admin@recipeapi.com"
28 |
29 | class Config:
30 | case_sensitive = True
31 |
32 |
33 | settings = Settings()
34 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/core/config.py:
--------------------------------------------------------------------------------
1 | import pathlib
2 |
3 | from pydantic import AnyHttpUrl, BaseSettings, EmailStr, validator
4 | from typing import List, Optional, Union
5 |
6 |
7 | # Project Directories
8 | ROOT = pathlib.Path(__file__).resolve().parent.parent
9 |
10 |
11 |
12 | class Settings(BaseSettings):
13 | API_V1_STR: str = "/api/v1"
14 | # BACKEND_CORS_ORIGINS is a JSON-formatted list of origins
15 | # e.g: '["http://localhost", "http://localhost:4200", "http://localhost:3000", \
16 | # "http://localhost:8080", "http://local.dockertoolbox.tiangolo.com"]'
17 | BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = []
18 |
19 | @validator("BACKEND_CORS_ORIGINS", pre=True)
20 | def assemble_cors_origins(cls, v: Union[str, List[str]]) -> Union[List[str], str]:
21 | if isinstance(v, str) and not v.startswith("["):
22 | return [i.strip() for i in v.split(",")]
23 | elif isinstance(v, (list, str)):
24 | return v
25 | raise ValueError(v)
26 |
27 | SQLALCHEMY_DATABASE_URI: Optional[str] = "sqlite:///example.db"
28 | FIRST_SUPERUSER: EmailStr = "admin@recipeapi.com"
29 |
30 | class Config:
31 | case_sensitive = True
32 |
33 |
34 | settings = Settings()
35 |
--------------------------------------------------------------------------------
/part-08-structure-and-versioning/app/main.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | from fastapi import FastAPI, APIRouter, Request, Depends
4 | from fastapi.templating import Jinja2Templates
5 | from sqlalchemy.orm import Session
6 |
7 | from app import crud
8 | from app.api import deps
9 | from app.api.api_v1.api import api_router
10 | from app.core.config import settings
11 |
12 | BASE_PATH = Path(__file__).resolve().parent
13 | TEMPLATES = Jinja2Templates(directory=str(BASE_PATH / "templates"))
14 |
15 | root_router = APIRouter()
16 | app = FastAPI(title="Recipe API")
17 |
18 |
19 | @root_router.get("/", status_code=200)
20 | def root(
21 | request: Request,
22 | db: Session = Depends(deps.get_db),
23 | ) -> dict:
24 | """
25 | Root GET
26 | """
27 | recipes = crud.recipe.get_multi(db=db, limit=10)
28 | return TEMPLATES.TemplateResponse(
29 | "index.html",
30 | {"request": request, "recipes": recipes},
31 | )
32 |
33 |
34 | app.include_router(api_router, prefix=settings.API_V1_STR)
35 | app.include_router(root_router)
36 |
37 |
38 | if __name__ == "__main__":
39 | # Use this for debugging purposes only
40 | import uvicorn
41 |
42 | uvicorn.run(app, host="0.0.0.0", port=8001, log_level="debug")
43 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/README.md:
--------------------------------------------------------------------------------
1 | ## Part 14 Local Setup
2 |
3 | (Non-Docker)
4 |
5 | 1. `pip install poetry` (or safer, follow the instructions: https://python-poetry.org/docs/#installation)
6 | 2. Install dependencies `cd` into the directory where the `pyproject.toml` is located then `poetry install`
7 | 3. If continuing from a previous part of the series, delete your current project database because we
8 | have made breaking DB migration changes. `rm example.db`. If you're starting here, you can ignore this step.
9 | 4. Run the DB migrations via poetry `poetry run python app/prestart.py` (only required once) (Unix users can use
10 | the bash script if preferred)
11 | 5. [UNIX]: Run the FastAPI server via poetry with the bash script: `poetry run ./run.sh`
12 | 6. [WINDOWS]: Run the FastAPI server via poetry with the Python command: `poetry run python app/main.py`
13 | 7. Open http://localhost:8001/
14 |
15 | ### Mailgun setup
16 | - Signup for a free account at [mailgun](https://www.mailgun.com/)
17 | - Get your API Key and Domain name and set them as environment variables to match the config.py file (or use a .env in your backend/app directory)
18 | - Set your [authorized recipient](https://help.mailgun.com/hc/en-us/articles/217531258-Authorized-Recipients) email addresses
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/src/components/Modal/PopupModal.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function PopupModal({ onCloseBtnPress, modalTitle, children }) {
4 | return (
5 |
6 |
7 |
11 |
12 |
{modalTitle}
13 |
30 |
31 | {children}
32 |
33 |
34 |
35 | );
36 | }
37 |
38 | export default PopupModal;
39 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/src/components/Modal/PopupModal.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function PopupModal({ onCloseBtnPress, modalTitle, children }) {
4 | return (
5 |
6 |
7 |
11 |
12 |
{modalTitle}
13 |
30 |
31 | {children}
32 |
33 |
34 |
35 | );
36 | }
37 |
38 | export default PopupModal;
39 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/src/components/Modal/PopupModal.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function PopupModal({ onCloseBtnPress, modalTitle, children }) {
4 | return (
5 |
6 |
7 |
11 |
12 |
{modalTitle}
13 |
30 |
31 | {children}
32 |
33 |
34 |
35 | );
36 | }
37 |
38 | export default PopupModal;
39 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/crud/crud_user.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Dict, Optional, Union
2 |
3 | from sqlalchemy.orm import Session
4 |
5 | from app.crud.base import CRUDBase
6 | from app.models.user import User
7 | from app.schemas.user import UserCreate, UserUpdate
8 | from app.core.security import get_password_hash
9 |
10 |
11 | class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
12 | def get_by_email(self, db: Session, *, email: str) -> Optional[User]:
13 | return db.query(User).filter(User.email == email).first()
14 |
15 | def create(self, db: Session, *, obj_in: UserCreate) -> User:
16 | create_data = obj_in.dict()
17 | create_data.pop("password")
18 | db_obj = User(**create_data)
19 | db_obj.hashed_password = get_password_hash(obj_in.password)
20 | db.add(db_obj)
21 | db.commit()
22 |
23 | return db_obj
24 |
25 | def update(
26 | self, db: Session, *, db_obj: User, obj_in: Union[UserUpdate, Dict[str, Any]]
27 | ) -> User:
28 | if isinstance(obj_in, dict):
29 | update_data = obj_in
30 | else:
31 | update_data = obj_in.dict(exclude_unset=True)
32 |
33 | return super().update(db, db_obj=db_obj, obj_in=update_data)
34 |
35 | def is_superuser(self, user: User) -> bool:
36 | return user.is_superuser
37 |
38 |
39 | user = CRUDUser(User)
40 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/crud/crud_user.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Dict, Optional, Union
2 |
3 | from sqlalchemy.orm import Session
4 |
5 | from app.crud.base import CRUDBase
6 | from app.models.user import User
7 | from app.schemas.user import UserCreate, UserUpdate
8 | from app.core.security import get_password_hash
9 |
10 |
11 | class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
12 | def get_by_email(self, db: Session, *, email: str) -> Optional[User]:
13 | return db.query(User).filter(User.email == email).first()
14 |
15 | def create(self, db: Session, *, obj_in: UserCreate) -> User:
16 | create_data = obj_in.dict()
17 | create_data.pop("password")
18 | db_obj = User(**create_data)
19 | db_obj.hashed_password = get_password_hash(obj_in.password)
20 | db.add(db_obj)
21 | db.commit()
22 |
23 | return db_obj
24 |
25 | def update(
26 | self, db: Session, *, db_obj: User, obj_in: Union[UserUpdate, Dict[str, Any]]
27 | ) -> User:
28 | if isinstance(obj_in, dict):
29 | update_data = obj_in
30 | else:
31 | update_data = obj_in.dict(exclude_unset=True)
32 |
33 | return super().update(db, db_obj=db_obj, obj_in=update_data)
34 |
35 | def is_superuser(self, user: User) -> bool:
36 | return user.is_superuser
37 |
38 |
39 | user = CRUDUser(User)
40 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/crud/crud_user.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Dict, Optional, Union
2 |
3 | from sqlalchemy.orm import Session
4 |
5 | from app.crud.base import CRUDBase
6 | from app.models.user import User
7 | from app.schemas.user import UserCreate, UserUpdate
8 | from app.core.security import get_password_hash
9 |
10 |
11 | class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
12 | def get_by_email(self, db: Session, *, email: str) -> Optional[User]:
13 | return db.query(User).filter(User.email == email).first()
14 |
15 | def create(self, db: Session, *, obj_in: UserCreate) -> User:
16 | create_data = obj_in.dict()
17 | create_data.pop("password")
18 | db_obj = User(**create_data)
19 | db_obj.hashed_password = get_password_hash(obj_in.password)
20 | db.add(db_obj)
21 | db.commit()
22 |
23 | return db_obj
24 |
25 | def update(
26 | self, db: Session, *, db_obj: User, obj_in: Union[UserUpdate, Dict[str, Any]]
27 | ) -> User:
28 | if isinstance(obj_in, dict):
29 | update_data = obj_in
30 | else:
31 | update_data = obj_in.dict(exclude_unset=True)
32 |
33 | return super().update(db, db_obj=db_obj, obj_in=update_data)
34 |
35 | def is_superuser(self, user: User) -> bool:
36 | return user.is_superuser
37 |
38 |
39 | user = CRUDUser(User)
40 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/backend/app/app/crud/crud_user.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Dict, Optional, Union
2 |
3 | from sqlalchemy.orm import Session
4 |
5 | from app.crud.base import CRUDBase
6 | from app.models.user import User
7 | from app.schemas.user import UserCreate, UserUpdate
8 | from app.core.security import get_password_hash
9 |
10 |
11 | class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
12 | def get_by_email(self, db: Session, *, email: str) -> Optional[User]:
13 | return db.query(User).filter(User.email == email).first()
14 |
15 | def create(self, db: Session, *, obj_in: UserCreate) -> User:
16 | create_data = obj_in.dict()
17 | create_data.pop("password")
18 | db_obj = User(**create_data)
19 | db_obj.hashed_password = get_password_hash(obj_in.password)
20 | db.add(db_obj)
21 | db.commit()
22 |
23 | return db_obj
24 |
25 | def update(
26 | self, db: Session, *, db_obj: User, obj_in: Union[UserUpdate, Dict[str, Any]]
27 | ) -> User:
28 | if isinstance(obj_in, dict):
29 | update_data = obj_in
30 | else:
31 | update_data = obj_in.dict(exclude_unset=True)
32 |
33 | return super().update(db, db_obj=db_obj, obj_in=update_data)
34 |
35 | def is_superuser(self, user: User) -> bool:
36 | return user.is_superuser
37 |
38 |
39 | user = CRUDUser(User)
40 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/backend/app/app/crud/crud_user.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Dict, Optional, Union
2 |
3 | from sqlalchemy.orm import Session
4 |
5 | from app.crud.base import CRUDBase
6 | from app.models.user import User
7 | from app.schemas.user import UserCreate, UserUpdate
8 | from app.core.security import get_password_hash
9 |
10 |
11 | class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
12 | def get_by_email(self, db: Session, *, email: str) -> Optional[User]:
13 | return db.query(User).filter(User.email == email).first()
14 |
15 | def create(self, db: Session, *, obj_in: UserCreate) -> User:
16 | create_data = obj_in.dict()
17 | create_data.pop("password")
18 | db_obj = User(**create_data)
19 | db_obj.hashed_password = get_password_hash(obj_in.password)
20 | db.add(db_obj)
21 | db.commit()
22 |
23 | return db_obj
24 |
25 | def update(
26 | self, db: Session, *, db_obj: User, obj_in: Union[UserUpdate, Dict[str, Any]]
27 | ) -> User:
28 | if isinstance(obj_in, dict):
29 | update_data = obj_in
30 | else:
31 | update_data = obj_in.dict(exclude_unset=True)
32 |
33 | return super().update(db, db_obj=db_obj, obj_in=update_data)
34 |
35 | def is_superuser(self, user: User) -> bool:
36 | return user.is_superuser
37 |
38 |
39 | user = CRUDUser(User)
40 |
--------------------------------------------------------------------------------
/part-12-react-frontend/frontend/src/pages/error-page/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from "react-router-dom";
3 | const ErrorPage = () => {
4 | return (
5 |
14 |
15 |
16 |
404
17 |
18 | Oops! Page not found
19 |
20 |
21 | The page you’re looking for doesn’t exist.
22 |
23 |
Go home
27 |
28 |
29 |
30 | )
31 | }
32 |
33 |
34 |
35 |
36 | export default ErrorPage;
37 |
--------------------------------------------------------------------------------
/part-13-docker-deployment/frontend/src/pages/error-page/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from "react-router-dom";
3 | const ErrorPage = () => {
4 | return (
5 |
14 |
15 |
16 |
404
17 |
18 | Oops! Page not found
19 |
20 |
21 | The page you’re looking for doesn’t exist.
22 |
23 |
Go home
27 |
28 |
29 |
30 | )
31 | }
32 |
33 |
34 |
35 |
36 | export default ErrorPage;
37 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/tests/conftest.py:
--------------------------------------------------------------------------------
1 | from typing import Generator
2 | from unittest.mock import MagicMock
3 |
4 | import pytest
5 | from fastapi.testclient import TestClient
6 |
7 | from app.main import app
8 | from app.api import deps
9 |
10 |
11 | async def override_reddit_dependency() -> MagicMock:
12 | mock = MagicMock()
13 | reddit_stub = {
14 | "recipes": [
15 | "2085: the best chicken wings ever!! (https://i.redd.it/5iabdxh1jq381.jpg)",
16 | ],
17 | "easyrecipes": [
18 | "74: Instagram accounts that post easy recipes? (https://www.reddit.com/r/easyrecipes/comments/rcluhd/instagram_accounts_that_post_easy_recipes/)",
19 | ],
20 | "TopSecretRecipes": [
21 | "238: Halal guys red sauce - looking for recipe. Tried a recipe from a google search and it wasn’t nearly spicy enough. (https://i.redd.it/516yb30q9u381.jpg)",
22 | "132: Benihana Diablo Sauce - THE AUTHENTIC RECIPE! (https://www.reddit.com/r/TopSecretRecipes/comments/rbcirf/benihana_diablo_sauce_the_authentic_recipe/)",
23 | ],
24 | }
25 | mock.get_reddit_top.return_value = reddit_stub
26 | return mock
27 |
28 |
29 | @pytest.fixture
30 | def client() -> Generator:
31 | with TestClient(app) as client:
32 | app.dependency_overrides[deps.get_reddit_client] = override_reddit_dependency
33 | yield client
34 | app.dependency_overrides = {}
35 |
--------------------------------------------------------------------------------
/part-14-send-email-in-background/frontend/src/pages/error-page/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from "react-router-dom";
3 | const ErrorPage = () => {
4 | return (
5 |
14 |
15 |
16 |
404
17 |
18 | Oops! Page not found
19 |
20 |
21 | The page you’re looking for doesn’t exist.
22 |
23 |
Go home
27 |
28 |
29 |
30 | )
31 | }
32 |
33 |
34 |
35 |
36 | export default ErrorPage;
37 |
--------------------------------------------------------------------------------
/part-12-react-frontend/backend/app/tests/conftest.py:
--------------------------------------------------------------------------------
1 | from typing import Generator
2 | from unittest.mock import MagicMock
3 |
4 | import pytest
5 | from fastapi.testclient import TestClient
6 |
7 | from app.main import app
8 | from app.api import deps
9 |
10 |
11 | async def override_reddit_dependency() -> MagicMock:
12 | mock = MagicMock()
13 | reddit_stub = {
14 | "recipes": [
15 | "2085: the best chicken wings ever!! (https://i.redd.it/5iabdxh1jq381.jpg)",
16 | ],
17 | "easyrecipes": [
18 | "74: Instagram accounts that post easy recipes? (https://www.reddit.com/r/easyrecipes/comments/rcluhd/instagram_accounts_that_post_easy_recipes/)",
19 | ],
20 | "TopSecretRecipes": [
21 | "238: Halal guys red sauce - looking for recipe. Tried a recipe from a google search and it wasn’t nearly spicy enough. (https://i.redd.it/516yb30q9u381.jpg)",
22 | "132: Benihana Diablo Sauce - THE AUTHENTIC RECIPE! (https://www.reddit.com/r/TopSecretRecipes/comments/rbcirf/benihana_diablo_sauce_the_authentic_recipe/)",
23 | ],
24 | }
25 | mock.get_reddit_top.return_value = reddit_stub
26 | return mock
27 |
28 |
29 | @pytest.fixture()
30 | def client() -> Generator:
31 | with TestClient(app) as client:
32 | app.dependency_overrides[deps.get_reddit_client] = override_reddit_dependency
33 | yield client
34 | app.dependency_overrides = {}
35 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/core/config.py:
--------------------------------------------------------------------------------
1 | import pathlib
2 |
3 | from pydantic import AnyHttpUrl, BaseSettings, EmailStr, validator
4 | from typing import List, Optional, Union
5 |
6 |
7 | # Project Directories
8 | ROOT = pathlib.Path(__file__).resolve().parent.parent
9 |
10 |
11 | class Settings(BaseSettings):
12 | API_V1_STR: str = "/api/v1"
13 | JWT_SECRET: str = "TEST_SECRET_DO_NOT_USE_IN_PROD"
14 | ALGORITHM: str = "HS256"
15 |
16 | # 60 minutes * 24 hours * 8 days = 8 days
17 | ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8
18 |
19 | # BACKEND_CORS_ORIGINS is a JSON-formatted list of origins
20 | # e.g: '["http://localhost", "http://localhost:4200", "http://localhost:3000", \
21 | # "http://localhost:8080", "http://local.dockertoolbox.tiangolo.com"]'
22 | BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = []
23 |
24 | @validator("BACKEND_CORS_ORIGINS", pre=True)
25 | def assemble_cors_origins(cls, v: Union[str, List[str]]) -> Union[List[str], str]:
26 | if isinstance(v, str) and not v.startswith("["):
27 | return [i.strip() for i in v.split(",")]
28 | elif isinstance(v, (list, str)):
29 | return v
30 | raise ValueError(v)
31 |
32 | SQLALCHEMY_DATABASE_URI: Optional[str] = "sqlite:///example.db"
33 | FIRST_SUPERUSER: EmailStr = "admin@recipeapi.com"
34 | FIRST_SUPERUSER_PW: str = "CHANGEME"
35 |
36 | class Config:
37 | case_sensitive = True
38 |
39 |
40 | settings = Settings()
41 |
--------------------------------------------------------------------------------
/part-11-dependency-injection/app/core/config.py:
--------------------------------------------------------------------------------
1 | import pathlib
2 |
3 | from pydantic import AnyHttpUrl, BaseSettings, EmailStr, validator
4 | from typing import List, Optional, Union
5 |
6 |
7 | # Project Directories
8 | ROOT = pathlib.Path(__file__).resolve().parent.parent
9 |
10 |
11 | class Settings(BaseSettings):
12 | API_V1_STR: str = "/api/v1"
13 | JWT_SECRET: str = "TEST_SECRET_DO_NOT_USE_IN_PROD"
14 | ALGORITHM: str = "HS256"
15 |
16 | # 60 minutes * 24 hours * 8 days = 8 days
17 | ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8
18 |
19 | # BACKEND_CORS_ORIGINS is a JSON-formatted list of origins
20 | # e.g: '["http://localhost", "http://localhost:4200", "http://localhost:3000", \
21 | # "http://localhost:8080", "http://local.dockertoolbox.tiangolo.com"]'
22 | BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = []
23 |
24 | @validator("BACKEND_CORS_ORIGINS", pre=True)
25 | def assemble_cors_origins(cls, v: Union[str, List[str]]) -> Union[List[str], str]:
26 | if isinstance(v, str) and not v.startswith("["):
27 | return [i.strip() for i in v.split(",")]
28 | elif isinstance(v, (list, str)):
29 | return v
30 | raise ValueError(v)
31 |
32 | SQLALCHEMY_DATABASE_URI: Optional[str] = "sqlite:///example.db"
33 | FIRST_SUPERUSER: EmailStr = "admin@recipeapi.com"
34 | FIRST_SUPERUSER_PW: str = "CHANGEME"
35 |
36 | class Config:
37 | case_sensitive = True
38 |
39 |
40 | settings = Settings()
41 |
--------------------------------------------------------------------------------
/part-09-async/app/main.py:
--------------------------------------------------------------------------------
1 | import time
2 | from pathlib import Path
3 |
4 | from fastapi import FastAPI, APIRouter, Request, Depends
5 | from fastapi.templating import Jinja2Templates
6 | from sqlalchemy.orm import Session
7 |
8 | from app import crud
9 | from app.api import deps
10 | from app.api.api_v1.api import api_router
11 | from app.core.config import settings
12 |
13 | BASE_PATH = Path(__file__).resolve().parent
14 | TEMPLATES = Jinja2Templates(directory=str(BASE_PATH / "templates"))
15 |
16 | root_router = APIRouter()
17 | app = FastAPI(title="Recipe API", openapi_url="/openapi.json")
18 |
19 |
20 | @root_router.get("/", status_code=200)
21 | def root(
22 | request: Request,
23 | db: Session = Depends(deps.get_db),
24 | ) -> dict:
25 | """
26 | Root GET
27 | """
28 | recipes = crud.recipe.get_multi(db=db, limit=10)
29 | return TEMPLATES.TemplateResponse(
30 | "index.html",
31 | {"request": request, "recipes": recipes},
32 | )
33 |
34 |
35 | @app.middleware("http")
36 | async def add_process_time_header(request: Request, call_next):
37 | start_time = time.time()
38 | response = await call_next(request)
39 | process_time = time.time() - start_time
40 | response.headers["X-Process-Time"] = str(process_time)
41 | return response
42 |
43 |
44 | app.include_router(api_router, prefix=settings.API_V1_STR)
45 | app.include_router(root_router)
46 |
47 |
48 | if __name__ == "__main__":
49 | # Use this for debugging purposes only
50 | import uvicorn
51 |
52 | uvicorn.run(app, host="0.0.0.0", port=8001, log_level="debug")
53 |
--------------------------------------------------------------------------------
/part-10-jwt-auth/app/main.py:
--------------------------------------------------------------------------------
1 | import time
2 | from pathlib import Path
3 |
4 | from fastapi import FastAPI, APIRouter, Request, Depends
5 | from fastapi.templating import Jinja2Templates
6 | from sqlalchemy.orm import Session
7 |
8 | from app import crud
9 | from app.api import deps
10 | from app.api.api_v1.api import api_router
11 | from app.core.config import settings
12 |
13 | BASE_PATH = Path(__file__).resolve().parent
14 | TEMPLATES = Jinja2Templates(directory=str(BASE_PATH / "templates"))
15 |
16 | root_router = APIRouter()
17 | app = FastAPI(title="Recipe API", openapi_url=f"{settings.API_V1_STR}/openapi.json")
18 |
19 |
20 | @root_router.get("/", status_code=200)
21 | def root(
22 | request: Request,
23 | db: Session = Depends(deps.get_db),
24 | ) -> dict:
25 | """
26 | Root GET
27 | """
28 | recipes = crud.recipe.get_multi(db=db, limit=10)
29 | return TEMPLATES.TemplateResponse(
30 | "index.html",
31 | {"request": request, "recipes": recipes},
32 | )
33 |
34 |
35 | @app.middleware("http")
36 | async def add_process_time_header(request: Request, call_next):
37 | start_time = time.time()
38 | response = await call_next(request)
39 | process_time = time.time() - start_time
40 | response.headers["X-Process-Time"] = str(process_time)
41 | return response
42 |
43 |
44 | app.include_router(api_router, prefix=settings.API_V1_STR)
45 | app.include_router(root_router)
46 |
47 |
48 | if __name__ == "__main__":
49 | # Use this for debugging purposes only
50 | import uvicorn
51 |
52 | uvicorn.run(app, host="0.0.0.0", port=8001, log_level="debug")
53 |
--------------------------------------------------------------------------------