Repository: hamedasgari20/Python-Django-FastAPI-advanced-topics
Branch: master
Commit: 558bc03c4418
Files: 8
Total size: 250.7 KB
Directory structure:
gitextract_hejsh2ic/
├── .gitignore
├── .idea/
│ ├── .gitignore
│ ├── inspectionProfiles/
│ │ └── profiles_settings.xml
│ ├── misc.xml
│ ├── modules.xml
│ ├── python advanced topics.iml
│ └── vcs.xml
└── README.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Created by https://www.toptal.com/developers/gitignore/api/pycharm
# Edit at https://www.toptal.com/developers/gitignore?templates=pycharm
### PyCharm ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### PyCharm Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
# https://plugins.jetbrains.com/plugin/7973-sonarlint
.idea/**/sonarlint/
# SonarQube Plugin
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
.idea/**/sonarIssues.xml
# Markdown Navigator plugin
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator/
# Cache file creation bug
# See https://youtrack.jetbrains.com/issue/JBR-2257
.idea/$CACHE_FILE$
# CodeStream plugin
# https://plugins.jetbrains.com/plugin/12206-codestream
.idea/codestream.xml
# Azure Toolkit for IntelliJ plugin
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
.idea/**/azureSettings.xml
# End of https://www.toptal.com/developers/gitignore/api/pycharm
Ask AI to edit or generate...
================================================
FILE: .idea/.gitignore
================================================
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
================================================
FILE: .idea/inspectionProfiles/profiles_settings.xml
================================================
================================================
FILE: .idea/misc.xml
================================================
================================================
FILE: .idea/modules.xml
================================================
================================================
FILE: .idea/python advanced topics.iml
================================================
================================================
FILE: .idea/vcs.xml
================================================
================================================
FILE: README.md
================================================

## Introduction
In this research, I have given a brief overview of advanced topics in Python and Django. This article does not provide
in-depth knowledge on various topics and the purpose of this research is to collect topics in one place to provide the
reader with a mental framework. In this article, I have used very simple examples to explain the topics easily.
Naturally, studying and examining more and more practical examples will help a lot to understand each topic. I hope
reading this article is useful for you. (**Hamid Asgari**)
* [Introduction](#introduction)
* [Python related topics:](#python-related-topics)
* [object-oriented programming (OOP)](#object-oriented-programming-oop)
* [Inheritance](#inheritance)
* [Method Resolution Order (MRO)](#method-resolution-order-mro)
* [1. Single Inheritance](#1-single-inheritance)
* [2. Multiple Inheritance](#2-multiple-inheritance)
* [3. The Diamond Inheritance Problem](#3-the-diamond-inheritance-problem)
* [4. Using super() with MRO](#4-using-super-with-mro)
* [5. Checking the MRO](#5-checking-the-mro)
* [Polymorphism](#polymorphism)
* [Encapsulation](#encapsulation)
* [Abstraction](#abstraction)
* [Decorators](#decorators)
* [More decorators in Python](#more-decorators-in-python)
* [Map function](#map-function)
* [Itertools](#itertools)
* [Lambda function](#lambda-function)
* [Exception Handling](#exception-handling)
* [SOLID principles](#solid-principles)
* [Python collection](#python-collection)
* [frozenset in Python](#frozenset-in-python)
* [Generators and Iterators](#generators-and-iterators)
* [Magic methods](#magic-methods)
* [GIL](#gil)
* [concurrency and parallelism](#concurrency-and-parallelism)
* [Main types of methods in python classes](#main-types-of-methods-in-python-classes)
* [Data serialization](#data-serialization)
* [Data class in python](#data-class-in-python)
* [Shallow copy and deep copy](#shallow-copy-and-deep-copy)
* [Local and global variables](#local-and-global-variables)
* [Comprehension](#comprehension)
* [Pydantic](#pydantic)
* [Args and Kwargs in Python](#args-and-kwargs-in-python)
* [Operator overloading](#operator-overloading)
* [Recursive function](#recursive-function)
* [Context manager](#context-manager)
* [Python 3.11 over previous versions](#python-311-over-previous-versions)
* [Semaphores and Mutexes](#semaphores-and-mutexes)
* [Python built-in functions](#python-built-in-functions)
* [Type Hints and Type Checking](#type-hints-and-type-checking)
* [Package Management](#package-management)
* [functools Module](#functools-module)
* [Advanced Collections](#advanced-collections)
* [Enum and NamedTuple](#enum-and-namedtuple)
* [asyncio (Advanced)](#asyncio-advanced)
* [datetime Module](#datetime-module)
* [Abstract Base Classes (ABC) Advanced](#abstract-base-classes-abc-advanced)
* [JSON vs Pickle Comparison](#json-vs-pickle-comparison)
* [Composition vs Inheritance](#composition-vs-inheritance)
* [Django related topics:](#django-related-topics)
* [Django signals](#django-signals)
* [Django middleware](#django-middleware)
* [Django custom template tags](#django-custom-template-tags)
* [Django permissions](#django-permissions)
* [Django custom user models](#django-custom-user-models)
* [Django Custom Managers](#django-custom-managers)
* [Django Custom validators](#django-custom-validators)
* [Custom management commands](#custom-management-commands)
* [Django's Query API](#djangos-query-api)
* [Custom query expressions or Custom Lookup](#custom-query-expressions-or-custom-lookup)
* [Django Filterset](#django-filterset)
* [Context managers](#context-managers)
* [Django Channels](#django-channels)
* [HTTP methods in Django](#http-methods-in-django)
* [annotate and aggregate in Django](#annotate-and-aggregate-in-django)
* [Mixin in Django](#mixin-in-django)
* [Cache in Django](#cache-in-django)
* [Django constraint](#django-constraint)
* [bulk creation in Django](#bulk-creation-in-django)
* [prefetch_related and select_related in Django](#prefetch_related-and-select_related-in-django)
* [Third-party packages in Django](#third-party-packages-in-django)
* [Property decorators](#property-decorators)
* [WSGI and ASGI](#wsgi-and-asgi)
* [WSGI (Web Server Gateway Interface):](#wsgi-web-server-gateway-interface)
* [ASGI (Asynchronous Server Gateway Interface):](#asgi-asynchronous-server-gateway-interface)
* [Advanced features in ORM](#advanced-features-in-orm)
* [Class-based views methods](#class-based-views-methods)
* [Django optimization](#django-optimization)
* [Generic Foreign Key in Django](#generic-foreign-key-in-django)
* [Django custom exceptions](#django-custom-exceptions)
* [select_for_update in Django](#select_for_update-in-django)
* [Django model methods](#django-model-methods)
* [Parametric unit tests](#parametric-unit-tests)
* [Testing in Django](#testing-in-django)
* [Security Best Practices in Django](#security-best-practices-in-django)
* [Logging and Monitoring in Django](#logging-and-monitoring-in-django)
* [FastAPI related topics:](#fastapi-related-topics)
* [Dependency Injection in FastAPI](#dependency-injection-in-fastapi)
* [use cases](#use-cases)
* [Dependency Ordering](#dependency-ordering)
* [Pydantic methods in FastAPI](#pydantic-methods-in-fastapi)
* [Decorators in FastAPI](#decorators-in-fastapi)
* [Real-World Use Cases of decorators:](#real-world-use-cases-of-decorators)
* [WebSockets in FastAPI](#websockets-in-fastapi)
* [Real-World Use Cases of websocket:](#real-world-use-cases-of-websocket)
* [Asynchronous File Uploads](#asynchronous-file-uploads)
* [Real-World Use Cases of Asynchronous File Uploads:](#real-world-use-cases-of-asynchronous-file-uploads)
* [Security Headers](#security-headers)
* [Background Tasks](#background-tasks)
* [Middleware in FastAPI](#middleware-in-fastapi)
* [Real-World Use Cases of Middlewares:](#real-world-use-cases-of-middlewares)
* [Permissions in FastAPI](#permissions-in-fastapi)
* [Custom Validators in FastAPI](#custom-validators-in-fastapi)
* [Use Cases in Real-World Applications](#use-cases-in-real-world-applications)
* [FastAPI BaseSettings](#fastapi-basesettings)
* [The use of **BaseSettings** in FastAPI has several real-world applications, including:](#the-use-of-basesettings-in-fastapi-has-several-real-world-applications-including)
* [Dependency Caching](#dependency-caching)
* [Use Cases in Real-World Applications](#use-cases-in-real-world-applications-1)
* [Rate Limiting](#rate-limiting)
* [Use Cases in Real-World Applications](#use-cases-in-real-world-applications-2)
* [Cache in FastAPI](#cache-in-fastapi)
* [Use Cases in Real-World Applications](#use-cases-in-real-world-applications-3)
* [Custom Exceptions in FastAPI](#custom-exceptions-in-fastapi)
* [Use Cases in Real-World Applications](#use-cases-in-real-world-applications-4)
* [Optimization techniques in FastAPI](#optimization-techniques-in-fastapi)
* [Concurrency and Parallelism In FastAPI](#concurrency-and-parallelism-in-fastapi)
* [API Documentation Best Practices](#api-documentation-best-practices)
* [Testing in FastAPI](#testing-in-fastapi)
* [Security Best Practices in FastAPI](#security-best-practices-in-fastapi)
* [Logging and Monitoring in FastAPI](#logging-and-monitoring-in-fastapi)
* [General Topics:](#general-topics)
* [REST API Design Principles](#rest-api-design-principles)
* [Deployment and DevOps](#deployment-and-devops)
* [Docker and Containerization](#docker-and-containerization)
* [CI/CD Pipelines](#cicd-pipelines)
* [Environment Management](#environment-management)
* [Interview Preparation questions](#interview-preparation-questions)
* [PostgreSQL Querying](#postgresql-querying)
* [Algorithmic Problem Solving](#algorithmic-problem-solving)
* [Common Data Structures Review](#common-data-structures-review)
* [Problem-Solving Strategy](#problem-solving-strategy)
* [Practice Problems & Solutions (Python)](#practice-problems--solutions-python)
* [Interview Questions](#interview-questions)
* [Problem 1: Database Transactions (Django & SQLAlchemy)](#problem-1-database-transactions-django--sqlalchemy)
## Python related topics:
### object-oriented programming (OOP)
Here are some of the most important topics in object-oriented programming (**OOP**) in Python, along with simple
examples of each:
#### Inheritance
Inheritance is a mechanism that allows a class to inherit properties and methods from another class. The class that
inherits from another class is called a subclass or derived class, while the class that is being inherited from is
called a superclass or base class.
```
from abc import ABC, abstractmethod
class Animal(ABC):
def __init__(self, name):
self.name = name
@abstractmethod
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
dog = Dog("Fido")
print(dog.name) # Output: Fido
print(dog.speak()) # Output: Woof!
cat = Cat("Whiskers")
print(cat.name) # Output: Whiskers
print(cat.speak()) # Output: Meow!
```
In this example, Animal is the superclass, and Dog and Cat are subclasses that inherit from it. The speak method is an
abstract method in the Animal class that is implemented in the subclasses.
We import the **ABC** (Abstract Base Class) class from the **abc** module and use the **@abstractmethod** decorator to mark the **speak** method as an abstract method in the **Animal** class. This enforces that subclasses of **Animal** must provide an implementation for the speak method.
#### Method Resolution Order (MRO)
The method resolution order (MRO) defines the sequence in which base classes are searched when executing a method. Initially, the search starts within the class itself, and then proceeds according to the order specified during inheritance. This order is also known as the linearization of a class, and the set of rules governing it is referred to as MRO (Method Resolution Order).
To illustrate this concept more clearly, let's walk through some examples that cover different inheritance scenarios.
##### 1. Single Inheritance
In single inheritance, the MRO is very straightforward. When a derived class inherits from a single base class, Python searches the base class for the desired method or attribute if it’s not found in the derived class.
```
class A:
def greet(self):
print("Hello from A")
class B(A):
pass
b = B()
b.greet()
```
Output:
```
Hello from A
```
##### 2. Multiple Inheritance
In multiple inheritance, the MRO determines the sequence in which parent classes are called. Python uses the C3 linearization algorithm to establish this order, ensuring that each class appears only once.
```
class A:
def greet(self):
print("Hello from A")
class B:
def greet(self):
print("Hello from B")
class C(A, B):
pass
c = C()
c.greet()
```
Output:
```
Hello from A
```
##### 3. The Diamond Inheritance Problem
The diamond inheritance problem occurs when a class inherits from multiple classes that share a common ancestor. MRO ensures that the common ancestor is not invoked multiple times, preventing redundancy.
```
class A:
def greet(self):
print("Hello from A")
class B(A):
def greet(self):
print("Hello from B")
class C(A):
def greet(self):
print("Hello from C")
class D(B, C):
pass
d = D()
d.greet()
```
Output:
```
Hello from B
```
##### 4. Using super() with MRO
Using the super() function in a multiple inheritance scenario ensures that the next class in the MRO sequence is called. This is particularly useful for maintaining a consistent flow through all classes in the hierarchy.
```
class A:
def greet(self):
print("Hello from A")
class B(A):
def greet(self):
print("Hello from B")
super().greet()
class C(A):
def greet(self):
print("Hello from C")
super().greet()
class D(B, C):
def greet(self):
print("Hello from D")
super().greet()
d = D()
d.greet()
```
Explanation:
D calls B, B calls C, and C calls A according to the MRO (D -> B -> C -> A).
Using super() ensures that each class in the MRO chain is called in order.
Output:
```
Hello from D
Hello from B
Hello from C
Hello from A
```
##### 5. Checking the MRO
You can inspect the MRO of a class using the mro() method or the __mro__ attribute.
```
print(D.mro())
```
Output:
```
[, , , , ]
```
This output tells you the exact order in which classes are searched for methods or attributes when an instance of D is used.
#### Polymorphism
Polymorphism is the ability of objects to take on different forms or perform different actions depending on the context.
In Python, polymorphism is achieved through **method overriding** and **method overloading**.
```
from abc import ABC, abstractmethod
import math
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
shapes = [Rectangle(5, 10), Circle(7)]
for shape in shapes:
print(shape.area())
```
In this example, Shape is an abstract class that defines the area method as an abstract method. Rectangle and Circle are
concrete subclasses that implement the area method. The shapes list contains instances of both Rectangle and Circle, and
the area method is called on each instance, demonstrating polymorphism.
In this code, we use the **ABC** class and the **@abstractmethod** decorator to define **Shape** as an abstract base class with an abstract area method. This ensures that subclasses of **Shape** must implement the area method. The **math.pi** constant is used for more accurate calculations of the circle's area.
#### Encapsulation
Encapsulation is the practice of hiding the internal details of an object and providing a public interface for
interacting with it. In Python, encapsulation is achieved through the use of _public and private methods and attributes_.
```
class BankAccount:
def __init__(self, balance):
self._balance = balance
def deposit(self, amount):
self._balance += amount
def withdraw(self, amount):
if self._balance >= amount:
self._balance -= amount
else:
raise ValueError("Insufficient balance")
def get_balance(self):
return self._balance
account = BankAccount(1000)
account.deposit(500)
account.withdraw(200)
print(account.get_balance()) # Output: 1300
```
In this example, **BankAccount** is a class that represents a bank account. The **_balance** attribute is marked as _private_
by convention (_using a single underscore_), and can only be accessed through public methods such as **deposit**, **withdraw**,
and **get_balance**.
#### Abstraction
Abstraction is the process of simplifying complex systems by breaking them down into smaller, more manageable parts. In
Python, abstraction is achieved through the use of _abstract classes_ and interfaces.
```
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
shapes = [Rectangle(5, 10)]
for shape in shapes:
print(shape.area())
```
In this example, **Shape** is an abstract class that defines the **area** method as an **abstract** method. **Rectangle** is a concrete
subclass that implements the area method. The shapes list contains an instance of **Rectangle**, demonstrating abstraction.
### Decorators
In Python, decorators are a way to modify the behavior of functions or classes by wrapping them with other functions.
Here's a simple example to demonstrate the basic concept of decorators:
```
def decorator_function(original_function):
def wrapper_function():
print("Before the original function is called.")
original_function()
print("After the original function is called.")
return wrapper_function
@decorator_function
def greet():
print("Hello, world!")
greet()
```
out put
```
Before the original function is called.
Hello, world!
After the original function is called.
```
Here is an example of how to implement the previous decorator in a class-based way:
```angular2html
class DecoratorClass:
def __init__(self, original_function):
self.original_function = original_function
def __call__(self):
print("Before the original function is called.")
self.original_function()
print("After the original function is called.")
@DecoratorClass
def greet():
print("Hello, world!")
greet()
```
In this example, the **DecoratorClass** is a class-based decorator that takes the original function as an argument in its constructor. The **__call__** method is used to define the behavior of the decorator when the decorated function is called.
### More decorators in Python
In Python, class decorators are functions or callable objects that are used to modify or enhance the behavior of a class. They are applied to the class definition itself and can add or modify class-level attributes, methods, or perform other operations on the class.
Here are a few examples of class decorators:
- classmethod
The **classmethod** decorator is a built-in decorator that transforms a method into a class method. Class methods can be called on the class itself, rather than on instances of the class.
Here's a simple example of a class method in Python
```
class Car:
total_cars = 0 # Class variable to keep track of the total number of cars
def __init__(self, name):
self.name = name
Car.total_cars += 1
def get_name(self):
return self.name
@classmethod
def get_total_cars(cls):
return cls.total_cars
car1 = Car("Toyota")
car2 = Car("Honda")
car3 = Car("Ford")
print(car1.get_name()) # Output: "Toyota"
print(Car.get_total_cars()) # Output: 3
```
In this example, we have a **Car** class with a class variable **total_cars**, which keeps track of the total number of car objects created. The **__init__** method increments **total_cars** by 1 whenever a new car object is created.
- staticmethod
The **staticmethod** decorator is another built-in decorator that transforms a method into a static method. Static methods are similar to regular functions and do not have access to the class or instance.
Here's a simple example of a static method in Python:
```angular2html
class MathUtils:
@staticmethod
def add_numbers(x, y):
return x + y
@staticmethod
def multiply_numbers(x, y):
return x * y
sum_result = MathUtils.add_numbers(5, 3)
print(sum_result) # Output: 8
product_result = MathUtils.multiply_numbers(4, 2)
print(product_result) # Output: 8
```
Static methods are defined using the **@staticmethod** decorator. They don't receive any special first parameter like instance methods or class methods. They behave like regular functions defined within the class.
- property
The **property** decorator allows you to define methods that can be accessed like attributes, providing a way to implement computed or dynamic properties.
```angular2html
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def diameter(self):
return self.radius * 2
@property
def area(self):
return 3.14 * self.radius**2
# Creating an instance of Circle
my_circle = Circle(5)
print(my_circle.diameter) # Output: 10
print(my_circle.area) # Output: 78.5
```
In this example, we use the **property** decorator to define the **diameter** and **area** methods.
- abstractmethod
The **abstractmethod** decorator is used in combination with the **ABC** module to define abstract methods in abstract base classes
The **abstractmethod** decorator ensures that any subclass of super class must implement this method.
```
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def calculate_area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def calculate_area(self):
return self.width * self.height
# Creating an instance of Rectangle
my_rectangle = Rectangle(5, 10)
print(my_rectangle.calculate_area()) # Output: 50
```
The **abstractmethod** decorator ensures that any subclass of **Shape** must implement this method.
- dataclass
The **dataclass** decorator is available in the **dataclasses** module (introduced in Python 3.7) and provides a concise way to define classes that are primarily used to store data.
```angular2html
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
# Creating an instance of Person
person = Person("Alice", 25)
print(person.name) # Output: Alice
print(person.age) # Output: 25
```
In this example, we apply the **@dataclass** decorator to the **Person** class. The decorator automatically generates special methods like **__init__, __repr__, and __eq__** based on the class variables (name and age in this case).
The **dataclass** decorator simplifies the process of defining classes that are primarily used for holding data, reducing the amount of boilerplate code required.
- cached_property
The **cached_property** decorator is available in third-party libraries such as **django.utils.functional** and **cachetools** and provides a way to cache the result of a method as a property, improving performance when the method is called multiple times.
```angular2html
from django.utils.functional import cached_property
class Square:
def __init__(self, side):
self.side = side
@cached_property
def area(self):
print("Calculating area...")
return self.side**2
# Creating an instance of Square
my_square = Square(5)
print(my_square.area) # Output: Calculating area... 25
print(my_square.area) # Output: 25 (Cached result, no recalculation)
```
In this example, we apply the **@cached_property** decorator to the **area** method. The decorator caches the result of the method on the instance, so subsequent accesses to area return the cached value without recalculating it.
### Map function
The __map()__ function in Python is a built-in function that applies a given function to each item in an iterable (such as a
list, tuple, or string) and returns an iterator with the results. It takes two or more arguments: the __function__ to apply
and one or more iterables. The basic syntax of the __map()__ function is as follows:
```
map(function, iterable1, iterable2, ...)
```
Here's an example that demonstrates the usage of the **map()** function:
```
# Example 1: Squaring numbers using map()
numbers = [1, 2, 3, 4, 5]
squared = map(lambda x: x**2, numbers)
print(list(squared))
# Output: [1, 4, 9, 16, 25]
```
### Itertools
Itertools is a Python module that provides a collection of functions for creating and manipulating iterators, which are
objects that can be iterated (looped) over. Here are some commonly used functions provided by the itertools module
- **count()**: Generates an infinite sequence of numbers starting from a specified value.
- **chain()**: Combines multiple iterators into a single iterator.
- **groupby()**: Groups consecutive elements in an iterable based on a common key
- **cycle()**: cycles through the elements of an iterable object infinitely
- **combinations()**: generates all possible combinations of a given iterable object
example of cycle():
```
import itertools
colors = ['red', 'green', 'blue']
color_cycle = itertools.cycle(colors)
for _ in range(5):
print(next(color_cycle))
```
out put:
```
red
green
blue
red
green
```
example of combinations():
```
import itertools
numbers = [1, 2, 3]
combinations = itertools.combinations(numbers, 2)
for combination in combinations:
print(combination)
```
output:
```
(1, 2)
(1, 3)
(2, 3)
```
Here are a few examples of how you can utilize itertools in the context of Django. In the following example, we use
itertools.chain() to combine the querysets of posts and comments into a single iterable. We then sort the combined
results based on the creation date, using sorted().
```
import itertools
from myapp.models import Post, Comment
posts = Post.objects.filter(published=True)
comments = Comment.objects.filter(approved=True)
combined_results = itertools.chain(posts, comments)
sorted_results = sorted(combined_results, key=lambda item: item.created_at, reverse=True)
```
### Lambda function
In Python, a **lambda** function is a small anonymous function that can be defined without a name. It is also known as an
inline function or a lambda expression. Lambda functions are typically used when you need a simple function that will be
used only once or as a parameter to another function. The general syntax of a lambda function in Python is:
```
lambda arguments: expression
```
For example, let's say we want to create a lambda function that takes two arguments and returns their sum:
```
add = lambda x, y: x + y
result = add(3, 5)
print(result) # Output: 8
```
Lambda functions are often used with built-in functions like **map()**, **filter()**, and **reduce()**. These functions take another
function as a parameter, and lambda functions provide a convenient way to define these functions on the fly without
explicitly defining a separate function. Here's an example using the **map()** function with a **lambda** function to square a
list of numbers:
```
numbers = [1, 2, 3, 4, 5]
squared_numbers = list(map(lambda x: x ** 2, numbers))
print(squared_numbers) # Output: [1, 4, 9, 16, 25]
```
For more complex or reusable functions, it's often better to define a regular named function using the **def** keyword.
### Exception Handling
Exception handling in Python allows you to gracefully handle and recover from runtime errors or exceptional conditions
that may occur during the execution of your program. Here's an example that demonstrates the usage of exception
handling:
```
try:
# Code that may raise an exception
x = 10 / 0 # division by zero raises a ZeroDivisionError
except ZeroDivisionError:
# Code to handle ZeroDivisionError
print("Division by zero is not allowed.")
# Output:
# Division by zero is not allowed.
```
### SOLID principles
The SOLID principles are a set of design principles that help in creating maintainable, scalable, and flexible software
systems.
- **Single Responsibility Principle (SRP):**
This principle states that a class should have only one responsibility. Let's say we have a **User** class that handles both user authentication and user data management.
Instead, we can separate these responsibilities into two classes: **Authenticator** and **UserManager**. Here's a Python
example:
```
class Authenticator:
def authenticate(self, username, password):
# Authentication logic here
class UserManager:
def create_user(self, user_data):
# User creation logic here
def delete_user(self, user_id):
# User deletion logic here
```
- **Open/Closed Principle (OCP):**
This principle states that software entities (classes, modules, functions) should be open for extension but closed for
modification. In other words, you should be able to add new functionality without modifying existing code. Let's say
we have a **Shape** class with different subclasses such as **Circle** and **Rectangle**. Instead of modifying the **Shape** class
every time we want to add a new shape, we can use **inheritance** and **polymorphism** to extend the functionality:
```
class Shape:
def area(self):
raise NotImplementedError()
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
```
- **Liskov Substitution Principle (LSP):**
This principle states that objects of a superclass should be replaceable with objects of its subclasses without
affecting the correctness of the program. Let's say we have a function that expects a **Shape** object. According to LSP,
we should be able to pass any subclass of **Shape** without causing any issues. In simpler terms, it means that a subclass should be able to be used wherever its superclass is expected, without causing any issues or breaking the functionality of the program.
Here's a simple Python example to illustrate the _Liskov Substitution Principle_:
```angular2html
class Shape:
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side * self.side
```
In this example, we have a superclass called **Shape** with a method **area()** that calculates the area of a shape. We then have two subclasses, **Rectangle** and **Square**, that inherit from the **Shape** class and override the **area()** method to calculate the area specific to each shape.
According to the _Liskov Substitution Principle_, we should be able to use objects of the **Rectangle** and **Square** classes interchangeably with objects of the Shape class. For example:
```angular2html
def print_area(shape):
print(f"The area is: {shape.area()}")
rectangle = Rectangle(4, 5)
square = Square(4)
print_area(rectangle) # Output: The area is: 20
print_area(square) # Output: The area is: 16
```
In this example, we can see that both the **Rectangle** and **Square** objects can be passed to the **print_area()** function, which expects an object of the **Shape** class. This demonstrates the _Liskov Substitution Principle_, as the subclasses can be used in place of the superclass without any issues or breaking the functionality of the program.
- **Interface Segregation Principle (ISP):**
This principle states that clients should not be forced to depend on interfaces they do not use. It promotes the idea of
having smaller, focused interfaces rather than large, general-purpose ones. Let's say we have an **Animal** interface with
methods like **walk(), swim(), and fly()**. Instead of having a single interface **Animal**, we can split it into smaller interfaces
based on functionality:
```
class Walker:
def walk(self):
raise NotImplementedError()
class Swimmer:
def swim(self):
raise NotImplementedError()
class Flyer:
def fly(self):
raise NotImplementedError()
class Dog(Walker):
def walk(self):
print("Dog is walking")
class Fish(Swimmer):
def swim(self):
print("Fish is swimming")
```
- **Dependency Inversion Principle (DIP):**
This principle states that high-level modules should not depend on low-level modules. Both should depend on
abstractions. It encourages the use of interfaces or abstract classes to decouple dependencies. . This helps to decouple the high-level and low-level modules, making it easier to change the low-level ones without affecting the high-level ones
### Python collection
Python provides several built-in collection types, such as **lists, tuples, sets, and dictionaries**. These collections can
be used to store and organize data efficiently.
- **lists**:
A list is a mutable collection that can store an ordered sequence of elements. It is defined using square
brackets (**[]**). Here's an example:
```
# Creating a list
fruits = ['apple', 'banana', 'orange']
# Accessing elements
print(fruits[0]) # Output: apple
# Modifying elements
fruits[1] = 'kiwi'
print(fruits) # Output: ['apple', 'kiwi', 'orange']
# Adding elements
fruits.append('grape')
print(fruits) # Output: ['apple', 'kiwi', 'orange', 'grape']
# Removing elements
fruits.remove('kiwi')
print(fruits) # Output: ['apple', 'orange', 'grape']
```
- **Tuples**:
A tuple is an immutable collection that can store an ordered sequence of elements. It is defined using
parentheses (). Here's an example:
```
# Creating a tuple
point = (3, 4)
# Accessing elements
print(point[0]) # Output: 3
# Unpacking tuple
x, y = point
print(x, y) # Output: 3 4
```
- **Sets**:
A set is an unordered collection that stores unique elements. It is defined using curly braces ({}) or the set()
function. Here's an example:
```
# Creating a set
numbers = {1, 2, 3, 4}
# Adding elements
numbers.add(5)
print(numbers) # Output: {1, 2, 3, 4, 5}
# Removing elements
numbers.remove(2)
print(numbers) # Output: {1, 3, 4, 5}
```
- **Dictionaries**:
A dictionary is a collection that stores **key-value** pairs. It is defined using curly braces ({}) and colons (:). Here's
an example:
```
# Creating a dictionary
student = {
'name': 'Alice',
'age': 20,
'university': 'XYZ'
}
# Accessing values
print(student['name']) # Output: Alice
# Modifying values
student['age'] = 21
print(student) # Output: {'name': 'Alice', 'age': 21, 'university': 'XYZ'}
# Adding new key-value pair
student['major'] = 'Computer Science'
print(student) # Output: {'name': 'Alice', 'age': 21, 'university': 'XYZ', 'major': 'Computer Science'}
# Removing key-value pair
del student['university']
print(student) # Output: {'name': 'Alice', 'age': 21, 'major': 'Computer Science'}
# Updating a field
student.update({'age': 21})
```
### frozenset in Python
In Python, a **frozenset** is an immutable (unchangeable) version of the built-in set type. It is an unordered collection of unique elements, just like a regular set, but it cannot be modified once created. This means you cannot add, remove, or modify elements in a frozenset after it is created.
Here's an example of creating and utilizing a frozenset:
```angular2html
# Create a frozenset
numbers = frozenset([1, 2, 3, 4, 5])
# Accessing elements
for number in numbers:
print(number)
# Frozenset operations
other_numbers = frozenset([4, 5, 6, 7, 8])
# Union
union = numbers.union(other_numbers)
print(union) # Output: frozenset({1, 2, 3, 4, 5, 6, 7, 8})
# Intersection
intersection = numbers.intersection(other_numbers)
print(intersection) # Output: frozenset({4, 5})
# Difference
difference = numbers.difference(other_numbers)
print(difference) # Output: frozenset({1, 2, 3})
# Subset check
is_subset = numbers.issubset(other_numbers)
print(is_subset) # Output: False
```
### Generators and Iterators
Generators and iterators are powerful constructs in Python used for efficient iteration and lazy evaluation. They
provide a way to generate or yield values on the fly, enabling you to work with large or infinite sequences without
loading everything into memory at once.
- Lazy file reading using generators:
```
def read_lines(filename):
with open(filename, 'r') as file:
for line in file:
yield line.strip()
lines = read_lines('large_file.txt')
for line in lines:
print(line)
```
Here, the **read_lines()** function returns a generator that yields one line at a time. This approach is memory-efficient
and allows you to process large files without loading the entire contents into memory.
Here is the implementation of the previous example with custom iterator:
```
class LineIterator:
def __init__(self, filename):
self.filename = filename
self.file = None
def __iter__(self):
self.file = open(self.filename, 'r')
return self
def __next__(self):
line = self.file.readline().strip()
if not line:
self.file.close()
raise StopIteration
return line
lines = LineIterator('large_file.txt')
for line in lines:
print(line)
```
The main advantage of the generator approach is its simplicity and conciseness. The generator function hides most of the
implementation details required for iterators, resulting in cleaner code. On the other hand, the iterator approach
provides more control and flexibility, allowing for more complex iterator implementations beyond what generators can
offer.
Ultimately, the choice between generators and iterators depends on the specific requirements of your program. Generators
are often the preferred choice for simpler iterations and lazy evaluation, while iterators offer more customization and
control when dealing with complex iteration scenarios.
### Magic methods
Magic methods, also known as dunder methods, are special methods in Python that start and end with double underscores.
They are not meant to be invoked directly by the user, but are called internally by the class on certain actions.
Here are some examples of commonly used magic methods in Python:
`__new__(self)`: It is a static method that is called before the `__init__` method when creating an object. The primary purpose of **__new__** is to handle the object construction process and return an instance of the class.
`__init__(self, ...)` : This method is called when an object is created and initialized. It takes arguments that are
used to initialize the object's attributes.
`__str__(self)` : This method is called when the object is printed as a string. It returns a string representation of
the object.
`__len__(self)` : This method is called when the built-in **len()** function is called on the object. It returns the length
of the object.
`__add__(self, other)` : This method is called when the + operator is used on the object. It takes another object as an
argument and returns the result of the addition.
`__call__(self, other)`: The method in Python is a special method that allows an object to be called like a function
Here's an example of a class that defines some of these magic methods:
```
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"{self.name} is {self.age} years old."
def __repr__(self):
return f"Person('{self.name}', {self.age})"
def __eq__(self, other):
return self.name == other.name and self.age == other.age
```
Here is a simple example that demonstrates the difference between __str__ and __repr__:
```angular2html
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"{self.name} ({self.age})"
def __repr__(self):
return f"Person(name='{self.name}', age={self.age})"
person = Person("Alice", 30)
print(str(person)) # Output: Alice (30)
print(repr(person)) # Output: Person(name='Alice', age=30)
```
In this example, we define a Person class that has a **name** and an **age** attribute. We define both __str__ and __repr__ methods for the class. The __str__ method returns a string that is intended to be readable by humans, while the __repr__ method returns a string that is intended to be unambiguous and can be used to recreate the object.
When we create a Person object and print it using the **str()** and **repr()** functions, we get different outputs. The **str()** function calls the __str__ method, which returns a user-friendly string representation of the object. The **repr()** function calls the __repr__ method, which returns a developer-friendly string representation of the object.
### GIL
The **_Global Interpreter Lock_** is a mechanism in _CPython_ (the most common implementation of Python) that serves to serialize access to Python objects, _preventing multiple threads from executing Python bytecodes at once_.
In simple words, the GIL is a mutex (or a lock) that allows only one thread to hold the control of the Python interpreter.This means that _only one thread can be in a state of execution at any point in time_.
### concurrency and parallelism
**Concurrency** involves allowing multiple tasks to take turns accessing the same shared resources, like disk, network, or a
single CPU core. **Parallelism**, on the other hand, is about maximizing the use of hardware resources, such as multiple CPU
cores, to execute multiple tasks simultaneously. **Threading** is a way to achieve concurrency by running multiple threads
within a single process, while **asyncio** is a way to achieve concurrency by running a single thread that can switch
between multiple tasks.
- **Threading**:
The following code snippet demonstrates how to use threading to execute multiple tasks concurrently within a single
process.
```
import threading
def task1():
# do some work
def task2():
# do some work
# create two threads to execute the tasks concurrently
t1 = threading.Thread(target=task1)
t2 = threading.Thread(target=task2)
# start the threads
t1.start()
t2.start()
# wait for the threads to finish
t1.join()
t2.join()
```
- **Asyncio**:
The following code snippet demonstrates how to use asyncio to execute multiple tasks concurrently within a single
thread:
```
import asyncio
async def task1():
# do some work
async def task2():
# do some work
# create an event loop
loop = asyncio.get_event_loop()
# execute the tasks concurrently within the event loop
loop.run_until_complete(asyncio.gather(task1(), task2()))
# close the event loop
loop.close()
```
- **Multiprocessing**:
The following code snippet demonstrates how to use multiprocessing to execute multiple tasks in parallel across
multiple processes:
```
import multiprocessing
def task1():
# do some work
def task2():
# do some work
# create two processes to execute the tasks in parallel
p1 = multiprocessing.Process(target=task1)
p2 = multiprocessing.Process(target=task2)
# start the processes
p1.start()
p2.start()
# wait for the processes to finish
p1.join()
p2.join()
```
In general, **concurrency** is preferred for **IO-bound** tasks, as it allows you to do something else while the IO resources
are being fetched. **Parallelism**, on the other hand, is preferred for **CPU-bound** tasks, as it allows you to take advantage
of multiple CPU cores to execute multiple tasks simultaneously.
### Main types of methods in python classes
In Python, there are three main types of methods that can be defined in a class:
1. **Instance Methods:**
Instance methods are the most common type of methods used in Python classes. They are defined using the **self**
parameter as the first argument. Instance methods can access and modify the instance attributes of the class.
```
class MyClass:
def my_instance_method(self, x, y):
self.x = x
self.y = y
return self.x + self.y
my_object = MyClass()
result = my_object.my_instance_method(3, 4)
print(result) # Output: 7
```
2. **Class Methods**:
Class methods are methods that are bound to the class and not the instance of the class. They are defined using the \*
**@classmethod** decorator, and they take the class itself as their first argument, typically named **cls**. Class
methods can be called on the class itself, rather than on an instance of the class.
```
class MyClass:
class_attribute = 0
@classmethod
def my_class_method(cls, x, y):
cls.class_attribute += 1
return cls.class_attribute + x + y
result1 = MyClass.my_class_method(3, 4)
print(result1) # Output: 1 + 3 + 4 = 8
result2 = MyClass.my_class_method(1, 2)
print(result2) # Output: 2 + 1 + 2 = 5
```
3. **Static Methods:**
Static methods are methods that don't depend on the class or instance. They are defined using the **@staticmethod**
decorator and take no special first argument. Static methods are typically used for utility functions that don't
require access to the class or instance.
```
class MyClass:
@staticmethod
def my_static_method(x, y):
return x + y
result = MyClass.my_static_method(3, 4)
print(result) # Output: 7
```
### Data serialization
Data serialization is the process of converting structured data into a format that allows sharing or storage of the data in a form that allows recovery of its original structure. In Python, there are several built-in modules for data serialization, including **pickle**, **json**, and **marshal**.
**Note** that the marshal module is not suitable for serializing data that needs to be exchanged between different programming languages or platforms, as the binary format is specific to Python. For that purpose, you may want to consider using a different serialization library such as JSON, pickle, or protobuf.
Here's an example of using the **pickle** module to serialize and deserialize a Python object:
```
import pickle
# Define a Python object
grades = {'Alice': 89, 'Bob': 72, 'Charles': 87}
# Serialize the object to a byte stream
serial_grades = pickle.dumps(grades)
# Deserialize the byte stream back into a Python object
received_grades = pickle.loads(serial_grades)
# Print the original and received objects
print('Original object:', grades)
print('Serialized object:', serial_grades)
print('Received object:', received_grades)
# Out put
# Original object: {'Alice': 89, 'Bob': 72, 'Charles': 87}
# Serialized object: b'\x80\x04\x95#\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x05Alice\x94KY\x8c\x03Bob\x94KH\x8c\x07Charles\x94KWu.'
# Received object: {'Alice': 89, 'Bob': 72, 'Charles': 87}
```
In this example, we define a Python dictionary grades and serialize it using the **pickle.dumps()** function. We then deserialize the byte stream back into a Python object using the **pickle.loads()** function and print the original and received objects.
### Data class in python
Overall, data classes provide a convenient way to define classes that mainly hold data values, without having to write boilerplate code for initialization, representation, and comparisons.
A **data class** is a class that is designed to only hold data values. It is similar to a regular class, but it usually doesn't have any other methods. It is typically used to store information that will be passed between different parts of a program or a system.
Here's a simple example of a data class in Python:
```angular2html
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
```
In the above example, the **@dataclass** decorator is used to create a data class called **Person**. The class has two attributes, name and age, which are defined using type annotations. The dataclass decorator automatically generates several special methods such as __init__(), __repr__(), and __eq__() for the class.
To add custom validation to the example using dataclasses, you can define a custom **__post_init__** method that runs after the object is initialized. Within this method, you can perform your custom validation logic. Here's how you can do it:
```
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
def __post_init__(self):
if self.age < 18:
raise ValueError("Age must be greater than or equal to 18")
```
### Shallow copy and deep copy
In Python, there are two types of copying: **shallow copy** and **deep copy**. A **shallow copy** creates a new object that stores references to the child objects of the original object. In contrast, a **deep copy** creates a new object that is completely independent of the original object.
To create a shallow copy of an object, we can use the copy method provided by the copy module in Python. The copy method returns a shallow copy of the object. For example:
```angular2html
import copy
list1 = [1, 2, [3, 4]]
list2 = copy.copy(list1)
print(list1) # [1, 2, [3, 4]]
print(list2) # [1, 2, [3, 4]]
list2[2][0] = 5
print(list1) # [1, 2, [5, 4]]
print(list2) # [1, 2, [5, 4]]
```
In this example, we create a shallow copy of **list1** using the copy method. When we modify the nested list in **list2**, the same change is reflected in **list1** because both lists share the same reference to the nested list.
To create a deep copy of an object, we can use the deepcopy method provided by the copy module. The deepcopy method returns a deep copy of the object. For example:
```angular2html
import copy
list1 = [1, 2, [3, 4]]
list2 = copy.deepcopy(list1)
print(list1) # [1, 2, [3, 4]]
print(list2) # [1, 2, [3, 4]]
list2[2][0] = 5
print(list1) # [1, 2, [3, 4]]
print(list2) # [1, 2, [5, 4]]
```
In this example, we create a deep copy of **list1** using the deepcopy method. When we modify the nested list in **list2**, the change is not reflected in **list1** because both lists have independent copies of the nested list.
### Local and global variables
In summary, _local variables_ are declared inside a function or method and can only be accessed within that specific block, while _global variables_ are declared outside any function or method and can be accessed throughout the program and inside every function. To modify a global variable inside a function, we need to use the **global** keyword.
Here are some simple examples of local and global variables in Python:
```angular2html
# Example of a local variable
def my_function():
x = 5
print(x)
my_function() # Output: 5
# Example of a global variable
y = 10
def my_function():
print(y)
my_function() # Output: 10
```
In this example, **x** is a local variable that is declared inside the **my_function** function and can only be accessed within that function. **y**, on the other hand, is a global variable that is declared outside any function and can be accessed inside the **my_function** function.
To modify a global variable inside a function, we need to use the **global** keyword. Here's an example:
```angular2html
# Example of modifying a global variable inside a function
z = 15
def my_function():
global z
z = 20
my_function()
print(z) # Output: 20
```
### Comprehension
Comprehension is a concise and efficient way to create lists, dictionaries, and sets in Python. Multiple and nested comprehensions can be used to create complex data structures in a single line of code.
Here's a very simple example of list comprehension:
```angular2html
# Example of list comprehension
my_list = [x for x in range(10)]
print(my_list) # Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
```
In this example, we use list comprehension to create a list of numbers from 0 to 9. The list comprehension is enclosed in square brackets and consists of an expression x followed by a for loop that iterates over a range of numbers from 0 to 9.
Here's a very simple example of nested list comprehension:
```angular2html
# Example of nested list comprehension
my_list = [[x*y for y in range(5)] for x in range(5)]
print(my_list) # Output: [[0, 0, 0, 0, 0], [0, 1, 2, 3, 4], [0, 2, 4, 6, 8], [0, 3, 6, 9, 12], [0, 4, 8, 12, 16]]
```
In this example, we use nested list comprehension to create a list of multiplication tables for numbers from 0 to 4. The outer list comprehension iterates over numbers from 0 to 4, and the inner list comprehension iterates over numbers from 0 to 4 to create a list of products.
### Pydantic
Pydantic is a Python package that provides data validation and settings management using Python type annotations. It is a lightweight and flexible package that can be used to validate and parse data from various sources such as JSON, YAML, and databases.
Here's a simple example of using Pydantic to define a data model and validate input data:
```angular2html
from pydantic import BaseModel
class Person(BaseModel):
name: str
age: int
person_data = {
"name": "John Doe",
"age": 30
}
person = Person(**person_data)
print(person)
```
To add custom validation to the example using Pydantic, you can use Pydantic's validation decorators like @validator. These decorators allow you to define custom validation functions that run when the model is being initialized. Here's how you can add custom validation to the Person model to ensure that the age is greater than or equal to 18:
```
from pydantic import BaseModel, validator
class Person(BaseModel):
name: str
age: int
@validator("age")
def validate_age(cls, value):
if value < 18:
raise ValueError("Age must be greater than or equal to 18")
return value
person_data = {
"name": "John Doe",
"age": 30
}
```
In this example, we define a custom validation function **validate_age** using the **@validator** decorator. This function checks if the age value is less than 18, and if so, raises a **ValueError** with a custom error message. If the validation passes, the function returns the value as it is.
In this example, we define a Pydantic data model Person that has two fields: name of type str and age of type int.
Pydantic automatically validates the input data against the data model and raises a **ValueError** if the data is invalid. If the data is valid, Pydantic creates a new Person object with the validated data and assigns it to the person variable. We then print the person object to verify that it was created correctly.
- Pydantic vs Data Classes:
In summary, Pydantic is more suitable when you need robust data validation, type conversion, and serialization capabilities. It's often used in scenarios where data integrity and consistency are critical, such as in web APIs or data validation pipelines. On the other hand, data classes are a simpler and more lightweight choice for defining basic data structures or DTOs (Data Transfer Objects) where validation and serialization are not primary concerns. Your choice between the two depends on your specific requirements and the complexity of your data handling needs.
### Args and Kwargs in Python
In Python, ***args** and ****kwargs** are used to pass a variable number of arguments to a function. They allow you to handle arbitrary numbers of positional and keyword arguments, respectively.
__args__ is used to pass a variable number of positional arguments to a function. It allows you to pass any number of arguments to a function without explicitly specifying them in the function definition. The arguments passed using *args are collected into a tuple within the function.
```angular2html
def print_args(*args):
for arg in args:
print(arg)
print_args('Hello', 'World', '!')
```
Output:
```angular2html
Hello
World
!
```
****kwargs** is used to pass a variable number of keyword arguments to a function. It allows you to pass key-value pairs as arguments to a function without explicitly specifying them in the function definition. The arguments passed using ****kwargs** are collected into a dictionary within the function.
Here's a simple example:
```angular2html
def print_kwargs(**kwargs):
for key, value in kwargs.items():
print(key, value)
print_kwargs(name='John', age=25, city='New York')
```
Output:
```angular2html
name John
age 25
city New York
```
In this example, the **print_kwargs()** function takes any number of keyword arguments using ****kwargs**. The key-value pairs are then printed one by one using a loop.
You can also combine ***args** and ****kwargs** in a function definition to accept both positional and keyword arguments
### Operator overloading
Operator overloading in Python refers to the ability to redefine the behavior of built-in operators **(+, -, *, /, etc.)** for user-defined classes.
This feature provides flexibility and allows you to make your classes work with operators in a way that makes sense for your specific use case.
Here's a simple example to illustrate operator overloading using the **+** operator:
```angular2html
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
new_x = self.x + other.x
new_y = self.y + other.y
return Point(new_x, new_y)
point1 = Point(2, 3)
point2 = Point(4, 5)
result = point1 + point2
print(result.x, result.y) # Output: 6 8
```
When we use the "+" operator between point1 and point2, it calls the **__add__()** method behind the scenes, allowing us to perform the addition operation in a customized way. The result is a new Point object with the summed coordinates.
### Recursive function
A recursive function in Python is a function that calls itself during its execution. It is a powerful technique used to solve problems by breaking them down into smaller, more manageable subproblems. Here's a simple example of a recursive function to calculate the factorial of a number:
```angular2html
def factorial(n):
if n == 0 or n == 1:
return 1
else:
return n * factorial(n - 1)
result = factorial(5)
print(result) # Output: 120
```
Recursive functions are particularly useful when solving problems that can be divided into smaller subproblems of the same nature. They provide an elegant and concise way to express repetitive computations and can simplify complex algorithms.
However, it's important to ensure that recursive functions have proper termination conditions to avoid infinite recursion. Recursive functions can consume more memory and may have performance implications compared to iterative solutions, so they should be used judiciously based on the problem at hand.
### Context manager
In Python, a context manager is an object that defines the methods **__enter__** and **__exit__**.
It allows you to manage resources, such as files or database connections, in a structured and efficient manner. The **with** statement is used to create a context, and the context manager ensures that the resources are properly set up and cleaned up, even if an exception occurs.
When you use the context manager with the **with** statement, the **__enter__** method is called at the beginning of the block, and the **__exit__** method is called at the end, regardless of whether an exception occurred or not. This ensures that resources are properly managed and cleaned up.
Let's consider a practical example of a context manager for working with files. We'll create a context manager that opens a file, performs some operations, and automatically closes the file when exiting the context.
```
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_value, traceback):
self.file.close()
# Using the file manager context manager with the 'with' statement
with FileManager("example.txt", "w") as file:
file.write("Hello, World!")
# File is automatically closed outside the context
```
In this example, the **FileManager** class is a context manager that takes a **filename** and a **mode** (such as 'r', 'w', or 'a') as parameters. In the **__enter__** method, it opens the file in the specified mode and returns the file object. This allows us to work with the file within the with block.
The **__exit__** method is responsible for closing the file when exiting the context. Regardless of whether an exception occurs or not, the **__exit__** method will be called, ensuring that the file is closed properly.
### Python 3.11 over previous versions
Here are some simple examples of the advantages of using Python 3.11 over previous versions:
- Fine-grained error locations in tracebacks:
When an error occurs, Python 3.11 will point to the exact expression that caused the error, instead of just the line. For example, if you have a syntax error in a long line of code, Python 3.11 will point to the exact part of the line that caused the error, making it easier to debug.
- Task and exception groups that simplify working with asynchronous code:
Task groups provide a cleaner syntax for running and monitoring asynchronous tasks. For example, you can use task groups to run multiple tasks concurrently and wait for them to complete before continuing.
- Faster code execution:
Python 3.11 is faster than previous versions, which means that your Python programs will run faster. For example, if you have a program that performs a lot of computations, you will notice a significant speed improvement in Python 3.11.
- A new built-in library for working with TOML files:
Python 3.11 includes a new built-in library for working with TOML files, which is a popular configuration file format. For example, you can use the "toml" module to read and write TOML files in your Python programs.
These are just a few simple examples of the advantages of using Python 3.11 over previous versions
### Semaphores and Mutexes
Semaphores and Mutexes are synchronization mechanisms used in concurrent programming to control access to shared resources and avoid race conditions. Here, I'll explain the concepts of Semaphore and Mutex in Python
- Mutex (Mutual Exclusion):
A Mutex is a synchronization primitive that allows only one thread to access a shared resource at a time. It ensures that only one thread can acquire the mutex and access the protected resource, while other threads must wait until the mutex is released.
Here's a simple Python example using the **threading** module:
```
import threading
# Create a mutex
mutex = threading.Lock()
shared_variable = 0
def increment_shared_variable():
global shared_variable
with mutex:
shared_variable += 1
# Create multiple threads to increment the shared variable
threads = []
for _ in range(5):
thread = threading.Thread(target=increment_shared_variable)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
print("Shared variable:", shared_variable)
```
In this example, the **mutex** ensures that only one thread can execute the critical section (incrementing **shared_variable**) at a time, preventing **race conditions**.
- Semaphore:
A Semaphore is a synchronization primitive that allows a fixed number of threads to access a resource simultaneously. It maintains a count, and threads can acquire or release the semaphore based on this count.
Here's a simple Python example using the **threading** module:
```angular2html
import threading
# Create a semaphore with a maximum of 2 permits
semaphore = threading.Semaphore(2)
def access_shared_resource(thread_id):
semaphore.acquire()
print(f"Thread {thread_id} is accessing the shared resource.")
# Simulate some work
threading.Event().wait()
print(f"Thread {thread_id} is releasing the shared resource.")
semaphore.release()
# Create multiple threads to access the shared resource
threads = []
for i in range(5):
thread = threading.Thread(target=access_shared_resource, args=(i,))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
```
In this example, the semaphore allows up to 2 threads to access the shared resource concurrently, while other threads will wait until a permit is released.
### Python built-in functions
Here are some advanced Python built-in functions with simple examples:
- `abs()` - returns the absolute value of a number
```angular2html
print(abs(-5)) # Output: 5
```
- `all()` - returns True if all elements in an iterable are true
```angular2html
print(all([True, True, False])) # Output: False
```
- `any()` - returns True if any element in an iterable is true
```angular2html
print(any([True, True, False])) # Output: True
```
- `bin()` - converts an integer to a binary string
```angular2html
print(bin(10)) # Output: 0b1010
```
- `enumerate()` - returns an iterator that generates tuples containing indices and values from an iterable
```angular2html
fruits = ['apple', 'banana', 'cherry']
for index, value in enumerate(fruits):
print(index, value)
# Output:
# 0 apple
# 1 banana
# 2 cherry
```
- `map()` - returns an iterator that generates the results of applying a function to the elements of an iterable
```angular2html
def square(num):
return num ** 2
numbers = [1, 2, 3, 4, 5]
squared_numbers = list(map(square, numbers))
print(squared_numbers) # Output: [1, 4, 9, 16, 25]
```
- `max()` - returns the largest element in an iterable or the largest of two or more arguments
```angular2html
print(max([1, 2, 3])) # Output: 3
```
- `pow()` - returns the result of raising a number to a power
```
print(pow(2, 3)) # Output: 8
```
- `reversed()` - returns an iterator that generates the elements of a sequence in reverse order
```
fruits = ['apple', 'banana', 'cherry']
for fruit in reversed(fruits):
print(fruit)
# Output:
# cherry
# banana
# apple
```
- `round()` - rounds a number to a specified number of decimal places
```
print(round(3.14159, 2)) # Output: 3.14
```
- `zip()` - returns an iterator that generates tuples by aggregating the elements of several iterables
```
fruits = ['apple', 'banana', 'cherry']
prices = [1.0, 2.0, 3.0]
for fruit, price in zip(fruits, prices):
print(fruit, price)
# Output:
# apple 1.0
# banana 2.0
# cherry 3.0
```
- `format()` - formats a string using replacement fields
```
name = 'Alice'
age = 30
print('My name is {0} and I am {1} years old.'.format(name, age))
# Output: My name is Alice and I am 30 years old.
```
- `frozenset()` - returns an immutable frozenset object initialized with elements from the given iterable
```angular2html
vowels = ('a', 'e', 'i', 'o', 'u')
f_set = frozenset(vowels)
print(f_set) # Output: frozenset({'a', 'e', 'i', 'o', 'u'})
```
- `getattr()` - returns the value of a named attribute of an object
```
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person('Alice', 30)
print(getattr(person, 'name')) # Output: 'Alice'
```
- `hash()` - returns the hash value of an object
```angular2html
print(hash('hello')) # Output: -3557965013271865832
```
- `isinstance()` - returns True if an object is an instance of a specified class
```angular2html
class Person:
pass
person = Person()
print(isinstance(person, Person)) # Output: True
```
- `issubclass()` - returns True if a class is a subclass of a specified class
```angular2html
class Animal:
pass
class Dog(Animal):
pass
print(issubclass(Dog, Animal)) # Output: True
```
- `setattr()` - sets the value of a named attribute of an object
```
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person('Alice', 30)
setattr(person, 'age', 31)
print(person.age) # Output: 31
```
- `delattr()` - deletes a named attribute from an object
```
class Person:
name = 'Alice'
person = Person()
delattr(person, 'name')
print(hasattr(person, 'name')) # Output: False
```
- `open()` - opens a file and returns a file object
```
file = open('example.txt', 'r')
print(file.read()) # Output: This is an example file.
file.close()
```
- `slice()` - returns a slice object
```
my_list = [1, 2, 3, 4, 5]
my_slice = slice(1, 4)
print(my_list[my_slice]) # Output: [2, 3, 4]
```
### Type Hints and Type Checking
Type hints in Python allow you to specify the expected types of variables, function parameters, and return values. This improves code readability, enables better IDE support, and allows static type checking with tools like `mypy`.
**Basic Type Hints:**
```python
from typing import List, Dict, Optional, Union, Tuple
# Function with type hints
def greet(name: str, age: int) -> str:
return f"Hello, {name}. You are {age} years old."
# Variables with type hints
count: int = 10
name: str = "Alice"
is_active: bool = True
# Collections
numbers: List[int] = [1, 2, 3, 4, 5]
user_data: Dict[str, Union[str, int]] = {"name": "John", "age": 30}
```
**Optional and Union Types:**
```python
from typing import Optional, Union
def find_user(user_id: int) -> Optional[Dict[str, str]]:
# Returns Dict or None
if user_id > 0:
return {"id": str(user_id), "name": "John"}
return None
def process_data(data: Union[str, int, float]) -> str:
return str(data)
```
**Use Cases:**
1. **Code Documentation:** Type hints serve as inline documentation
2. **IDE Support:** Better autocomplete and error detection
3. **Refactoring:** Safer refactoring with type checking
4. **Team Collaboration:** Clearer code contracts between team members
5. **Catching Bugs:** Static type checking can catch type-related errors before runtime
### Package Management
Effective package management is crucial for Python projects. Here are the main tools and practices:
**pip and requirements.txt:**
```python
# requirements.txt
Django==4.2.0
requests==2.31.0
pytest==7.4.0
# Install packages
# pip install -r requirements.txt
# Generate requirements.txt
# pip freeze > requirements.txt
```
**Poetry (Modern Package Management):**
```bash
# Install Poetry
# curl -sSL https://install.python-poetry.org | python3 -
# Create a new project
poetry new myproject
# Add dependencies
poetry add django
poetry add pytest --dev
# Install dependencies
poetry install
# Update dependencies
poetry update
```
**pyproject.toml (Poetry configuration):**
```toml
[tool.poetry]
name = "myproject"
version = "0.1.0"
description = ""
[tool.poetry.dependencies]
python = "^3.9"
django = "^4.2.0"
requests = "^2.31.0"
[tool.poetry.dev-dependencies]
pytest = "^7.4.0"
black = "^23.0.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
```
**Virtual Environments:**
```bash
# Create virtual environment
python -m venv venv
# Activate (Windows)
venv\Scripts\activate
# Activate (Linux/Mac)
source venv/bin/activate
# Deactivate
deactivate
```
**Best Practices:**
1. **Always use virtual environments** for project isolation
2. **Pin dependency versions** in production (use `==` instead of `>=`)
3. **Separate dev dependencies** from production dependencies
4. **Use poetry or pip-tools** for better dependency resolution
5. **Regularly update dependencies** to get security patches
### functools Module
The `functools` module provides higher-order functions and operations on callable objects.
**partial (Partial Application):**
```python
from functools import partial
def multiply(x, y, z):
return x * y * z
# Create a new function with some arguments pre-filled
multiply_by_2 = partial(multiply, 2)
result = multiply_by_2(3, 4) # Equivalent to multiply(2, 3, 4)
print(result) # Output: 24
```
**reduce (Functional Reduction):**
```python
from functools import reduce
numbers = [1, 2, 3, 4, 5]
product = reduce(lambda x, y: x * y, numbers)
print(product) # Output: 120
# Equivalent to:
result = 1
for num in numbers:
result *= num
```
**Use Cases:**
1. **Performance Optimization:** Cache expensive function calls
2. **Function Composition:** Create specialized functions from general ones
3. **Decorator Development:** Preserve function metadata in decorators
4. **Functional Programming:** Apply functional patterns
### Advanced Collections
Python's `collections` module provides specialized container datatypes.
**defaultdict (Default Dictionary):**
```python
from collections import defaultdict
# Regular dict - raises KeyError for missing keys
regular_dict = {}
# regular_dict['missing'] # KeyError
# defaultdict - returns default value
dd = defaultdict(list)
dd['fruits'].append('apple')
dd['fruits'].append('banana')
print(dd['vegetables']) # Output: [] (empty list, not KeyError)
```
**Counter (Counting Elements):**
```python
from collections import Counter
# Count occurrences
words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
counter = Counter(words)
print(counter) # Output: Counter({'apple': 3, 'banana': 2, 'cherry': 1})
# Most common
print(counter.most_common(2)) # Output: [('apple', 3), ('banana', 2)]
```
**OrderedDict (Remember Insertion Order):**
```python
from collections import OrderedDict
# In Python 3.7+, regular dicts maintain order, but OrderedDict has extra features
od = OrderedDict()
od['first'] = 1
od['second'] = 2
od['third'] = 3
# Move to end
od.move_to_end('first')
print(list(od.keys())) # Output: ['second', 'third', 'first']
```
**deque (Double-Ended Queue):**
```python
from collections import deque
# Efficient append/pop from both ends
dq = deque([1, 2, 3])
dq.appendleft(0) # Add to left
dq.append(4) # Add to right
print(dq) # Output: deque([0, 1, 2, 3, 4])
dq.popleft() # Remove from left
dq.pop() # Remove from right
```
**ChainMap (Multiple Dictionaries):**
```python
from collections import ChainMap
dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'd': 4}
dict3 = {'b': 5, 'e': 6}
# Chain maps - searches in order
chain = ChainMap(dict1, dict2, dict3)
print(chain['b']) # Output: 2 (from dict1, first match)
print(chain['c']) # Output: 3 (from dict2)
```
**Use Cases:**
1. **Data Grouping:** defaultdict for grouping operations
2. **Frequency Analysis:** Counter for counting occurrences
3. **Queue Operations:** deque for efficient queue/stack operations
4. **Configuration Management:** ChainMap for layered configurations
### Enum and NamedTuple
**Enum (Enumerations):**
```python
from enum import Enum, auto
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
# Usage
print(Color.RED) # Output: Color.RED
print(Color.RED.value) # Output: 1
print(Color.RED.name) # Output: RED
# Auto values
class Status(Enum):
PENDING = auto()
PROCESSING = auto()
COMPLETED = auto()
# String enum
class HttpMethod(str, Enum):
GET = "GET"
POST = "POST"
PUT = "PUT"
DELETE = "DELETE"
```
**NamedTuple (Tuple with Named Fields):**
```python
from collections import namedtuple
# Define a named tuple
Point = namedtuple('Point', ['x', 'y'])
# Create instances
p1 = Point(1, 2)
p2 = Point(x=3, y=4)
print(p1.x, p1.y) # Output: 1 2
print(p1[0]) # Output: 1 (still indexable)
# With type hints (Python 3.6+)
from typing import NamedTuple
class Point(NamedTuple):
x: int
y: int
p = Point(1, 2)
print(p.x, p.y) # Output: 1 2
```
**Use Cases:**
1. **Constants:** Enum for fixed set of values
2. **Status Codes:** Represent states with enums
3. **Data Structures:** NamedTuple for simple data containers
4. **API Design:** Use enums for method types, status codes
### asyncio (Advanced)
Advanced asyncio patterns for complex asynchronous programming.
**Async Context Managers:**
```python
import asyncio
class AsyncContextManager:
async def __aenter__(self):
print("Entering async context")
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
print("Exiting async context")
return False
async def main():
async with AsyncContextManager() as acm:
print("Inside async context")
await asyncio.sleep(1)
asyncio.run(main())
```
**Async Generators:**
```python
async def async_range(n):
for i in range(n):
await asyncio.sleep(0.1)
yield i
async def main():
async for value in async_range(5):
print(value)
asyncio.run(main())
```
**Task Groups (Python 3.11+):**
```python
async def fetch_data(url):
await asyncio.sleep(0.1)
return f"Data from {url}"
async def main():
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(fetch_data("url1"))
task2 = tg.create_task(fetch_data("url2"))
task3 = tg.create_task(fetch_data("url3"))
# All tasks completed
print(task1.result(), task2.result(), task3.result())
asyncio.run(main())
```
**Use Cases:**
1. **Concurrent I/O:** Handle multiple network requests simultaneously
2. **Streaming Data:** Process data streams asynchronously
3. **Real-time Applications:** WebSockets, chat applications
4. **Resource Management:** Async context managers for resources
### datetime Module
Comprehensive date and time handling.
**Basic Operations:**
```python
from datetime import datetime, date, time, timedelta
# Current date and time
now = datetime.now()
print(now) # Output: 2024-01-15 10:30:45.123456
# Create specific date/time
dt = datetime(2024, 1, 15, 10, 30, 45)
print(dt.date()) # Output: 2024-01-15
print(dt.time()) # Output: 10:30:45
# Formatting
formatted = dt.strftime('%Y-%m-%d %H:%M:%S')
print(formatted) # Output: 2024-01-15 10:30:45
# Parsing
parsed = datetime.strptime('2024-01-15', '%Y-%m-%d')
print(parsed) # Output: 2024-01-15 00:00:00
```
**Time Arithmetic:**
```python
from datetime import datetime, timedelta
now = datetime.now()
# Add/subtract time
future = now + timedelta(days=7, hours=3)
past = now - timedelta(weeks=2)
# Difference
diff = future - past
print(diff.days) # Output: 21
# Timezone aware
from datetime import timezone
utc_now = datetime.now(timezone.utc)
print(utc_now) # Output: 2024-01-15 10:30:45.123456+00:00
```
**Use Cases:**
1. **Date Calculations:** Calculate deadlines, durations
2. **Logging:** Timestamp events
3. **Scheduling:** Schedule tasks and reminders
4. **Data Analysis:** Time series data processing
### Abstract Base Classes (ABC) Advanced
Advanced usage of ABCs for interface definition.
**Multiple Abstract Methods:**
```python
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
def describe(self):
return f"Shape with area {self.area()} and perimeter {self.perimeter()}"
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
# Can't instantiate Shape directly
# shape = Shape() # TypeError
rect = Rectangle(5, 3)
print(rect.describe())
```
**Abstract Properties:**
```python
from abc import ABC, abstractproperty
class Animal(ABC):
@abstractproperty
def sound(self):
pass
class Dog(Animal):
@property
def sound(self):
return "Woof!"
dog = Dog()
print(dog.sound) # Output: Woof!
```
**Use Cases:**
1. **Interface Definition:** Define contracts for subclasses
2. **Framework Design:** Create extensible frameworks
3. **Type Checking:** Enable static type checking
4. **Documentation:** Document expected methods
### JSON vs Pickle Comparison
Understanding serialization options.
**JSON (Text-based, Portable):**
```python
import json
data = {'name': 'John', 'age': 30, 'scores': [85, 90, 95]}
# Serialize
json_str = json.dumps(data)
print(json_str) # Output: {"name": "John", "age": 30, "scores": [85, 90, 95]}
# Deserialize
loaded = json.loads(json_str)
print(loaded) # Output: {'name': 'John', 'age': 30, 'scores': [85, 90, 95]}
# File operations
with open('data.json', 'w') as f:
json.dump(data, f)
with open('data.json', 'r') as f:
loaded = json.load(f)
```
**Pickle (Binary, Python-specific):**
```python
import pickle
class CustomClass:
def __init__(self, value):
self.value = value
obj = CustomClass(42)
# Serialize
pickled = pickle.dumps(obj)
# Deserialize
unpickled = pickle.loads(pickled)
print(unpickled.value) # Output: 42
# File operations
with open('data.pkl', 'wb') as f:
pickle.dump(obj, f)
with open('data.pkl', 'rb') as f:
loaded = pickle.load(f)
```
**Comparison:**
| Feature | JSON | Pickle |
|---------|------|--------|
| Format | Text | Binary |
| Python objects | Limited | All types |
| Security | Safe | Can execute code |
| Portability | Cross-language | Python only |
| Speed | Slower | Faster |
| File size | Larger | Smaller |
**Use Cases:**
1. **JSON:** API communication, configuration files, data exchange
2. **Pickle:** Python-specific data, complex objects, temporary storage
3. **Security:** Always prefer JSON for untrusted data
4. **Performance:** Pickle for internal Python applications
### Composition vs Inheritance
Choosing between composition and inheritance.
**Inheritance:**
```python
class Animal:
def make_sound(self):
pass
class Dog(Animal):
def make_sound(self):
return "Woof!"
class Cat(Animal):
def make_sound(self):
return "Meow!"
```
**Composition:**
```python
class Engine:
def start(self):
return "Engine started"
class Car:
def __init__(self):
self.engine = Engine() # Composition
def start(self):
return self.engine.start()
car = Car()
print(car.start()) # Output: Engine started
```
**When to Use:**
- **Inheritance:** "is-a" relationship, code reuse, polymorphism
- **Composition:** "has-a" relationship, flexibility, avoid deep hierarchies
**Use Cases:**
1. **Inheritance:** Model hierarchies, shared behavior
2. **Composition:** Flexible design, avoid coupling
3. **Mixins:** Combine both approaches
4. **Design Patterns:** Strategy, Decorator patterns use composition
## Django related topics:
### Django signals
Django signals can be used to automate tasks and update data in your application without having to manually perform
those tasks or update the data.
Let's say we have a Django application that allows users to place orders for products. We want to automatically update
the product quantity in the database whenever an order is placed. We can use Django signals to accomplish this.
Let's say you want to send an email notification whenever a new user is registered in your Django application. You can use Django's built-in signals for this purpose.
- **Import necessary modules:**
```angular2html
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.core.mail import send_mail
```
- **Create a signal handler function to send an email:**
```
@receiver(post_save, sender=User)
def send_registration_email(sender, instance, created, **kwargs):
if created:
subject = 'Welcome to My Website'
message = 'Thank you for registering on our website.'
from_email = 'noreply@example.com'
recipient_list = [instance.email]
send_mail(subject, message, from_email, recipient_list)
```
- **Connect the signal handler:**
You need to connect the signal handler to the **post_save** signal of the **User** model. You can do this in your Django app's **apps.py** or **models.py** file, or in a separate **signals.py** file:
```angular2html
from django.apps import AppConfig
class YourAppConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'your_app'
def ready(self):
import your_app.signals # Import the signals module where you defined your signal handler
```
Now, whenever a new user is registered in your Django application, the **send_registration_email** function will be triggered, sending a welcome email to the user.
**Use cases:**
1. [X] **User Authentication:** Use signals to perform custom actions when a user is registered or when a user logs in. or Send a welcome email to a user upon registration.
2. [X] **Database Changes:** Notify users or perform other actions when a certain condition is met, like a low stock alert for an e-commerce application.
3. [X] **Signals for Models:** Trigger actions when a model is created, updated, or deleted.
4. [X] **File Uploads:** Perform post-processing tasks on uploaded files. For example, generating thumbnails after an image is uploaded.
5. [X] **Notification Systems:** Send notifications to users or administrators when certain events occur, such as a new comment on a post.
6. [X] **Django Middleware:** For instance, you can create a signal that fires before or after middleware processes a request, allowing you to perform custom actions at different points in the request/response cycle.
7. [X] **Internationalization (i18n) and Localization (l10n):** Use signals to switch the language or locale dynamically based on user preferences or specific events within your application
### Django middleware
Django middleware provides a way to process **requests** and **responses** globally in your application. Middleware can be used
to perform authentication, logging, modifying headers, and more.
Let's say we have a Django application that needs to add a custom header to every HTTP response. We can use Django
middleware to accomplish this.
Here are the steps to use Django middleware:
1. Create a new file called **middleware.py** in the same directory as your settings.py file.
2. Define a class that extends the **MiddlewareMixin** class.
Implement the **process_response** method to modify the response object.
3. Add your middleware class to the **MIDDLEWARE** setting in your **settings.py** file.
Here's an example implementation:
```
class CustomHeaderMiddleware:
def process_response(self, request, response):
response['X-Custom-Header'] = 'Hello, World!'
return response
```
In this example, we define a new middleware class called **CustomHeaderMiddleware** that extends the MiddlewareMixin class.
We implement the **process_response** method to modify the response object by adding a custom header.
Finally, we add our middleware class to the MIDDLEWARE setting in our **settings.py** file:
```
MIDDLEWARE = [
# Other middleware classes...
'myapp.middleware.CustomHeaderMiddleware',
]
```
Now, whenever an HTTP response is returned by our application, the **CustomHeaderMiddleware** class will modify the response
by adding a custom header.
**Use cases:**
1. [X] **Authentication and Authorization:** Middleware can check if a user is authenticated and has the required permissions to access a particular view.
2. [X] **Security Measures:** Implement security-related tasks such as preventing Cross-Site Request Forgery (CSRF) attacks by adding CSRF tokens to forms or setting security headers like HTTP Strict Transport Security (HSTS) headers.
3. [X] **Request Transformation:** Modify the request data before it reaches the view. For example, parsing JSON data from the request body and making it available to the view as a Python dictionary.
4. [X] **Throttling and Rate Limiting:** Implement rate limiting to prevent abuse or overuse of your API or services by limiting the number of requests a client can make within a certain time frame.
5. [X] **CORS (Cross-Origin Resource Sharing):** Enforce security policies for allowing or denying cross-origin requests to your application's resources.
6. [X] **Content Compression:** Compress responses to reduce bandwidth usage and improve page load times, especially for resources like HTML, CSS, and JavaScript files.
### Django custom template tags
Custom template tags can be used to perform complex operations on data or to create reusable components for your
templates.
Here's a simple example of how to create a custom template tag in Django:
1. Create a new Python module in one of your Django app's templatetags directory. For example, if you have an app named
**myapp**, you could create a new module named **myapp_tags.py** in the **myapp/templatetags** directory.
2. Define a function in the module that takes a template context and any arguments you want to pass to the tag. The
function should return the value you want to output in the template. For example, here's a function that takes a list
of strings and returns the first item in the list:
```
from django import template
register = template.Library()
@register.simple_tag
def first_item(my_list):
if my_list:
return my_list[0]
return ''
```
In this example, we're using the **@register.simple_tag** decorator to register the function as a simple template tag.
1. In your template, load the custom tag library and use the new tag in your HTML code. To load the custom tag library,
add **{% load myapp_tags %}** at the top of your template. Here's an example of how to use the first_item tag we
defined earlier:
```
{% load myapp_tags %}
{% for item in my_list %}
{% first_item item %}
{% endfor %}
```
In this example, we're using the **first_item** tag to output the first item in each list item in a loop.
Here are some common use cases for template tags in Django:
1. [ ] **Displaying Data from the Database:** Template tags are often used to fetch and display data from the database. You can use **{% for %}** loops to iterate through querysets and display records, or use **{% if %}** tags to conditionally show content based on database values.
2. [ ] **Including Other Templates:** You can use the **{% include %}** tag to include other templates within your template. This is useful for reusing common components across multiple pages.
3. [ ] **Conditional Rendering:** Template tags like **{% if %}** and **{% else %}** are used for conditional rendering. You can show different content based on certain conditions.
4. [ ] **Loading External Scripts and Styles:** You can use the **{% load %}** tag to load custom template tags or template libraries that provide additional functionality.
5. [ ] **Extending Base Templates:** Template inheritance allows you to create a base template and extend it in other templates. This is useful for maintaining a consistent layout across your site.
6. [ ] **Setting Variables:** The **{% with %}** tag allows you to set variables within a template, making it easier to reuse values.
7. [ ] **Handling Forms:** Template tags like **{% csrf_token %}** are used for including CSRF tokens in forms to ensure security. Additionally, you can use tags like **{% forloop %}** to iterate through form fields.
These are just a few examples of how template tags can be used in Django templates to add dynamic behavior and logic to your web pages.
### Django permissions
Django provides a built-in permissions system that allows you to control access to views and models in your application.
Permissions can be assigned to users or groups, and can be checked in your views or templates to determine whether a
user has the necessary permissions to perform certain actions.
Here's a simple example of how to use Django permissions:
1. First, you need to define the permissions for your models. You can do this by adding a permissions attribute to
your **model's meta class**. For example, let's say you have a Book model and you want to define a permission called
**can_edit_book**:
```
from django.db import models
from django.contrib.auth.models import User
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(User, on_delete=models.CASCADE)
published_date = models.DateField()
class Meta:
permissions = [
("can_edit_book", "Can edit book"),
]
```
In this example, we've added a permissions attribute to the Book model's meta class, and defined a single permission
named **can_edit_book**.
5. Next, you need to assign the permissions to users or groups. You can do this using Django's built-in admin interface,
or programmatically in your code. For example, let's say we want to assign the **can_edit_book** permission to a group
called Editors:
```
from django.contrib.auth.models import Group
editors_group, created = Group.objects.get_or_create(name='Editors')
editors_group.permissions.add('myapp.can_edit_book')
```
In this example, we're using the Group model to get or create a group called Editors, and then adding the
**myapp.can_edit_book** permission to the group.
9. Finally, you can check the user's permissions in your views or templates to control access to certain actions. For
example, let's say we have a view that allows users to edit a book:
```
from django.shortcuts import get_object_or_404, render
from django.contrib.auth.decorators import login_required, permission_required
from myapp.models import Book
@login_required
@permission_required('myapp.can_edit_book', raise_exception=True)
def edit_book(request, book_id):
book = get_object_or_404(Book, id=book_id)
# perform edit operation
return render(request, 'book_edit.html', {'book': book})
```
In this example, we're using the **permission_required** decorator to check if the user has the myapp.can_edit_book
permission before allowing them to edit the book. If the user does not have the necessary permission, a **PermissionDenied**
exception will be raised.
**Use cases:**
1. [X] **Access Control for Views and URLs:** For example, allow only authenticated users to access their profile page, while allowing administrators to access an admin dashboard.
2. [X] **Admin Panel Permissions:** Define who can create, modify, or delete records in the admin interface for specific models.
3. [X] **API Access Control:** Implement role-based access control (RBAC) for APIs, allowing different levels of access for different user roles.
### Django custom user models
Django's built-in **User** model provides a lot of functionality for authentication and authorization, but sometimes you may
need to customize the user model to include additional fields or functionality. In such cases, you can create a custom
user model that inherits from Django's **AbstractBaseUser** or **AbstractUser** classes.
Here's a simple example of how to create a custom user model in Django:
- Create a new app for your custom user model. For example, let's say you want to create a custom user model for a blog
app. You could create a new app called **blog_auth**.
- Create a new model for your custom user model. For example, let's say you want to add a **bio field** to your user
model:
```
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db import models
class BlogUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
first_name = models.CharField(max_length=30, blank=True)
last_name = models.CharField(max_length=30, blank=True)
bio = models.TextField(blank=True)
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
USERNAME_FIELD = 'email'
EMAIL_FIELD = 'email'
def __str__(self):
return self.email
```
In this example, we've created a new model called **BlogUser** that inherits from **AbstractBaseUser** and **PermissionsMixin**.
We've also defined the necessary fields, including a custom bio field.
- Update your project settings to use the custom user model. In your project's **settings.py** file, update the \*
**AUTH_USER_MODEL** setting to point to your new user model:
```
AUTH_USER_MODEL = 'blog_auth.BlogUser'
```
- Migrate your database to create the new user model table. Run the following commands in your terminal:
```
python manage.py makemigrations
python manage.py migrate
```
That's it! You now have a custom user model in Django that you can use for authentication and authorization in your blog
app.
### Django Custom Managers
Django provides a default manager for each model that allows you to interact with the database. However, sometimes you
may need to customize the default manager to add additional functionality or implement custom queries.
Here's a simple example of how to create a custom manager in Django:
- Let's say we have a model called **Book** in our app that represents books in a library. We want to create a custom
manager that returns only the books that are currently available for borrowing.
```
from django.db import models
class AvailableBooksManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_available=True)
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=100)
is_available = models.BooleanField(default=True)
objects = models.Manager() # default manager
available_books = AvailableBooksManager() # custom manager
```
In this example, we've created a custom manager called **AvailableBooksManager** that filters the books based on the
**is_available** field. We've also added a **available_books** attribute to the Book model that uses the custom manager.
- Now we can use the custom manager in our views or templates to get only the books that are currently available for
borrowing. For example:
```
from django.shortcuts import render
from myapp.models import Book
def available_books(request):
books = Book.available_books.all()
return render(request, 'available_books.html', {'books': books})
```
In this example, we're using the **available_books** manager to get all the books that are currently available for
borrowing, and passing them to the **available_books.html** template.
**Use cases:**
1. [X] **Filtering and Query Optimization:** For example, you can create a manager that filters out inactive or deleted records by default.
2. [X] **Data Validation and Preprocessing:** For instance, you can create a manager that ensures all data entered is in uppercase.
3. [X] **Complex Query Construction:** For example, you can create a manager that fetches the top-rated products based on user reviews.
4. [X] **Pagination and Result Limiting:** For example, Create a manager that retrieves a specified number of records per page.
### Django Custom validators
Django provides a set of built-in validators that can be used to validate form data and model fields. However, sometimes
you may need to create your own custom validators to validate data in a specific way. Here's a simple example of how to
create a custom validator in Django:
- Let's say we have a model called Book in our app that represents books in a library. We want to create a custom
validator that checks whether the book's title starts with a capital letter.
```
from django.core.exceptions import ValidationError
def validate_title(value):
if not value[0].isupper():
raise ValidationError("Title must start with a capital letter.")
class Book(models.Model):
title = models.CharField(max_length=100, validators=[validate_title])
author = models.CharField(max_length=100)
```
In this example, we've created a custom validator called **validate_title** that checks whether the first character of the
title is a capital letter. If the validation fails, a ValidationError is raised. We've also added the validate_title
validator to the title field of the Book model.
Now when a user tries to create a book with a title that doesn't start with a capital letter, the validation will fail
and an error message will be displayed.
**Use cases**
1. [X] **Password Strength Validation:** Ensure that user passwords meet certain complexity requirements, such as a minimum length, the presence of uppercase and lowercase characters, and special characters.
2. [X] **Age Verification:** Validate that a user's age is above a certain threshold for age-restricted content or actions.
3. [X] **URL or Domain Validation:** Verify that a URL or domain meets specific criteria, such as being a valid domain name or belonging to an allowed list of domains.
4. [X] **File Type Validation:** Ensure that uploaded files have valid file extensions or meet specific criteria.
5. [X] **Consistency Checks:** Ensure consistency between related fields in a model. For example, checking that start dates are earlier than end dates.
### Custom management commands
Custom management commands allow you to add your own functionality to the **manage.py** command, making it easy to automate
tasks and perform custom operations on your Django project.
Here is a simple example to demonstrate how to create and use custom management commands in Django:
- Create a new Django app:
```
python manage.py startapp myapp
```
- Create a new directory called **management** inside the app directory, and create another directory called **commands** inside the management directory:
```
mkdir myapp/management
mkdir myapp/management/commands
```
- Create a new Python file inside the commands directory, and name it **mycommand.py**. This will be the file that
contains the code for your custom management command:
```
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = 'My custom management command'
def handle(self, *args, **options):
print('Hello from my custom management command!')
```
Register the new command with Django by adding an empty** **__init__.py** file inside the management directory:
```
touch myapp/management/__init__.py
```
- Test the new command by running it from the command line:
```
python manage.py mycommand
```
You should see the message **"Hello from my custom management command!"** printed to the console.
**Use cases:**
1. [X] **Data Import/Export:** Export data from your database to different formats for backup or analysis.
2. [X] **Database Maintenance:** Implement commands for database maintenance tasks, such as creating database backups, purging old records, or optimizing database tables.
3. [X] **User Management:** Create custom commands for managing users, such as creating user accounts in bulk, resetting passwords, or deactivating inactive users.
4. [X] **Content Population:** Populate your database with sample or test data for development and testing purposes using custom commands.
5. [X] **Cache Management:** Develop commands to clear or refresh your cache to keep your application's data cache up to date.
6. [X] **API Data Fetching:** Create commands to fetch data from external APIs, update your database with the retrieved data, and perform any necessary data transformations.
7. [X] **Report Generation:** Generate and distribute reports as PDFs, Excel sheets, or other formats using custom management commands.
### Django's Query API
Here are some examples of how to use Django's Query API:
- Retrieving all objects from a model:
```angular2html
from myapp.models import MyModel
all_objects = MyModel.objects.all()
```
This will retrieve all objects from the **MyModel** model and store them in the **all_objects** variable.
- Filtering objects based on a condition:
```angular2html
from myapp.models import MyModel
filtered_objects = MyModel.objects.filter(attribute=value)
```
This will retrieve all objects from the **MyModel** model where the attribute matches the value and store them in the filtered_objects variable.
- Chaining filters:
```angular2html
from myapp.models import MyModel
filtered_objects = MyModel.objects.filter(attribute1=value1).filter(attribute2=value2)
```
This will retrieve all objects from the **MyModel** model where attribute1 matches value1 and attribute2 matches value2 and store them in the **filtered_objects** variable.
- Querying related fields:
```angular2html
from myapp.models import MyModel
related_objects = MyModel.objects.filter(related_model__attribute=value)
```
This will retrieve all objects from the **MyModel** model where the related model's attribute matches value and store them in the **related_objects** variable.
- Ordering results:
```angular2html
from myapp.models import MyModel
ordered_objects = MyModel.objects.order_by('attribute')
```
This will retrieve all objects from the **MyModel** model and order them by the attribute field in ascending order. You can also use the - sign to order in descending order.
- Limiting results:
```angular2html
from myapp.models import MyModel
limited_objects = MyModel.objects.all()[:10]
```
This will retrieve the first 10 objects from the **MyModel** model and store them in the **limited_objects** variable.
- Using OR conditions:
```angular2html
from myapp.models import MyModel
from django.db.models import Q
objects = MyModel.objects.filter(Q(attribute1=value1) | Q(attribute2=value2))
```
This will retrieve all objects from the **MyModel** model where either attribute1 matches value1 or attribute2 matches value2 and store them in the objects variable.
- Using LIKE conditions:
```angular2html
from myapp.models import MyModel
objects = MyModel.objects.filter(attribute__contains=value)
```
This will retrieve all objects from the **MyModel** model where attribute contains value and store them in the objects variable.
- Using NOT conditions:
```angular2html
from myapp.models import MyModel
objects = MyModel.objects.exclude(attribute=value)
```
This will retrieve all objects from the **MyModel** model where attribute does not match value and store them in the objects variable.
- Retrieving a single object:
```angular2html
from myapp.models import MyModel
object = MyModel.objects.get(id=value)
```
This will retrieve a single object from the **MyModel** model where **id** matches value and store it in the object variable.
- Updating objects:
```angular2html
from myapp.models import MyModel
MyModel.objects.filter(attribute=value).update(attribute=new_value)
```
This will update all objects from the **MyModel** model where attribute matches value and set attribute to new_value.
### Custom query expressions or Custom Lookup
Custom query expressions are a way to extend the Django query API with your own custom SQL expressions. Custom query
expressions can be useful when you need to perform queries that are not easily expressible using the standard query API.
Suppose you have a model called Person that represents a **person** with a first name, last name, and age. You want to
perform a query that returns all people whose first name starts with a given letter. Here's how you can create a custom
query expression to perform this query:
```
from django.db.models import Lookup
class StartsWith(Lookup):
lookup_name = 'startswith'
def as_sql(self, compiler, connection):
lhs, lhs_params = self.process_lhs(compiler, connection)
rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + [rhs_params[0] + '%']
return f"{lhs} LIKE %s", params
Person.objects.filter(first_name__startswith='A')
```
In this example, we define a custom lookup called **StartsWith** that extends the Lookup class. We set the lookup_name
attribute to **'startswith'** to define the name of the lookup. We then define the **as_sql** method to generate the SQL
expression for the lookup.
Here's another simple example of defining and using a custom lookup in Django:
```angular2html
from django.db import models
from django.db.models import Lookup
class GreaterThanLookup(Lookup):
lookup_name = 'gt'
def as_sql(self, compiler, connection):
lhs, lhs_params = self.process_lhs(compiler, connection)
rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + rhs_params
return f'{lhs} > {rhs}', params
models.IntegerField.register_lookup(GreaterThanLookup)
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=8, decimal_places=2)
```
Usage of the custom lookup
```angular2html
expensive_products = Product.objects.filter(price__gt=100.00)
```
In this example, we define a custom lookup called **GreaterThanLookup** that performs a greater-than comparison (>) for IntegerField fields. We subclass **django.db.models.Lookup** and override the **as_sql()** method to define the SQL representation of the lookup.
The usage of the custom lookup is demonstrated, where we filter the Product objects based on the price field using the gt lookup.
### Django Filterset
Django Filterset provides a simple and intuitive way to filter data in Django. It allows you to define filterset classes that specify the fields to filter on and provides a range of built-in filters that you can use to filter data. You can also define custom filters and combine filters to create more complex filtering logic.
Here are some simple examples of using Django Filterset:
- **Basic filtering:**
To filter a queryset using Django Filterset, you first need to define a filterset class that specifies the fields to filter on. For example:
```angular2html
import django_filters
from .models import Product
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
fields = ['name', 'price']
```
This defines a filterset class that filters on the **name** and **price** fields of the Product model. You can then use this filterset class to filter a queryset:
```angular2html
from .filters import ProductFilter
def product_list(request):
queryset = Product.objects.all()
filterset = ProductFilter(request.GET, queryset=queryset)
return render(request, 'product_list.html', {'filterset': filterset})
```
This filters the queryset based on the parameters provided in the GET request.
- **Custom filtering:**
You can also define custom filters that perform more complex filtering logic. For example:
```angular2html
import django_filters
from .models import Product
class ProductFilter(django_filters.FilterSet):
min_price = django_filters.NumberFilter(field_name='price', lookup_expr='gte')
max_price = django_filters.NumberFilter(field_name='price', lookup_expr='lte')
class Meta:
model = Product
fields = ['name', 'min_price', 'max_price']
```
This defines custom filters that filter on the **price** field of the Product model. The **min_price** filter filters on products with a price greater than or equal to the provided value, and the **max_price** filter filters on products with a price less than or equal to the provided value.
Instead of using FilterSets, you can directly use the Django ORM filters to perform filtering on your querysets. Django ORM provides a wide range of filter options such as exact **match, case-insensitive match, range filters**, and more. You can chain multiple filters together to create complex queries.
**Use cases:**
1. [X] **Search Functionality:** Implement a search feature allowing users to search for records based on specific criteria, such as keywords or partial matches in various fields.
2. [X] **Data Filtering:** Create filters to allow users to filter data based on multiple fields, such as date ranges, categories, or status.
3. [X] **Exporting Filtered Data:** Enable users to export filtered data to CSV, Excel, or other formats for further analysis or reporting.
4. [X] **Geolocation Filtering:** Build Filtersets for location-based queries, such as finding nearby places or events within a specific radius.
5. [X] **Data Visualization:** Integrate Filtersets with data visualization libraries to enable users to filter and explore data interactively.
### Context managers
The context manager is responsible for setting up the context, running the block of code, and then cleaning up the context when the block is exited, regardless of whether the block raised an exception or not. In Django, context managers can be used to control signals, transactions, and caching.
Here are some simple examples of context managers in Django:
- **Database transactions**:
In Django, context managers can be used to control database transactions. For example:
```angular2html
from django.db import transaction
with transaction.atomic():
# code that requires a transaction
```
- **Caching**:
Context managers can be used to control caching in Django. For example:
```angular2html
from django.core.cache import cache
class CacheContext:
def __init__(self, key, value):
self.key = key
self.value = value
def __enter__(self):
cache.set(self.key, self.value)
def __exit__(self, exc_type, exc_val, exc_tb):
cache.delete(self.key)
with CacheContext('my_key', 'my_value'):
# code that requires the cache to be set
```
In summary, context managers in Django provide a simple and intuitive API for a powerful construct. They allow you to allocate and release resources precisely when you want to, and they make it easier to write safe and readable code.
Overall, context managers provide a robust and efficient way to manage resources and ensure proper cleanup in Django applications. They contribute to cleaner code, improved error handling, and better resource utilization.
**Use Cases:**
1. [ ] **Database Transactions:** Use context managers to manage database transactions. This ensures that database changes are committed if the code block executes without errors or rolled back in case of exceptions.
2. [ ] **File Handling:** Open and close files safely using a context manager, ensuring that files are closed properly even if an exception occurs.
3. [ ] **Resource Locking:** Implement resource locking to ensure exclusive access to a resource, preventing concurrent access by multiple threads or processes.
4. [ ] **Database Connections:** Manage database connections and cursors safely, ensuring that connections are closed after use.
### Django Channels
Django Channels is a package that allows Django to handle WebSockets and other non-HTTP protocols. It extends the built-in capabilities of Django, allowing Django projects to handle not only HTTP but also protocols that require long-running connections, such as WebSockets, MQTT (IoT), chatbots, radios, and other real-time applications.
Here are some differences between Django Channels and Django's built-in HTTP capabilities:
Django Channels:
- Allows Django projects to handle protocols other than HTTP, such as WebSockets, MQTT, and chatbots
- Provides support for Django's core features like authentication and sessions
- Preserves the synchronous behavior of Django and adds a layer of asynchronous protocols allowing users to write views that are entirely synchronous, asynchronous, or a mixture of both
- Replaces Django's default WSGI with its ASGI (Asynchronous Server Gateway Interface)
Django's built-in HTTP capabilities:
- Handle only HTTP requests
- Do not support protocols that require long-running connections, such as WebSockets and MQTT
- Do not provide support for asynchronous code execution
In summary, Django Channels extends Django's built-in capabilities to handle protocols other than HTTP and provides support for asynchronous code execution.
**Use Cases:**
1. [X] **Real-Time Chat Applications:** Create real-time chat applications where users can send and receive messages instantly using WebSockets.
2. [X] **Live Notifications:** Implement live notifications to inform users about new messages, friend requests, updates, or any other events that require immediate user attention.
3. [X] **Multiplayer Online Games:** Develop real-time multiplayer games using WebSockets for game state synchronization, player interaction, and chat functionality.
4. [X] **IoT Integration:** Integrate with Internet of Things (IoT) devices by handling real-time data streams, sensor data, and device control.
5. [X] **Financial Trading Platforms:** Build financial trading applications that require real-time price updates, order execution, and live trading data.
6. [X] **Geolocation and Mapping:** Develop applications that track and display the real-time location of vehicles, deliveries, or assets on a map.
7. [X] **Live Video Streaming:** Build live video streaming platforms for webinars, conferences, or live events, where viewers can interact with the stream in real time.
### HTTP methods in Django
Django provides built-in support for handling HTTP requests and responses using request and response objects Here are some examples of how to use HTTP methods in Django:
- **GET**:
To handle a GET request in a class-based view, you can define a method called **get()** in your view class. For example:
```angular2html
from django.views import View
from django.http import HttpResponse
class HelloView(View):
def get(self, request):
return HttpResponse('Hello, World!')
```
- **POST**:
To handle a POST request in a class-based view, you can define a method called **post()** in your view class. For example:
```angular2html
from django.views import View
from django.http import HttpResponse
class LoginView(View):
def post(self, request):
username = request.POST['username']
password = request.POST['password']
# Authenticate user
return HttpResponse('Logged in successfully')
```
- **PUT** and **DELETE**:
Django's class-based views do not have built-in support for handling PUT and DELETE requests. However, you can use mixins or third-party packages like Django REST framework to handle these requests. For example, using Django REST framework, you can define a class-based view that inherits from **APIView** and override the appropriate methods for PUT and DELETE requests:
```angular2html
from rest_framework.views import APIView
from rest_framework.response import Response
class UserDetailView(APIView):
def put(self, request, pk):
# Update user object with pk
return Response({'message': 'User updated'})
def delete(self, request, pk):
# Delete user object with pk
return Response({'message': 'User deleted'})
```
In Django, the difference between **PUT** and **PATCH** methods is similar to their difference in general HTTP terms.
- **PUT** is used to modify an entire resource on the server. When a client sends a PUT request in Django, it updates the entire resource with the new data provided. If the resource does not exist, PUT creates a new resource. PUT is idempotent, meaning that calling it once or multiple times successively has the same effect.
- **PATCH** is used to modify a part of a resource on the server. When a client sends a PATCH request in Django, it updates only the specified part of the resource with the new data provided. PATCH is not idempotent, meaning that successive identical PATCH requests may have additional effects. PATCH allows partial updates and side-effects on other resources.
Here's an example of how to handle PUT and PATCH requests in Django using Django REST Framework:
```angular2html
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from myapp_models import MyResource
class MyResourceView(APIView):
def put(self, request, pk):
# Retrieve the resource with the given pk
resource = MyResource.objects.get(pk=pk)
# Update the entire resource with the new data
resource.name = request.data.get('name')
resource.age = request.data.get('age')
resource.save()
return Response(status=status.HTTP_200_OK)
def patch(self, request, pk):
# Retrieve the resource with the given pk
resource = MyResource.objects.get(pk=pk)
# Update only the specified part of the resource with the new data
if 'name' in request.data:
resource.name = request.data['name']
if 'age' in request.data:
resource.age = request.data['age']
resource.save()
return Response(status=status.HTTP_200_OK)
```
In the above example, the put method is used to handle PUT requests and update the entire resource with the new name and age. The patch method is used to handle PATCH requests and update only the specified part of the resource. The request.data attribute is used to access the data sent in the request body.
### annotate and aggregate in Django
**annotate()** is typically used when you want to add additional information or calculated fields to each object in the queryset. For example, you can annotate a queryset of books with the number of authors for each book
On the other hand, **aggregate()** is used when you want to perform calculations on the entire queryset, such as calculating the sum, average, or count of a specific field across all objects in the queryset.
The output of **annotate()** is a QuerySet, which means it returns a modified queryset with the annotated values. On the other hand, **aggregate()** returns a dictionary containing the calculated aggregate values
Here is a simple example of using annotate() and aggregate() in Django:
```angular2html
from django.db.models import Count
from myapp.models import Book, Author
# Count the number of books for each author
authors = Author.objects.annotate(num_books=Count('book'))
# Count the total number of books
total_books = Book.objects.aggregate(total=Count('id'))
```
In the above example, **annotate()** is used to count the number of books for each author. The **Count()** function is used to count the number of related books for each author. The resulting queryset will have an additional attribute num_books for each author object.
**aggregate()** is used to count the total number of books. The **Count()** function is used to count the number of books in the Book model. The resulting dictionary will have a single key total with the total number of books as its value.
It is important to note that **annotate()** returns a queryset, while **aggregate()** returns a dictionary.
### Mixin in Django
In Django, a **mixin** is a class that provides additional functionality to other classes through **inheritance**. Mixins are used to add common or reusable functionality to multiple classes without the need for duplicating code.
Here's a simple example to demonstrate how mixins work in Django:
```angular2html
# Define a mixin class
class TimestampMixin:
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
# Use the mixin in a model
class MyModel(TimestampMixin, models.Model):
name = models.CharField(max_length=100)
# other fields
# Use the mixin in a view
class MyView(TimestampMixin, View):
def get(self, request):
# handle GET request
pass
def post(self, request):
# handle POST request
pass
```
In this example, we have defined a mixin class called **TimestampMixin**. It adds two fields, **created_at** and **updated_at**, to any class that inherits from it. The Meta class with **abstract = True** ensures that the mixin itself is not treated as a model.
By using mixins, you can easily add common functionality to multiple classes without duplicating code. This promotes code reuse, improves maintainability, and keeps your codebase organized.
**Use Cases:**
1. [X] **Authentication Mixin:** Add authentication checks to views, ensuring that only authenticated users can access specific views.
2. [X] **Permission Mixin:** Check user permissions or roles before allowing access to certain views, ensuring that users have the required privileges.
3. [X] **Pagination Mixin:** Implement pagination for list views, allowing users to navigate through large datasets.
4. [X] **Timestamp Mixin:** Add timestamp fields (created_at, updated_at) to models for automatic tracking of creation and modification times.
5. [X] **Geolocation Mixin:** Add latitude and longitude fields to models, enabling geospatial queries and location-based services.
6. [X] **Captcha Mixin:** Integrate CAPTCHA validation with forms to prevent spam submissions.
7. [X] **File Upload Mixin:** Add file upload functionality to forms, allowing users to upload files like images or documents.
8. [X] **User Tracking Mixin:** Implement middleware to track user activity, such as page views or interactions, and log them for analytics.
These are just a few examples of how mixins can enhance Django applications by promoting code reuse and modularity. They allow you to easily add and extend functionality across various components of your Django project, making your code more maintainable and efficient.
### Cache in Django
By using caching in Django, you can significantly improve the performance of your application by reducing the load on the database and serving cached results faster. It is an essential technique for optimizing web applications and improving user experience.
Django provides built-in support for caching through its caching framework. The caching framework allows you to cache the results of views, template fragments, and even low-level database queries. It supports various cache backends and provides flexibility in configuring cache settings.
Here's a simple example to demonstrate how caching works in Django:
1- Configure the cache backend in your Django settings:
```angular2html
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
}
}
```
2- Use the **cache_page** decorator to cache the result of a view:
```angular2html
from django.views.decorators.cache import cache_page
@cache_page(60 * 15) # Cache the page for 15 minutes
def my_view(request):
# Expensive computation or database query
# ...
return HttpResponse('Hello, World!')
```
We then use the **cache_page** decorator to cache the result of the **my_view** function for 15 minutes. This means that the first time the view is accessed, the result will be computed and stored in the cache. For subsequent requests within the next 15 minutes, the cached result will be returned directly without executing the view function again.
**Use Cases:**
1. [X] **Page Caching:** Cache the rendered HTML of frequently accessed pages to reduce the load on the database and speed up page rendering.
2. [X] **Queryset Caching:** Cache the results of database queries, especially for complex or frequently used queries. This reduces the need to hit the database repeatedly.
3. [X] **Template Fragment Caching:** Cache specific portions of templates, such as navigation menus or sidebars, to minimize rendering time for frequently used components.
4. [X] **Computed Values Caching:** Cache the results of expensive computations or data transformations to avoid recomputation for the same input.
5. [X] **Third-Party API Responses:** Cache responses from third-party APIs to reduce the number of requests made to external services and improve the application's reliability.
6. [X] **Rate Limiting:** Implement rate limiting by caching user request information to control the number of requests allowed within a specific time period.
Django provides a flexible caching framework that supports various caching backends, including in-memory caching, file-based caching, and distributed cache systems like Memcached and Redis. By strategically implementing caching in your Django application, you can significantly improve its performance and scalability.
### Django constraint
In Django, a constraint refers to a rule or condition that can be applied to a database table to enforce data integrity. Constraints ensure that the data stored in the table follows certain rules and meets specific requirements.
Django provides a way to define constraints on model fields using the constraints attribute. This attribute allows you to specify one or more constraints for a model, such as unique constraints, check constraints, foreign key constraints, and more.
Here's a simple example of defining and using a constraint in Django:
```angular2html
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
class Meta:
constraints = [
models.CheckConstraint(check=models.Q(age__gte=18), name='age_gte_18')
]
```
In this example, we define a **Person** model with two fields: **name** and **age**. We want to enforce a constraint that ensures the age field is greater than or equal to 18.
By defining this constraint, Django will automatically create the necessary SQL statements to enforce the constraint when creating or modifying the database table for the Person model. Constraints help maintain data integrity and ensure that the data stored in the database follows the defined rules. They can be particularly useful in scenarios where you want to enforce specific conditions or relationships between fields in your models.
- **Django constraints vs model validators differences**
Django constraints and model validators are two different mechanisms for ensuring data integrity in Django applications.
Constraints are used to enforce data integrity rules at the database level, while validators are used to enforce data integrity rules at the application leve.
During exceptions in Django Constraints raise database-level errors such as IntegrityError, while validators raise application-level errors such as ValidationError
**Use Cases:**
1. [X] **Unique Constraints:** Ensure that a specific field or combination of fields contains unique values across records. Commonly used for unique usernames, email addresses, or product codes.
2. [X] **Check Constraints:** Specify custom validation rules for data in one or more fields. For example, ensure that a price field is always positive, or that a date falls within a certain range.
3. [X] **Unique Together Constraints:** Ensure that a combination of fields contains unique values. Useful when you want to enforce uniqueness based on multiple fields, such as a combination of username and email.
4. [X] **Table-Level Constraints:** Enforce constraints that involve multiple fields or records at the table level. For instance, ensuring that the start date is always earlier than the end date for date ranges.
### bulk creation in Django
In Django, bulk creation refers to the process of creating multiple model instances in a single database query, rather than creating them one by one. This can significantly improve the performance of creating large numbers of objects. This method takes a list of model instances as an argument and inserts them into the database efficiently.
Here's a simple example of using **bulk_create()** in Django:
```angular2html
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=100)
publication_year = models.IntegerField()
# Create a list of Book instances
books = [
Book(title='Book 1', author='Author 1', publication_year=2020),
Book(title='Book 2', author='Author 2', publication_year=2021),
Book(title='Book 3', author='Author 3', publication_year=2022),
]
# Bulk create the books
Book.objects.bulk_create(books)
```
In this example, we define a **Book** model with three fields: title, author, and publication_year. We then create a list of Book instances with different values. To perform bulk creation, we use the **bulk_create()** method of the Book.objects manager. We pass the list of Book instances as an argument to **bulk_create()**, and Django will efficiently insert them into the database in a single query.
Overall, **bulk_create()** is a useful feature in Django for efficiently creating multiple model instances in a single database query.
**Use Cases:**
1. [X] **Data Import and Migration:** Importing data from external sources, such as CSV files or JSON files, into your Django application's database.
2. [X] **Bulk Updates:** Updating multiple rows in a database table simultaneously when changes need to be made to many records at once.
3. [X] **Historical Data:** Maintaining historical records, such as version history, snapshots, or backups, by inserting historical data into dedicated tables.
### prefetch_related and select_related in Django
In Django, **prefetch_related** and **select_related** are query optimization techniques that allow you to reduce the number of database queries when retrieving related objects.
1- **select_related**
It works for **ForeignKey** and **OneToOneField** relationships. By using **select_related**, you can avoid the overhead of multiple database queries when accessing related objects.
Here's a simple example:
```angular2html
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
```
Suppose we have a **Book** model with a **ForeignKey** relationship to the **Author** model. If we want to retrieve all books and their corresponding authors, we can use **select_related** to fetch the related authors in a single query:
```angular2html
books = Book.objects.select_related('author').all()
for book in books:
print(f"Book: {book.title}, Author: {book.author.name}")
```
In this example, **select_related('author')** is used to fetch the related Author objects along with the Book objects in a single query. This avoids the need for an additional query for each book's author.
2- **prefetch_related**
It works for **ManyToManyField** and **reverse ForeignKey** relationships. By using **prefetch_related**, you can reduce the number of queries when accessing related objects. Here's a simple example:
```angular2html
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=100)
class Product(models.Model):
name = models.CharField(max_length=100)
categories = models.ManyToManyField(Category)
```
Suppose we have a **Product** model with a **ManyToManyField** relationship to the **Category** model. If we want to retrieve all products and their corresponding categories, we can use **prefetch_related** to fetch the related categories efficiently:
```angular2html
products = Product.objects.prefetch_related('categories').all()
for product in products:
print(f"Product: {product.name}")
for category in product.categories.all():
print(f"Category: {category.name}")
```
To retrieve all authors along with their books efficiently, we can use **prefetch_related** as follows:
```angular2html
authors = Author.objects.prefetch_related('books').all()
for author in authors:
print(f"Author: {author.name}")
for book in author.books.all():
print(f"Book: {book.title}")
```
In this example, **prefetch_related('books')** is used to fetch all the related Book objects efficiently for each author. It reduces the number of queries by fetching all the related books in a separate query, rather than fetching them individually for each author.
In this example, **prefetch_related('categories')** is used to fetch the related Category objects efficiently. It reduces the number of queries by fetching all the related categories in a separate query, rather than fetching them individually for each product.
Using **select_related** and **prefetch_related** can significantly improve the performance of your Django queries by reducing the number of database queries. It is especially useful when dealing with large datasets or complex relationships between models.
### Third-party packages in Django
There are several third-party packages that can greatly enhance your development experience and provide additional functionality. Here is a list of must-have third-party packages that are commonly used in Django projects:
- **Django REST framework**: A powerful and flexible toolkit for building Web APIs.
- **Django Crispy Forms**: Easily manage Django forms' layout using bootstrap styles.
- **Django Debug Toolbar**: Provides a set of panels displaying various debug information about the current request/response.
- **Django Celery**: Distributed task queue system for asynchronous processing.
- **Django allauth**: User registration, authentication, and account management.
- **Django Rest Auth**: Provides a set of REST API endpoints for user registration, authentication, and account management.
- **Django Filter**: Simplifies the process of filtering querysets dynamically.
- **Django Rest Framework Swagger**: Generates interactive API documentation using the OpenAPI standard.
- **Django Environ**: Allows you to define environment variables for your Django project.
### Property decorators
The @property decorator in Django is used to define a method that behaves like a model attribute. It allows you to call custom model methods as if they were normal model attributes. Here's a simple example to illustrate the concept:
```python
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
price = models.DecimalField(max_digits=5, decimal_places=2)
@property
def price_in_euros(self):
return f"{self.price} EUR"
```
In this example, we define a **Book** model with fields for **title**, **author**, and **price**. We then define a **price_in_euros** method with the **@property** decorator. This method returns the price of the book in euros as a string.
By using the **@property** decorator, we can access the **price_in_euros** method as if it were a model attribute. For example, we can retrieve the price of a book in euros as follows:
```angular2html
book = Book.objects.get(pk=1)
print(book.price_in_euros) # Output: "19.99 EUR"
```
In Django models, a method with the @property decorator behaves like a model attribute, while a method without this decorator behaves like a normal method.
Here's an example to illustrate the difference:
```angular2html
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
price = models.DecimalField(max_digits=5, decimal_places=2)
@property
def price_in_euros(self):
return f"{self.price} EUR"
def calculate_discounted_price(self, discount):
return self.price * (1 - discount)
```
When we access **my_book.price_in_euros**, it behaves like a model attribute and returns the price of the book in euros as a string. On the other hand, when we call **my_book.calculate_discounted_price(0.1)**, it behaves like a normal method and returns the discounted price of the book.
### WSGI and ASGI
The Web Server Gateway Interface (**WSGI**) and the Asynchronous Server Gateway Interface (**ASGI**) are two different specifications in Django for handling web requests and responses. Here's a brief explanation of each:
#### WSGI (Web Server Gateway Interface):
- WSGI is a synchronous interface that defines how web servers communicate with Python web applications.
- It provides a standard way for web servers to forward requests to Python web frameworks and applications.
- WSGI servers handle one request at a time and block until the response is generated, making it suitable for traditional synchronous web applications.
- WSGI is the older and more established interface, widely used by Python web frameworks and servers.
#### ASGI (Asynchronous Server Gateway Interface):
- ASGI is an asynchronous interface that extends the capabilities of WSGI to support asynchronous web applications.
- It allows multiple, concurrent requests to be handled asynchronously, making it suitable for real-time applications, long-polling, and WebSockets.
- ASGI servers can handle both synchronous and asynchronous applications, providing flexibility for developers.
- ASGI is designed to be a superset of WSGI, meaning that WSGI applications can be run inside ASGI servers.
In summary, **WSGI** is a synchronous interface for handling web requests and responses, while **ASGI** is an asynchronous interface that extends the capabilities of WSGI to support asynchronous applications.
**ASGI** provides the ability to handle multiple concurrent requests and is suitable for real-time and long-polling applications.
### Advanced features in ORM
Django ORM (Object-Relational Mapping) provides several advanced features that allow for more complex and powerful database queries and data manipulation. Here are some of the advanced features of Django ORM:
- **Q objects:**
Q objects allow for complex queries by combining multiple filters using logical operators like AND (&) and OR (|). They encapsulate keyword arguments for filtering and provide more flexibility in query construction
```angular2html
from django.db.models import Q
# Query to retrieve books with either 'fiction' or 'mystery' genre
books = Book.objects.filter(Q(genre='fiction') | Q(genre='mystery'))
```
- **F expressions:**
F expressions allow you to perform database operations using field values without pulling the values from the database. They enable calculations and comparisons directly within the database query, improving performance and reducing round trips to the database
```angular2html
from django.db.models import F
# Query to update the price of all books by increasing it by 10%
Book.objects.all().update(price=F('price') * 1.1)
```
- **QuerySet methods**
Django provides a comprehensive set of QuerySet methods for retrieving, filtering, and manipulating data from the database. These methods include **annotate()**, **aggregate()**, **values()**, **distinct()**, **order_by()**, **reverse()**, and many more
```angular2html
# Query to retrieve the top 5 books with the highest ratings
top_books = Book.objects.order_by('-rating')[:5]
# Query to retrieve the distinct authors of all books
authors = Book.objects.values('author').distinct()
# Query to annotate the average price for each genre
genres = Book.objects.values('genre').annotate(avg_price=Avg('price'))
```
- **Model managers**
Model managers allow you to customize the default QuerySet behavior by defining custom methods on the manager class. Managers provide a way to encapsulate common query logic and make it easily accessible across multiple models
```angular2html
class BookManager(models.Manager):
def get_best_sellers(self):
return self.filter(sales__gt=1000)
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
price = models.DecimalField(max_digits=5, decimal_places=2)
sales = models.IntegerField()
objects = BookManager()
# Query to retrieve the best-selling books
best_sellers = Book.objects.get_best_sellers()
```
- **Model inheritance**
Django supports model inheritance, allowing you to create more complex data models by building relationships between models. You can use abstract base classes, multi-table inheritance, and proxy models to implement different types of model inheritance
```angular2html
class Publication(models.Model):
title = models.CharField(max_length=100)
publisher = models.CharField(max_length=50)
class Book(Publication):
author = models.CharField(max_length=50)
price = models.DecimalField(max_digits=5, decimal_places=2)
class Magazine(Publication):
issue_number = models.IntegerField()
# Query to retrieve all publications (books and magazines)
publications = Publication.objects.all()
```
These examples demonstrate the usage of advanced features in Django ORM, such as Q objects for complex queries, F expressions for database operations, QuerySet methods for data retrieval and manipulation, model managers for custom query logic, and model inheritance for creating relationships between models.
### Class-based views methods
Django class-based views provide a set of methods that can be overridden to customize the behavior of the view. Here are some of the commonly used methods in Django class-based views:
- **as_view()**:
This method returns a callable view function that handles the request and generates the response. It is called when the URL pattern is matched to the view class.
- **dispatch()**:
This method is called by the **as_view()** method to handle the request. It inspects the request method (GET, POST, etc.) and calls the appropriate method (get(), post(), etc.) to generate the response.
- **HTTP method handlers**:
The HTTP method handlers (get(), post(), etc.) are called by the **dispatch()** method to generate the response. They perform the necessary operations (querying the database, rendering templates, etc.) and return an **HttpResponse** object.
- **get_context_data()**:
This method is called by the HTTP method handlers to retrieve the context data for the template. It returns a dictionary of context variables that are passed to the template.
- **get(), post(), put(), delete()**:
These methods are called by the **dispatch()** method to handle the corresponding HTTP methods. They perform the necessary operations (querying the database, rendering templates, etc.) and return an HttpResponse object.
- **get_queryset()**:
This method is used to retrieve the queryset that the view will use to retrieve objects. It can be overridden to customize the queryset based on the request.
- **get_object()**:
This method is used to retrieve a single object from the queryset. It can be overridden to customize the object retrieval based on the request.
- **form_valid(), form_invalid()**:
These methods are called when a form is submitted and validated. They perform the necessary operations (saving the form data, redirecting to a new page, etc.) and return an HttpResponse object.
By understanding these methods, you can customize the behavior of Django class-based views to suit your specific requirements and optimize the performance of your Django applications.
### Django optimization
To optimize your Django application for scalability, there are several techniques you can implement:
- **Database Optimization:**
Django's database layer provides various ways to improve performance, such as using database indexes, optimizing queries, and reducing database round trips
Suppose you have a Django model called **Book** with a field called **title**. To optimize the database query, you can add an index to the title field by adding **db_index=True** to the field definition in the model:
```angular2html
class Book(models.Model):
title = models.CharField(max_length=100, db_index=True)
author = models.CharField(max_length=50)
```
this index will speed up queries that filter or order by the title field, as the database can use the index to quickly find the relevant rows
- **Caching:**
Implementing caching can significantly improve the performance of your Django application. You can use tools like Django's built-in caching framework. For example, to cache the result of a query for 5 minutes, you can use the **cache_page** decorator:
```angular2html
from django.views.decorators.cache import cache_page
@cache_page(60 * 5)
def my_view(request):
# perform database query
books = Book.objects.all()
```
- **Code Optimization:**
Optimizing your code can help improve the overall performance of your application. This includes writing efficient algorithms, reducing unnecessary database queries, and optimizing resource-intensive operations.
Suppose you have a Django view that returns a list of all **Book** objects in the database. To optimize the code, you can use the **prefetch_related** method to fetch related objects in a single query:
code example:
```angular2html
def book_list(request):
# perform database query for all books
books = Book.objects.all()
# perform database query for each author of each book
for book in books:
authors = book.author_set.all()
...
```
Optimized version:
```angular2html
def book_list(request):
# fetch related authors in a single query
books = Book.objects.prefetch_related('author_set').all()
for book in books:
authors = book.author_set.all()
...
```
In the first example, the code performs a separate database query for each author of each book, resulting in a large number of queries. In the second example, the **prefetch_related** method fetches all related authors in a single query, reducing the number of queries and improving performance.
- **Distributed Task Queues:**
Using distributed task queues, such as **Celery**, can help offload time-consuming tasks to separate worker processes, allowing your application to handle more concurrent requests.
Suppose you have a view that performs a time-consuming task, such as sending an email. To offload the task to a separate worker process, you can use **Celery** such as below:
```angular2html
from celery import shared_task
@shared_task
def send_email_task(email):
# send email
...
def my_view(request):
# offload task to Celery worker
send_email_task.delay(email)
...
```
- **Scaling Horizontally:**
To handle increased traffic and user load, you can scale your Django application horizontally by splitting it into individual services (SOA) and scaling them independently. This approach allows you to distribute the load and optimize each service individually.
Suppose you have a Django application that handles both user authentication and payment processing. To scale the application horizontally, you can split it into two separate services:
1- Authentication service: Handles user authentication and authorization.
2- Payment service: Handles payment processing and billing.
- **Benchmarking:**
Regularly benchmarking and profiling your application can help identify performance bottlenecks and areas for improvement. Tools like **Django Debug Toolbar** and **Django Silk** can assist in analyzing and optimizing your code.
### Generic Foreign Key in Django
In Django, a Generic Foreign Key is a feature that allows you to create a relationship between a model and any other model in the database without directly specifying the related model. This is useful when you have a model that needs to be related to multiple other models. Instead of creating separate foreign keys for each related model, you can use a generic foreign key to achieve this flexibility.
In this example, we will create a model called **Vote**, which can be used to represent votes on different types of content, such as **posts**, **comments**, or **images**.
1- Define the models in **models.py**:
```angular2html
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
class Vote(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
voter_name = models.CharField(max_length=100)
vote_type = models.CharField(max_length=10) # e.g., 'upvote', 'downvote'
def __str__(self):
return f"{self.voter_name} voted on {self.content_object} ({self.vote_type})"
```
2- Create instances of the models:
Now, you can create instances of the **Vote** model and associate them with any other models in the database. For example:
```angular2html
from django.contrib.contenttypes.models import ContentType
# Create a post instance (just an example, you can have other models)
post = Post.objects.create(title="My Post", content="This is my post content")
# Create a comment instance (just an example, you can have other models)
comment = Comment.objects.create(text="Nice post!")
# Create a vote for the post
post_vote = Vote.objects.create(
content_object=post,
voter_name="John Doe",
vote_type="upvote"
)
# Create a vote for the comment
comment_vote = Vote.objects.create(
content_object=comment,
voter_name="Alice",
vote_type="downvote"
)
```
In this example, we have created a Vote model with a generic foreign key, allowing it to be associated with different types of content (posts, comments, etc.).
You can now use the **Vote** model to track votes on various content types throughout your Django project.
### Django custom exceptions
In Django, you can create custom exceptions to handle specific error scenarios in your application. Custom exceptions allow you to define your own exception classes that inherit from Python's built-in **Exception** class or any other existing exception class. By doing so, you can raise and catch these custom exceptions in your code to handle different types of errors more effectively.
Here's a simple example of how to create and use custom exceptions in Django:
1- Define the custom exception in a **exceptions.py** file (you can create this file in your app directory):
```angular2html
class InvalidDataError(Exception):
def __init__(self, message="Invalid data provided."):
self.message = message
super().__init__(self.message)
```
2- Raise the custom exception in your views or other parts of the code:
```angular2html
from django.shortcuts import render
from .exceptions import InvalidDataError
def custom_exception_view(request):
try:
# Some logic to validate data
data = request.POST.get('data')
if not data:
raise InvalidDataError("Data is missing!")
# Rest of the code for processing valid data
return render(request, 'success.html', {'data': data})
except InvalidDataError as e:
# Handle the custom exception
error_message = str(e)
return render(request, 'error.html', {'error_message': error_message})
```
In this example, we create a view function called **custom_exception_view**. Inside the view, we perform some validation on the data received in the POST request. If the data is missing or invalid, we raise the **InvalidDataError** custom exception with a specific error message.
here are some examples of Exception Handling in Django:
- Catching exceptions in views:
```
from django.http import HttpResponseServerError
def my_view(request):
try:
# Code that may raise an exception
# ...
except SomeException as e:
# Code to handle SomeException
return HttpResponseServerError("An error occurred: {}".format(str(e)))
```
In this example, if SomeException is raised within the try block, the corresponding except block will be executed. It
returns an HTTP 500 response with a custom error message.
- Handling database-related exceptions:
When working with Django's ORM (Object-Relational Mapping) and database operations, you may encounter exceptions related
to database errors.
```
from django.db import DatabaseError
def my_view(request):
try:
# Perform database operations
# ...
except DatabaseError as e:
# Handle database-related exception
return render(request, 'error.html', {'message': str(e)})
```
In this example, if a DatabaseError occurs during database operations, you can render an error template with the
exception message.
### select_for_update in Django
In Django, **select_for_update** is a method that allows you to lock database rows for update during a database query. This is useful in scenarios where you want to prevent multiple transactions from simultaneously modifying the same rows, thus avoiding potential conflicts and ensuring data consistency.
When you use **select_for_update**, it places a "select for update" lock on the selected rows in the database. Other transactions attempting to modify the same rows will have to wait until the lock is released. This feature is particularly important in situations where concurrent updates could lead to incorrect results or data integrity issues.
Assume we have a model called **Book** that represents books in a library. We want to ensure that when a user borrows a book, the book's availability status is updated, and during this process, no other user can borrow the same book until the update is complete.
1- Define the model in **models.py**:
```angular2html
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
is_available = models.BooleanField(default=True)
def __str__(self):
return self.title
```
2- Borrow the book using **select_for_update**:
In your view or business logic, use **select_for_update** to borrow the book and update its availability status:
```angular2html
from django.db import transaction
from .models import Book
def borrow_book(book_id):
try:
with transaction.atomic():
book = Book.objects.select_for_update().get(pk=book_id)
if book.is_available:
book.is_available = False
book.save()
return f"Successfully borrowed {book.title}."
else:
return f"Sorry, {book.title} is not available for borrowing."
except Book.DoesNotExist:
return "Book not found."
```
In this example, we use a context manager (**with transaction.atomic()**) to ensure that the update operation is atomic and handled within a single database transaction. The **select_for_update()** method is called on the **Book.objects** queryset to obtain a lock on the selected row for update.
This example demonstrates how to use **select_for_update** to lock a specific row in the database during an update operation to maintain data integrity and consistency in a concurrent environment.
### Django model methods
To list all the methods available for models in Django, we can refer to the methods provided by the base Model class and other related classes.
Here are some common methods available for models in Django:
- ```save():``` :
Saves the model instance to the database.
- ```delete():```
Deletes the model instance from the database.
- ```full_clean():```
Performs model validation, including field validation, and raises any validation errors.
- ```clean_fields(): ```
Performs field validation and raises any validation errors.
- ```clean():```
Performs model-specific validation and raises any validation errors.
- ```validate_unique():```
Validates the uniqueness of fields and raises any validation errors.
- ```refresh_from_db():```
Reloads the model instance's fields from the database.
- ```get_FOO_display(): ```
Returns the display value of a choices field, where FOO is the name of the field.
- ```get_absolute_url():```
Returns the URL for displaying the model instance.
- ```get_field_name():```
Returns the name of the field associated with a given database column name.
- ```get_next_by_FOO():```
Returns the next model instance by a specified field, where FOO is the name of the field.
- ```get_previous_by_FOO():```
Returns the previous model instance by a specified field, where FOO is the name of the field.
- ```serializable_value():```
Returns the value of a field as a serializable object.
- ```to_json():```
Returns a JSON representation of the model instance.
- ```to_dict():```
Returns a dictionary representation of the model instance.
- ```queryset():```
Is used to customize the initial queryset used in a model's manager
- ```pre_save()``` and ```post_save()```:
Are signals that are emitted before and after saving a model instance
- ```all()```:
Returns a QuerySet containing all objects of the model from the database
- ```filter()```:
Is used to retrieve a subset of objects from the database based on specified criteria
### Parametric unit tests
In Django, parametric unit tests refer to the practice of executing the same test logic with different input parameters or test data. This allows you to write more concise and reusable tests by avoiding code duplication.
Here's a simple example of a parametric unit test in Django:
```angular2html
from django.test import TestCase
def multiply_numbers(a, b):
return a * b
class MathTestCase(TestCase):
def test_multiply_numbers(self):
test_cases = [
(2, 3, 6), # (a, b, expected_result)
(0, 5, 0),
(-4, 2, -8),
(10, -3, -30),
]
for a, b, expected_result in test_cases:
result = multiply_numbers(a, b)
self.assertEqual(result, expected_result)
```
When running the test, Django's test runner will execute the **test_multiply_numbers** method for each test case, providing detailed feedback for each iteration.
Using parametric unit tests like this helps to reduce code duplication and makes it easier to add, modify, or remove test cases. It also provides a clear overview of which test cases pass or fail and enables you to test your code against a variety of scenarios.
### Testing in Django
Comprehensive testing is essential for Django applications:
**Test Client:**
```python
from django.test import TestCase, Client
from django.urls import reverse
class ViewTestCase(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user(
username='testuser',
password='testpass123'
)
def test_home_page(self):
response = self.client.get(reverse('home'))
self.assertEqual(response.status_code, 200)
def test_login_required(self):
response = self.client.get(reverse('protected'))
self.assertRedirects(response, '/login/')
```
**Mocking External Services:**
```python
from unittest.mock import patch, Mock
from django.test import TestCase
class EmailTestCase(TestCase):
@patch('myapp.views.send_email')
def test_send_notification(self, mock_send_email):
mock_send_email.return_value = True
response = self.client.post('/send-notification/')
self.assertTrue(mock_send_email.called)
```
**Factory Boy (Test Data Generation):**
```python
# Install: pip install factory_boy
import factory
from django.contrib.auth.models import User
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
username = factory.Sequence(lambda n: f"user{n}")
email = factory.LazyAttribute(lambda obj: f"{obj.username}@example.com")
# Usage in tests
user = UserFactory()
```
**Fixtures:**
```python
# fixtures/initial_data.json
[
{
"model": "myapp.book",
"pk": 1,
"fields": {
"title": "Test Book",
"author": 1
}
}
]
# In tests
class BookTestCase(TestCase):
fixtures = ['initial_data.json']
```
**Use Cases:**
1. **Unit Testing:** Test individual components in isolation
2. **Integration Testing:** Test component interactions
3. **API Testing:** Test REST API endpoints
4. **Performance Testing:** Test query performance and optimization
### Security Best Practices in Django
Security is paramount in web applications. Here are essential security practices:
**CSRF Protection:**
```python
from django.views.decorators.csrf import csrf_exempt, csrf_protect
# CSRF is enabled by default
# To exempt (use carefully):
@csrf_exempt
def my_view(request):
pass
```
**SQL Injection Prevention:**
```python
# BAD - Vulnerable to SQL injection
User.objects.extra(where=["username = '%s'" % username])
# GOOD - Use parameterized queries
User.objects.filter(username=username)
```
**XSS Prevention:**
```python
# In templates, always use:
{{ user_input|escape }}
# or
{% autoescape on %}
{{ user_input }}
{% endautoescape %}
```
**Password Hashing:**
```python
from django.contrib.auth.hashers import make_password, check_password
# Hash password
hashed = make_password('mypassword')
# Check password
is_valid = check_password('mypassword', hashed)
```
**Security Settings:**
```python
# settings.py
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
```
**Use Cases:**
1. **Authentication:** Secure user authentication and authorization
2. **Data Protection:** Protect sensitive data from unauthorized access
3. **Input Validation:** Prevent injection attacks
4. **HTTPS Enforcement:** Ensure secure communication
### Logging and Monitoring in Django
Effective logging and monitoring help track application behavior:
**Logging Configuration:**
```python
# settings.py
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {message}',
'style': '{',
},
},
'handlers': {
'file': {
'level': 'INFO',
'class': 'logging.FileHandler',
'filename': 'django.log',
'formatter': 'verbose',
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
},
'loggers': {
'django': {
'handlers': ['file', 'console'],
'level': 'INFO',
'propagate': True,
},
'myapp': {
'handlers': ['file', 'console'],
'level': 'DEBUG',
},
},
}
```
**Using Loggers:**
```python
import logging
logger = logging.getLogger(__name__)
def my_view(request):
logger.info('Processing request')
try:
# Your code
logger.debug('Debug information')
except Exception as e:
logger.error(f'Error occurred: {e}', exc_info=True)
```
**Sentry Integration:**
```python
# Install: pip install sentry-sdk
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
sentry_sdk.init(
dsn="your-sentry-dsn",
integrations=[DjangoIntegration()],
traces_sample_rate=1.0,
send_default_pii=True
)
```
**Use Cases:**
1. **Error Tracking:** Monitor and debug production errors
2. **Performance Monitoring:** Track slow queries and operations
3. **Audit Logging:** Log important user actions
4. **Debugging:** Detailed logs for troubleshooting
## FastAPI related topics:
### Dependency Injection in FastAPI
In FastAPI, dependency injection is a powerful feature that allows you to declare dependencies for your route handlers, making your code modular, testable, and reusable. Dependencies can be functions or classes that provide the necessary data or services required by a route. FastAPI automatically manages the injection of these dependencies when a request is processed.
Here's a simple example of dependency injection in FastAPI:
```
from fastapi import FastAPI, Depends
app = FastAPI()
# Dependency function
def get_query_parameter(q: str = None):
return q or "No query parameter provided."
# Route using the dependency
@app.get("/items/")
async def read_item(commons: str = Depends(get_query_parameter)):
return {"message": commons}
```
**get_query_parameter** is a dependency function that takes a query parameter **q** and returns its value or a default message if no parameter is provided. **Depends** is used to declare the dependency in the route handler (read_item). FastAPI will automatically execute the **get_query_parameter** function and inject its result into the commons parameter of the route handler. The route handler then uses the value provided by the dependency.
#### use cases
1. **Authentication:** You can use dependency injection to handle authentication. For example, a dependency function could verify an API key, OAuth token, or JWT token and provide user information to the route handler.
2. **Database Connections:** Dependency injection is useful for managing database connections. You can create a dependency that establishes a database connection and injects it into route handlers that require database access.
3. **Configuration Settings:** Injecting configuration settings (e.g., API keys, environment variables) into your route handlers allows for easy configuration changes without modifying the route functions.
4. **Logging:** Use dependency injection to inject a logging service into route handlers, enabling consistent logging across your application.
5. **Caching:** Dependencies can be employed for caching mechanisms. For instance, a dependency can check if the requested data is already cached and return it without hitting the actual data source.
6. **Request Validation:** You can create a dependency function to handle request validation, checking headers, parameters, and request bodies before the route handler is executed.
7. **Authorization:** Dependency injection can be used to handle authorization logic. A dependency function could check whether the authenticated user has the required permissions for a specific operation.
8. **Rate Limiting:** Implementing rate limiting is another use case. A dependency can check the rate limit for a user and prevent excessive requests.
### Dependency Ordering
**Dependency ordering** in FastAPI refers to the order in which dependencies are executed. FastAPI executes dependencies in a top-down order, meaning that dependencies listed first are executed before those listed later. Understanding dependency ordering is crucial when you have dependencies that depend on the results of other dependencies.
Here's an example to illustrate dependency ordering in FastAPI:
```
from fastapi import FastAPI, Depends
app = FastAPI()
# First dependency
async def common_parameters(q: str = None, skip: int = 0, limit: int = 10):
return {"q": q, "skip": skip, "limit": limit}
# Second dependency that depends on the result of the first one
async def query_processing(params: dict = Depends(common_parameters)):
# Some processing using the result of the first dependency
processed_query = f"Processed query: {params['q']}, Skip: {params['skip']}, Limit: {params['limit']}"
return {"processed_query": processed_query}
# Route using the dependencies
@app.get("/items/")
async def read_item(query_result: dict = Depends(query_processing)):
return query_result
```
**common_parameters** is the first dependency that provides common parameters like q, skip, and limit. **query_processing** is the second dependency that depends on the result of common_parameters. It performs some processing based on the parameters obtained from the first dependency. The route handler **read_item** depends on the result of **query_processing**. It uses the processed query result in the route response.
### Pydantic methods in FastAPI
In FastAPI, models are typically defined using Pydantic, a data validation and parsing library. Pydantic models in FastAPI can utilize various methods to customize their behavior. These methods help in validating, parsing, and processing the data before it is used in your application. Let's explore some of these methods with examples and discuss their use cases in real-world applications.
- @validator Decorator:
You can use the @validator decorator to define custom validation logic for a specific field.
```
from typing import List
from pydantic import BaseModel, validator
class Item(BaseModel):
name: str
price: float
@validator("price")
def validate_price(cls, value):
if value < 0:
raise ValueError("Price must be non-negative")
return round(value, 2)
```
- @root_validator Decorator:
Use Case: Use the @root_validator decorator to perform validation that involves multiple fields.
```
from pydantic import BaseModel, root_validator
class Item(BaseModel):
name: str
price: float
quantity: int
@root_validator
def validate_total_price(cls, values):
total_price = values['price'] * values['quantity']
if total_price > 100:
raise ValueError("Total price cannot exceed 100")
return values
```
### Decorators in FastAPI
In FastAPI, decorators are used to modify the behavior of functions or class methods. They are functions that take another function as an argument and extend or modify the behavior of that function. Decorators in FastAPI are often used for creating reusable components, handling dependencies, and adding extra functionality to route handlers or other parts of your application.
Let's start with a simple example of using decorators in FastAPI:
```
from fastapi import FastAPI, Depends
app = FastAPI()
# Decorator to log information before and after handling a request
def log_request(func):
async def wrapper(*args, **kwargs):
print("Request received")
response = await func(*args, **kwargs)
print("Request handled")
return response
return wrapper
# Using the decorator for a route
@app.get("/items/", dependencies=[Depends(log_request)])
async def read_items():
return {"message": "Items retrieved"}
```
#### Real-World Use Cases of decorators:
- **Authentication and Authorization**: To check if a user is authenticated before allowing access to certain routes.
- **Rate Limiting**: To limit the rate of requests to a specific route.
- **Logging and Monitoring**: To log information about incoming requests and responses.
- **Validation and Transformation**: To validate input data before processing it.
### WebSockets in FastAPI
WebSocket is a communication protocol that provides full-duplex communication channels over a single, long-lived connection. FastAPI supports WebSocket communication, allowing you to build real-time applications, such as chat applications, live updates, notifications, or collaborative editing. Let's go through a simple example of using WebSocket in FastAPI and then discuss potential use cases.
Now, let's create a simple WebSocket application:
```
from fastapi import FastAPI, WebSocket
app = FastAPI()
# WebSocket endpoint
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Message text was: {data}")
```
The **/ws** route is designated as a WebSocket endpoint. The **websocket_endpoint** function is called when a WebSocket connection is established. It accepts the WebSocket connection and enters a loop to continuously receive and send messages.
The **await websocket.receive_text()** line waits for incoming messages, and **await websocket.send_text()** sends a response back to the client.
#### Real-World Use Cases of websocket:
- **Real-Time Chat Applications:** Implementing real-time chat applications where users can send and receive messages instantly.
- **Live Updates and Notifications:** Providing live updates and notifications to users, such as news updates, sports scores, or social media notifications.
- **Real-Time Monitoring and Dashboards:** Building real-time monitoring dashboards that display live data, such as system metrics, financial data, or IoT sensor readings.
- **Financial Trading Platforms:** Developing financial trading platforms where real-time updates of stock prices, trades, and market data are crucial.
- **Job Processing and Notifications:** Providing real-time updates on the progress of long-running tasks or job processing.
### Asynchronous File Uploads
FastAPI supports asynchronous file uploads, allowing you to handle large file uploads efficiently. This is especially beneficial when dealing with web applications that need to process file uploads without blocking the server for other requests.
Now, let's create a FastAPI application with an asynchronous file upload endpoint:
```angular2html
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
# Asynchronous file upload endpoint
@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile = File(...)):
return {"filename": file.filename}
```
The **/uploadfile/** route is designated as an asynchronous file upload endpoint.
The **create_upload_file** function is called when a POST request is made to **/uploadfile/** with a file attached.
The **UploadFile** class from FastAPI's **File** module is used to handle file uploads asynchronously.
#### Real-World Use Cases of Asynchronous File Uploads:
- **Large File Uploads:** Allowing users to upload large files, such as images, videos, or documents, without causing timeouts or blocking other requests.
- **Image and Video Processing:** Processing images or videos uploaded by users asynchronously, such as generating thumbnails, applying filters, or transcoding videos.
- **Document Processing:** Handling document uploads, such as PDFs or text files, and processing them asynchronously, such as extracting text, generating previews, or converting formats.
- **Audio File Processing:** Uploading audio files for processing, such as extracting metadata, analyzing content, or converting formats.
### Security Headers
Security headers play a crucial role in web applications to enhance security by preventing certain types of attacks. FastAPI allows you to set security headers easily, providing protection against common web vulnerabilities.
Now, let's create a FastAPI application with custom security headers:
```
from fastapi import FastAPI
from fastapi.middleware.trustedhost import TrustedHostMiddleware
app = FastAPI()
# Middleware for trusted hosts
app.add_middleware(TrustedHostMiddleware, allowed_hosts=["example.com", "sub.example.com"])
# Middleware for security headers
@app.middleware("http")
async def add_security_headers(request, call_next):
response = await call_next(request)
# Set security headers
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
response.headers["Content-Security-Policy"] = "default-src 'self'"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["Referrer-Policy"] = "no-referrer"
return response
# Route to demonstrate security headers
@app.get("/")
async def read_root():
return {"message": "Welcome to the secure endpoint!"}
```
The **TrustedHostMiddleware** is used to enforce a list of allowed hostnames for better protection against HTTP Host header attacks.
The **add_security_headers** function is a middleware that sets various security headers in the HTTP response.
The security headers set include:
- **Strict-Transport-Security (HSTS):** Enforces the use of HTTPS for a specified duration.
- **Content-Security-Policy (CSP):** Defines content security policies to mitigate XSS attacks.
- **X-Content-Type-Options:** Prevents browsers from MIME-sniffing a response.
- **X-Frame-Options:** Prevents the browser from rendering a page in a frame.
- **X-XSS-Protection:** Enables a browser's built-in XSS protection.
- **Referrer-Policy:** Controls how much information is included in the Referer header.
Implementing these security headers is an essential part of securing web applications and protecting against a wide range of common web vulnerabilities. They contribute to creating a robust and secure environment for your users and help in maintaining compliance with security standards and best practices.
### Background Tasks
Background tasks in FastAPI allow you to execute functions asynchronously in the background, separate from the main request-response cycle. This is particularly useful for tasks that do not need an immediate response but can be performed in the background to improve the overall responsiveness of your application.
Now, let's create a FastAPI application with a background task:
```angular2html
from fastapi import FastAPI, BackgroundTasks
import time
app = FastAPI()
# Background task
def process_data(background_tasks: BackgroundTasks, data: str):
# Simulate a time-consuming task
time.sleep(5)
print(f"Processing data: {data}")
# Route using the background task
@app.post("/process_data/")
async def create_process_data(data: str, background_tasks: BackgroundTasks):
background_tasks.add_task(process_data, data)
return {"message": "Data processing started in the background"}
```
The **process_data** function is a background task that simulates a time-consuming operation. In a real-world scenario, this could be a task like sending emails, processing images, updating records in a database, etc.
The **create_process_data** route uses the **BackgroundTasks** dependency to add the **process_data** background task. The route returns a response immediately, and the background task runs asynchronously.
### Middleware in FastAPI
Middleware in FastAPI is a function that works with every request before it is processed by any specific path operation and with every response before returning it.
You can add middleware to FastAPI applications using the **@app.middleware("http")** decorator on top of a function. The middleware function receives the request and a **call_next** function that will receive the request as a parameter. It then passes the request to be processed by the rest of the application and takes the response generated by the application, allowing you to modify it further before returning it
Here's a simple example of a middleware that adds a custom header to the response to measure the processing time:
```
from fastapi import FastAPI, Request
import time
app = FastAPI()
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
return response
```
#### Real-World Use Cases of Middlewares:
- Adding custom functionality to the request-response cycle without disrupting the core framework
- Separating concerns and keeping API endpoints focused on their core tasks while handling shared operations through middleware components
- Enabling the addition of authentication, error handling, data transformation, and more by extending the functionality of APIs in unique ways
In addition to built-in middleware, FastAPI allows the creation of custom middleware to extend its capabilities beyond the built-in options, offering a powerful way to enhance the functionality of APIs
### Permissions in FastAPI
In FastAPI, permissions are used to control access to different parts of an application based on the user's role or other factors.
Here's a simple example of using **permissions** in FastAPI to restrict access to a specific route based on the user's role:
```
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from typing import Optional
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
async def get_current_user(token: str = Depends(oauth2_scheme)):
# Implement logic to get user details from the token
# Example: decode the token and fetch user details from the database
# Return the user object if the token is valid, else raise an HTTPException
return {"username": "user1", "role": "admin"}
def has_permission(user: dict = Depends(get_current_user), required_role: Optional[str] = None):
if required_role and user["role"] != required_role:
raise HTTPException(status_code=403, detail="Permission denied")
return user
@app.get("/admin")
async def admin_route(current_user: dict = Depends(has_permission)):
return {"message": "Welcome admin!"}
```
**Permissions** in FastAPI are crucial for implementing role-based access control (RBAC) and ensuring that users can only access the resources they are authorized to. Some real-world use cases include:
- **Role-Based Access Control (RBAC):** Restricting access to certain routes or data based on the user's role, such as admin, user, or manager
- **Row-Level Security:** Implementing fine-grained permissions based on the state or attributes of specific resources, such as allowing users to view, edit, or retract scientific papers based on the submission process state
- **Custom Business Logic:** Enforcing custom business rules and access restrictions based on specific application requirements, such as allowing users to modify only their own items
By using permissions, FastAPI applications can ensure that sensitive data and functionality are protected, and that users are granted appropriate access based on their roles and other contextual factors.
### Custom Validators in FastAPI
Custom validators in FastAPI can be implemented using **Pydantic**, which is used for data validation in FastAPI. Pydantic allows you to define custom validation rules for request data. Here's a simple example of using custom validators in FastAPI with Pydantic:
```
from fastapi import FastAPI
from pydantic import BaseModel, Field, ValidationError
app = FastAPI()
class Item(BaseModel):
name: str
price: float
@property
def price_must_be_positive(cls):
if cls.price <= 0:
raise ValueError('price must be positive')
@app.post("/items/")
async def create_item(item: Item):
try:
item.price_must_be_positive
except ValueError as e:
return {"error": str(e)}
return {"item_name": item.name, "item_price": item.price}
```
In this example, a custom validator **price_must_be_positive** is defined within the **Item** class to ensure that the price of an item is positive. If the validation fails, a **ValueError** is raised, and an appropriate error message is returned.
#### Use Cases in Real-World Applications
Custom validators in FastAPI can be used in various real-world scenarios, such as:
- **Data Integrity Checks:** Ensuring that the incoming data meets specific business rules or constraints, such as validating the format of user input, ensuring the correctness of submitted data, or enforcing specific conditions on the input data
- **Complex Data Validation:** Implementing custom validation logic for complex data structures or interdependent fields, such as validating relationships between different fields or performing custom checks based on multiple input parameters
- **Integration with External Services:** Validating data against external services or APIs, such as verifying the correctness of input data before making requests to external systems or services
By using custom validators, FastAPI applications can enforce specific data validation rules tailored to their business requirements, ensuring the integrity and correctness of incoming data.
### FastAPI BaseSettings
In FastAPI, **BaseSettings** is a class provided by **Pydantic** for managing application settings and environment variables. It allows you to define a settings model with default values and data types, and then load and use these settings throughout your application. Here's a simple example of using **BaseSettings** in FastAPI:
```
from fastapi import FastAPI
from pydantic import BaseSettings
class Settings(BaseSettings):
app_name: str = "Awesome API"
admin_email: str
items_per_user: int = 50
settings = Settings()
app = FastAPI()
@app.get("/info")
async def info():
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user
}
```
In this example, the **Settings** class inherits from **BaseSettings** and defines the application settings, including default values and data types. These settings can then be used throughout the FastAPI application.
#### The use of **BaseSettings** in FastAPI has several real-world applications, including:
- **Centralized Settings Management:** Providing a centralized and structured way to manage application settings, including environment-specific configurations, database credentials, API keys, and other parameters
- **Environment Variable Handling:** Facilitating the loading and management of environment variables, allowing for easy configuration of application behavior across different deployment environments such as development, testing, and production
By using **BaseSettings**, FastAPI applications can maintain a clear and consistent approach to managing settings and configurations, ensuring that the application behaves predictably across different environments and deployment scenarios.
### Dependency Caching
In FastAPI, dependency caching refers to the ability to cache the result of a dependency function so that it is not re-evaluated for each request. This can be useful for improving performance and reducing redundant computations. Here's a simple example of using dependency caching in FastAPI:
```
from fastapi import FastAPI, Depends
from functools import lru_cache
app = FastAPI()
@lru_cache
def expensive_operation():
# Simulate an expensive operation
return "result"
async def get_cached_result(result: str = Depends(expensive_operation)):
return {"cached_result": result}
@app.get("/cached")
async def cached_endpoint(response: dict = Depends(get_cached_result)):
return response
```
In this example, the **@lru_cache** decorator is used to cache the result of the **expensive_operation** function. The **get_cached_result** function then depends on the cached result of **expensive_operation**, and the cached result is used in the **/cached** endpoint.
#### Use Cases in Real-World Applications
Dependency caching in FastAPI has several real-world applications, including:
- **Performance Optimization:** Caching the result of computationally expensive operations, such as database queries, external API calls, or complex calculations, to improve response times and reduce server load
- **Rate Limiting:** Using caching to enforce rate limits on certain operations or endpoints by storing and checking the frequency of requests within a specific time frame
- **External Service Integration:** Caching the results of requests to external services or APIs to reduce latency and minimize the impact of service outages or slowdowns
By leveraging dependency caching, FastAPI applications can efficiently manage the results of dependencies, reduce redundant computations, and enhance overall system performance.
### Rate Limiting
**Rate limiting** in FastAPI allows you to restrict the number of requests a client can make to an API within a given time frame. This can be achieved using third-party packages such as **fastapi-limiter**. Here's a simple example of using rate limiting in FastAPI with fastapi-limiter:
```
from fastapi import FastAPI
from fastapi_limiter import FastAPILimiter
from fastapi_limiter.depends import RateLimiter
app = FastAPI()
# Initialize the rate limiter
FastAPILimiter.init()
@app.get("/limited")
@RateLimiter(times=5, seconds=60)
async def limited_endpoint():
return {"message": "This endpoint is rate-limited"}
```
In this example, the **fastapi-limiter** package is used to apply rate limiting to the **/limited** endpoint, allowing a maximum of 5 requests per 60 seconds.
#### Use Cases in Real-World Applications
Rate limiting in FastAPI has several real-world applications, including:
- **Protection Against Abuse:** Preventing abuse and misuse of APIs by limiting the number of requests from a single client within a specific time period, safeguarding the API from excessive traffic and potential denial-of-service attacks
- **Throttling:** Managing the flow of requests to ensure that the API server is not overwhelmed by a large number of requests, maintaining system stability and preventing performance degradation during peak usage periods
By implementing rate limiting, FastAPI applications can effectively manage and control the volume of incoming requests, ensuring the stability, security, and fair usage of the API.
### Cache in FastAPI
FastAPI provides various options for caching responses and function results, including third-party packages such as **fastapi-cache**. This tool allows you to cache FastAPI responses and function results, with support for backends like Redis, Memcached, and Amazon DynamoDB
Here's a simple example of using **fastapi-cache** to cache a FastAPI response:
```
from fastapi import FastAPI
from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend
from fastapi_cache.decorator import cache
import aioredis
app = FastAPI()
# Initialize the cache with Redis as the backend
@app.on_event("startup")
async def startup():
redis = await aioredis.create_redis_pool('redis://localhost')
FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache")
# Cache the response of the endpoint
@app.get("/")
@cache()
async def cached_endpoint():
return {"message": "This response is cached"}
```
#### Use Cases in Real-World Applications
Caching in FastAPI has several real-world applications, including:
- **Performance Optimization:** Caching responses to reduce the computational load and improve the response time of frequently accessed endpoints, enhancing the overall performance of the API
- **Database Query Results:** Caching the results of database queries to minimize the load on the database and improve the responsiveness of data retrieval operations
- **External API Responses:** Caching responses from external APIs to reduce latency and minimize the impact of external service outages or slowdowns, ensuring a more reliable and responsive API
By leveraging caching in FastAPI, applications can efficiently manage and optimize the handling of responses and data, leading to improved performance and a better user experience.
### Custom Exceptions in FastAPI
Custom exceptions in FastAPI allow you to define and handle application-specific errors in a structured manner. You can create custom exception classes and handle them using FastAPI's exception handling mechanisms. Here's a simple example of using custom exceptions in FastAPI:
```angular2html
from fastapi import FastAPI, HTTPException
app = FastAPI()
class CustomException(Exception):
def __init__(self, detail: str):
self.detail = detail
@app.get("/custom_exception")
async def custom_exception_endpoint():
raise CustomException(detail="Custom exception message")
```
In this example, a custom exception class **CustomException** is defined, and it is raised within the **/custom_exception** endpoint. You can then handle this custom exception using FastAPI's exception handling mechanisms.
#### Use Cases in Real-World Applications
Custom exceptions in FastAPI have several real-world applications, including:
- **Application-Specific Errors:** Defining custom exceptions to represent specific error conditions or business logic failures within the application, providing a structured way to handle and communicate these errors to clients
- **Error Abstraction:** Abstracting and encapsulating complex error handling logic into custom exception classes, promoting code modularity and maintainability by centralizing error management
- **Consistent Error Responses:** Standardizing error responses by using custom exceptions to represent different error scenarios, ensuring a consistent and predictable API behavior for clients
By utilizing custom exceptions, FastAPI applications can effectively manage and communicate application-specific errors, enhancing the robustness and reliability of the API.
### Optimization techniques in FastAPI
FastAPI is a high-performance web framework for building REST APIs in Python. It provides various optimization techniques to enhance the performance of applications.
Some of the Optimization Techniques
- **Asynchronous Programming:** Utilize asynchronous functions to handle heavy request payloads and I/O bound operations
- **Use of Pydantic for Data Validation:** Leverage Pydantic models for data validation and conversion
- **Dependency Caching:** Reuse dependencies and cache their results within a request's scope to avoid recalculating them
- **BackgroundTasks:** Use BackgroundTasks for handling tasks that can be run in the background to improve response times
- **Optimizing CPU Intensive Tasks:** Send CPU intensive tasks to workers in another process for optimization
- **Caching Responses:** Implement response caching to store the results of expensive computations and serve them directly for subsequent identical requests
- **Optimizing JSON Responses:** Utilize FastAPI's JSON response classes for efficient serialization and deserialization of JSON data
- **Asynchronous Database Operations:** Use asynchronous database drivers and queries to optimize database interactions and improve overall request handling
- **Project Structure:** Organize the project structure following best practices to enhance maintainability and performance
- **Use of APIRouter:** Utilize APIRouter to modularize and organize route handling, especially for larger applications with multiple files
- **Observability Tools:** Employ observability tools like New Relic to gain insights into performance issues and optimize FastAPI applications
- **Custom Base Model:** Implement a custom base model from the beginning to ensure consistency and efficiency in data handling
- **Use of Pydantic's BaseSettings for Configs:** Leverage Pydantic's BaseSettings for efficient management of application configurations
- **Optimizing File Handling:** Save files in chunks to optimize file handling and improve overall performance
- **Avoid Unnecessary Asynchronous Operations:** Do not make routes async if there are only blocking I/O operations, as unnecessary async operations can impact performance
- **Careful Usage of Dynamic Pydantic Fields:** Exercise caution when using dynamic Pydantic fields to avoid potential performance impacts
- **Dependency Calls Caching:** Cache dependency calls to improve performance by reusing previously calculated results
- **Documentation:** Ensure comprehensive documentation to facilitate understanding and usage, contributing to efficient development and performance
### Concurrency and Parallelism In FastAPI
Concurrency and parallelism are essential concepts in FastAPI for handling multiple tasks simultaneously and improving performance. Concurrency refers to the ability to execute multiple tasks in overlapping time periods, while parallelism involves executing multiple tasks simultaneously.
Real-world use cases of concurrency and parallelism in FastAPI include:
- **Handling Multiple Requests:** Concurrency allows the server to handle multiple incoming requests simultaneously, improving the responsiveness of the application.
- **Asynchronous I/O Operations:** Concurrency is beneficial for I/O-bound operations such as reading from databases, making API calls, or accessing files, as it allows the server to perform other tasks while waiting for I/O operations to complete.
- **Parallel Processing:** For CPU-bound tasks like data processing or machine learning computations, parallelism can be used to execute multiple tasks concurrently, leveraging multi-core processors for improved performance.
- **Real-time Data Streaming:** Concurrency enables the server to handle real-time data streaming, such as sending continuous updates to clients while simultaneously processing other tasks.
- **Background Tasks:** Asynchronous operations are useful for executing background tasks, such as sending emails, processing notifications, or performing periodic maintenance tasks without blocking the main application flow
### API Documentation Best Practices
FastAPI automatically generates interactive API documentation, but following best practices enhances its usefulness:
**Customizing OpenAPI Schema:**
```python
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
app = FastAPI(
title="My API",
description="A comprehensive API for managing resources",
version="1.0.0",
)
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="My API",
version="1.0.0",
description="Custom API documentation",
routes=app.routes,
)
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
```
**Adding Examples and Descriptions:**
```python
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str = Field(..., description="Item name", example="Widget")
price: float = Field(..., gt=0, description="Item price in USD", example=29.99)
description: str = Field(None, description="Item description", example="A useful widget")
@app.post("/items/", response_model=Item, summary="Create an item",
description="Create a new item in the system",
response_description="The created item")
async def create_item(item: Item):
"""Create a new item.
- **name**: The name of the item
- **price**: The price must be greater than 0
- **description**: Optional description
"""
return item
```
**Tagging and Grouping:**
```python
@app.post("/users/", tags=["users"], summary="Create user")
async def create_user(user: User):
pass
@app.get("/users/", tags=["users"], summary="List users")
async def list_users():
pass
@app.post("/items/", tags=["items"], summary="Create item")
async def create_item(item: Item):
pass
```
**Use Cases:**
1. **API Discovery:** Help developers understand available endpoints
2. **Testing:** Interactive docs allow testing endpoints directly
3. **Documentation:** Automatic documentation reduces maintenance
4. **Client Generation:** OpenAPI schema can generate client libraries
### Testing in FastAPI
Comprehensive testing ensures FastAPI applications work correctly:
**Test Client:**
```python
from fastapi.testclient import TestClient
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Hello World"}
# Tests
def test_read_root():
client = TestClient(app)
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello World"}
```
**Testing with Dependencies:**
```python
from fastapi import Depends
from unittest.mock import Mock
def get_db():
# Mock database dependency
return Mock()
@app.get("/items/")
def read_items(db=Depends(get_db)):
return db.query_items()
def test_read_items():
client = TestClient(app)
response = client.get("/items/")
assert response.status_code == 200
```
**Async Testing:**
```python
import pytest
from httpx import AsyncClient
@pytest.mark.asyncio
async def test_async_endpoint():
async with AsyncClient(app=app, base_url="http://test") as ac:
response = await ac.get("/async-endpoint/")
assert response.status_code == 200
```
**Use Cases:**
1. **Unit Testing:** Test individual endpoints and functions
2. **Integration Testing:** Test complete request/response cycles
3. **API Contract Testing:** Ensure API contracts are maintained
4. **Performance Testing:** Test endpoint performance under load
### Security Best Practices in FastAPI
Security is critical for API applications:
**Authentication with JWT:**
```python
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
return username
@app.get("/protected/")
async def protected_route(current_user: str = Depends(get_current_user)):
return {"user": current_user}
```
**Input Validation:**
```python
from pydantic import BaseModel, validator, EmailStr
class UserCreate(BaseModel):
email: EmailStr
password: str
@validator('password')
def validate_password(cls, v):
if len(v) < 8:
raise ValueError('Password must be at least 8 characters')
return v
```
**Rate Limiting:**
```python
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@app.get("/api/")
@limiter.limit("5/minute")
async def limited_endpoint(request: Request):
return {"message": "This is rate limited"}
```
**CORS Configuration:**
```python
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://example.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
```
**Use Cases:**
1. **Authentication:** Secure API access with tokens
2. **Authorization:** Control access to resources
3. **Input Sanitization:** Prevent injection attacks
4. **Rate Limiting:** Prevent abuse and ensure fair usage
### Logging and Monitoring in FastAPI
Effective logging and monitoring help track API behavior:
**Structured Logging:**
```python
import logging
import sys
from pythonjsonlogger import jsonlogger
# Configure JSON logger
logHandler = logging.StreamHandler(sys.stdout)
formatter = jsonlogger.JsonFormatter()
logHandler.setFormatter(formatter)
rootLogger = logging.getLogger()
rootLogger.addHandler(logHandler)
rootLogger.setLevel(logging.INFO)
logger = logging.getLogger(__name__)
@app.get("/items/")
async def get_items():
logger.info("Fetching items", extra={"endpoint": "/items/"})
return {"items": []}
```
**Request Logging Middleware:**
```python
import time
from fastapi import Request
@app.middleware("http")
async def log_requests(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
logger.info(
"Request processed",
extra={
"method": request.method,
"url": str(request.url),
"status_code": response.status_code,
"process_time": process_time,
}
)
return response
```
**Prometheus Metrics:**
```python
# Install: pip install prometheus-fastapi-instrumentator
from prometheus_fastapi_instrumentator import Instrumentator
Instrumentator().instrument(app).expose(app)
```
**Use Cases:**
1. **Error Tracking:** Monitor and debug production errors
2. **Performance Monitoring:** Track response times and throughput
3. **Audit Logging:** Log important API operations
4. **Analytics:** Understand API usage patterns
## General Topics:
### REST API Design Principles
Following REST principles ensures APIs are intuitive and maintainable:
**Resource-Based URLs:**
```python
# Good
GET /api/users/ # List users
POST /api/users/ # Create user
GET /api/users/123/ # Get user
PUT /api/users/123/ # Update user
DELETE /api/users/123/ # Delete user
# Bad
GET /api/getUser/123
POST /api/createUser
```
**HTTP Status Codes:**
```python
from fastapi import status
@app.post("/users/", status_code=status.HTTP_201_CREATED)
async def create_user(user: User):
return user
@app.get("/users/", status_code=status.HTTP_200_OK)
async def list_users():
return []
@app.get("/users/999/", status_code=status.HTTP_404_NOT_FOUND)
async def get_user(user_id: int):
raise HTTPException(status_code=404, detail="User not found")
```
**Versioning:**
```python
# URL versioning
@app.get("/v1/users/")
async def list_users_v1():
pass
@app.get("/v2/users/")
async def list_users_v2():
pass
# Header versioning
@app.get("/users/", headers={"API-Version": "v1"})
async def list_users():
pass
```
**Pagination:**
```python
from fastapi import Query
@app.get("/items/")
async def list_items(
skip: int = Query(0, ge=0),
limit: int = Query(10, ge=1, le=100)
):
return {"items": [], "skip": skip, "limit": limit}
```
**Use Cases:**
1. **API Consistency:** Standard patterns make APIs easier to use
2. **Scalability:** RESTful design supports growth
3. **Maintainability:** Clear structure simplifies updates
4. **Developer Experience:** Intuitive APIs reduce learning curve
### Deployment and DevOps
#### Docker and Containerization
Containerization ensures consistent deployment across environments:
**Dockerfile for FastAPI:**
```dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
```
**Dockerfile for Django:**
```dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN python manage.py collectstatic --noinput
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"]
```
**Docker Compose:**
```yaml
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
depends_on:
- db
db:
image: postgres:15
environment:
- POSTGRES_DB=mydb
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
```
**Use Cases:**
1. **Environment Consistency:** Same environment in dev, staging, production
2. **Isolation:** Applications don't interfere with each other
3. **Scalability:** Easy to scale containers horizontally
4. **CI/CD Integration:** Containers work seamlessly with CI/CD pipelines
#### CI/CD Pipelines
Automated testing and deployment improve development workflow:
**GitHub Actions Example:**
```yaml
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install pytest
- name: Run tests
run: pytest
- name: Run linter
run: |
pip install black flake8
black --check .
flake8 .
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Deploy to production
run: |
# Deployment commands
```
**GitLab CI Example:**
```yaml
# .gitlab-ci.yml
stages:
- test
- deploy
test:
stage: test
image: python:3.11
script:
- pip install -r requirements.txt
- pytest
- black --check .
- flake8 .
deploy:
stage: deploy
script:
- echo "Deploying to production"
only:
- main
```
**Use Cases:**
1. **Automated Testing:** Run tests on every commit
2. **Code Quality:** Enforce code standards automatically
3. **Deployment Automation:** Deploy to production automatically
4. **Rollback Capability:** Easy to revert to previous versions
#### Environment Management
Managing different environments (dev, staging, production) effectively:
**Environment Variables:**
```python
# .env
DATABASE_URL=postgresql://user:pass@localhost/db
SECRET_KEY=your-secret-key
DEBUG=True
# settings.py
import os
from dotenv import load_dotenv
load_dotenv()
DATABASE_URL = os.getenv("DATABASE_URL")
SECRET_KEY = os.getenv("SECRET_KEY")
DEBUG = os.getenv("DEBUG", "False") == "True"
```
**Configuration Management:**
```python
# config.py
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str
secret_key: str
debug: bool = False
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
settings = Settings()
```
**Use Cases:**
1. **Security:** Keep secrets out of code
2. **Flexibility:** Different configs for different environments
3. **Maintainability:** Centralized configuration management
4. **Compliance:** Meet security and compliance requirements
## Interview Preparation questions
This guide provides explanations, practical examples, questions, answers, and Python code snippets to solidify your understanding. Let's dive in!
### PostgreSQL Querying
- **Why Raw SQL?**
While ORMs (like SQLAlchemy or Django ORM) are incredibly useful for rapid development and abstraction, understanding raw SQL is crucial for:
* **Performance Tuning:** Writing highly optimized queries that an ORM might not generate.
* **Complex Reporting:** Crafting intricate queries involving multiple joins, subqueries, window functions, or CTEs.
* **Debugging:** Understanding the exact SQL being executed by an ORM or identifying database-level issues.
* **Database Features:** Leveraging specific PostgreSQL features not directly exposed by all ORMs (e.g., specific index types, extensions).
* **Legacy Systems:** Working with systems that may not use an ORM.
- **Core SQL Concepts Review**
Before diving into questions, let's quickly recap essential SQL commands:
* `SELECT`: Retrieves data from one or more tables.
* `FROM`: Specifies the table(s) to query.
* `WHERE`: Filters rows based on specified conditions.
* `JOIN` (`INNER`, `LEFT`, `RIGHT`, `FULL OUTER`): Combines rows from two or more tables based on a related column.
* `GROUP BY`: Groups rows that have the same values in specified columns into a summary row. Often used with aggregate functions (`COUNT`, `MAX`, `MIN`, `SUM`, `AVG`).
* `HAVING`: Filters groups based on a specified condition (used *after* `GROUP BY`).
* `ORDER BY`: Sorts the result set based on specified columns (`ASC`, `DESC`).
* `LIMIT`: Restricts the number of rows returned.
* `OFFSET`: Skips a specified number of rows before starting to return rows (often used with `LIMIT` for pagination).
* `INSERT INTO`: Adds new rows to a table.
* `UPDATE`: Modifies existing rows in a table.
* `DELETE FROM`: Removes rows from a table.
* **Transactions:** (`BEGIN`, `COMMIT`, `ROLLBACK`) Ensuring atomicity of operations.
* **Indexes:** Data structures that improve the speed of data retrieval operations. Common types: B-tree (default), Hash, GiST, GIN.
* **Subqueries:** Queries nested inside another query.
* **Common Table Expressions (CTEs):** (`WITH ... AS ...`) Temporary, named result sets you can reference within a single statement. Improves readability for complex queries.
* **Window Functions:** Perform calculations across a set of table rows that are somehow related to the current row (e.g., ranking, running totals).
- **Example Schema**
We'll use the following simple schema for our practice questions:
```sql
-- Departments Table
CREATE TABLE departments (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
location VARCHAR(100)
);
-- Employees Table
CREATE TABLE employees (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE,
salary DECIMAL(10, 2) CHECK (salary > 0),
hire_date DATE DEFAULT CURRENT_DATE,
department_id INTEGER REFERENCES departments(id) ON DELETE SET NULL -- Foreign key
);
-- Projects Table
CREATE TABLE projects (
id SERIAL PRIMARY KEY,
name VARCHAR(150) NOT NULL UNIQUE,
start_date DATE,
deadline DATE
);
-- Employee-Project Junction Table (Many-to-Many)
CREATE TABLE employee_projects (
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
project_id INTEGER REFERENCES projects(id) ON DELETE CASCADE,
role VARCHAR(50),
PRIMARY KEY (employee_id, project_id) -- Composite primary key
);
-- Sample Data (Conceptual)
INSERT INTO departments (name, location) VALUES
('Engineering', 'Building A'), ('Sales', 'Building B'), ('HR', 'Building A'), ('Marketing', 'Building B');
INSERT INTO employees (name, email, salary, department_id) VALUES
('Alice', 'alice@company.com', 90000, 1),
('Bob', 'bob@company.com', 85000, 1),
('Charlie', 'charlie@company.com', 70000, 2),
('David', 'david@company.com', 72000, 2),
('Eve', 'eve@company.com', 110000, 1),
('Frank', 'frank@company.com', 60000, 3),
('Grace', 'grace@company.com', 95000, NULL); -- No department assigned yet
INSERT INTO projects (name, start_date, deadline) VALUES
('Project Alpha', '2023-01-15', '2023-12-31'),
('Project Beta', '2023-03-01', '2024-06-30'),
('Project Gamma', '2023-07-01', NULL);
INSERT INTO employee_projects (employee_id, project_id, role) VALUES
(1, 1, 'Developer'), (1, 2, 'Lead Developer'),
(2, 1, 'Developer'),
(3, 2, 'Sales Lead'),
(5, 2, 'Tech Lead'),
(5, 3, 'Architect');
```
- **Practice Questions & Answers (SQL)**
**Q1: Basic Retrieval**
* **Question:** Find the names and salaries of all employees earning more than $80,000.
* **Answer (SQL):**
```sql
SELECT name, salary
FROM employees
WHERE salary > 80000;
```
**Q2: `ORDER BY` and `LIMIT`**
* **Question:** Find the names and hire dates of the 3 most recently hired employees.
* **Answer (SQL):**
```sql
SELECT name, hire_date
FROM employees
ORDER BY hire_date DESC
LIMIT 3;
```
**Q3: `JOIN`**
* **Question:** List the names of all employees and their corresponding department names. Only include employees who are assigned to a department.
* **Answer (SQL):**
```sql
SELECT e.name AS employee_name, d.name AS department_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.id;
```
**Q4: `LEFT JOIN`**
* **Question:** List all employee names and their department names. Include employees even if they are not currently assigned to a department.
* **Answer (SQL):**
```sql
SELECT e.name AS employee_name, d.name AS department_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;
```
* **Explanation:** `LEFT JOIN` ensures all rows from the *left* table (`employees`) are included. If there's no match in the *right* table (`departments`), the columns from the right table will be `NULL`.
**Q5: `GROUP BY` and Aggregate Functions**
* **Question:** Find the number of employees in each department. Display the department name and the count.
* **Answer (SQL):**
```sql
SELECT d.name AS department_name, COUNT(e.id) AS employee_count
FROM departments d
LEFT JOIN employees e ON d.id = e.department_id
GROUP BY d.name
ORDER BY employee_count DESC;
```
* **Note:** Using `LEFT JOIN` here ensures departments with zero employees are also listed (with a count of 0). Using `COUNT(e.id)` correctly counts employees, ignoring `NULL`s from the `LEFT JOIN`.
**Q6: `GROUP BY` and `HAVING`**
* **Question:** Find the departments with an average employee salary greater than $85,000. Display the department name and the average salary.
* **Answer (SQL):**
```sql
SELECT d.name AS department_name, AVG(e.salary) AS average_salary
FROM employees e
JOIN departments d ON e.department_id = d.id
GROUP BY d.name
HAVING AVG(e.salary) > 85000;
```
* **Explanation:** `WHERE` filters rows *before* aggregation, while `HAVING` filters groups *after* aggregation.
**Q7: Subquery in `WHERE` Clause**
* **Question:** Find the names of employees who earn more than the overall average salary of all employees.
* **Answer (SQL):**
```sql
SELECT name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
```
**Q8: Subquery in `SELECT` Clause (Correlated Subquery)**
* **Question:** For each employee, show their name, salary, and the average salary of their department.
* **Answer (SQL):**
```sql
SELECT
e.name AS employee_name,
e.salary,
(SELECT AVG(salary) FROM employees e2 WHERE e2.department_id = e.department_id) AS department_avg_salary
FROM employees e;
```
* **Note:** Correlated subqueries can sometimes be less performant than joins or window functions for this type of task, especially on large tables.
**Q9: Many-to-Many Join**
* **Question:** List all employees working on 'Project Beta'. Show employee name and their role on the project.
* **Answer (SQL):**
```sql
SELECT e.name AS employee_name, ep.role
FROM employees e
JOIN employee_projects ep ON e.id = ep.employee_id
JOIN projects p ON ep.project_id = p.id
WHERE p.name = 'Project Beta';
```
**Q10: Common Table Expression (CTE)**
* **Question:** Find departments where the maximum salary is greater than $100,000. Use a CTE to first find the maximum salary per department.
* **Answer (SQL):**
```sql
WITH DepartmentMaxSalary AS (
SELECT
department_id,
MAX(salary) AS max_salary
FROM employees
WHERE department_id IS NOT NULL
GROUP BY department_id
)
SELECT d.name AS department_name, dms.max_salary
FROM departments d
JOIN DepartmentMaxSalary dms ON d.id = dms.department_id
WHERE dms.max_salary > 100000;
```
**Q11: Window Function (`RANK`)**
* **Question:** Rank employees within each department based on their salary (highest salary gets rank 1). Display department name, employee name, salary, and rank.
* **Answer (SQL):**
```sql
SELECT
d.name AS department_name,
e.name AS employee_name,
e.salary,
RANK() OVER (PARTITION BY e.department_id ORDER BY e.salary DESC) AS salary_rank
FROM employees e
JOIN departments d ON e.department_id = d.id
ORDER BY d.name, salary_rank;
```
* **Explanation:** `PARTITION BY e.department_id` divides the rows into partitions (one for each department). `ORDER BY e.salary DESC` sorts rows within each partition. `RANK()` assigns the rank based on this order. `DENSE_RANK()` is similar but doesn't leave gaps for ties. `ROW_NUMBER()` assigns unique numbers regardless of ties.
**Q12: Data Modification (`INSERT`, `UPDATE`, `DELETE`)**
* **Question:**
1. Add a new department 'Finance' in 'Building C'.
2. Update 'Charlie's salary to $75,000.
3. Remove the employee named 'Frank'.
* **Answer (SQL):**
```sql
-- 1. Insert new department
INSERT INTO departments (name, location)
VALUES ('Finance', 'Building C');
-- 2. Update Charlie's salary (assuming email is unique identifier if name isn't)
UPDATE employees
SET salary = 75000
WHERE email = 'charlie@company.com'; -- Use a unique identifier like id or email
-- 3. Delete Frank (use a unique identifier)
DELETE FROM employees
WHERE name = 'Frank' AND email = 'frank@company.com'; -- Be specific
```
* **Caution:** `DELETE` operations are permanent. Always be careful and use specific `WHERE` clauses, preferably targeting primary keys or unique constraints. Transactions (`BEGIN`, `COMMIT`, `ROLLBACK`) are essential for safety.
**Q13: Indexing Concept**
* **Question:** When would you add an index to the `employees` table? Explain your reasoning for choosing a specific column(s).
* **Answer (Conceptual):**
* You would add an index to columns frequently used in `WHERE` clauses, `JOIN` conditions, or `ORDER BY` clauses to speed up data retrieval.
* **`id` (Primary Key):** PostgreSQL automatically creates a unique B-tree index on primary keys. This is essential for fast lookups and enforcing uniqueness.
* **`department_id` (Foreign Key):** Definitely index this column. It's used in `JOIN` operations with the `departments` table and potentially in `WHERE` clauses (e.g., `WHERE department_id = X`). A B-tree index is suitable here.
* **`email` (Unique Constraint):** PostgreSQL often automatically creates an index for unique constraints (typically B-tree) to efficiently check for duplicates during inserts/updates and speed up lookups based on email.
* **`name`:** If you frequently search or sort employees by name (`WHERE name = '...'` or `ORDER BY name`), an index on this column could be beneficial. A B-tree index works well for range queries and exact matches.
* **`salary`:** If you often filter or sort by salary (`WHERE salary > X`, `ORDER BY salary`), indexing this column can improve performance. A B-tree index is appropriate.
* **`hire_date`:** Similar to `salary`, if filtering or sorting by `hire_date` is common, an index helps.
* **Composite Index:** If you frequently filter by `department_id` AND `salary` together (e.g., `WHERE department_id = X AND salary > Y`), a composite index on `(department_id, salary)` might be more efficient than two separate indexes. The order matters.
* **Trade-offs:** Indexes speed up reads (`SELECT`) but slow down writes (`INSERT`, `UPDATE`, `DELETE`) because the index also needs to be updated. They also consume disk space. Only index columns where the performance benefit outweighs the cost.
**Q14: Transaction Concept**
* **Question:** Imagine you need to transfer an employee ('Alice') from 'Engineering' to 'Sales' and simultaneously update her role on 'Project Alpha' to 'Consultant'. Why is it important to use a transaction for these operations?
* **Answer (Conceptual):**
* A transaction groups multiple SQL statements into a single, atomic unit of work. It guarantees **ACID** properties (Atomicity, Consistency, Isolation, Durability).
* **Atomicity:** In this scenario, both updating the employee's `department_id` and updating their role in `employee_projects` must succeed together, or neither should happen. If the first update succeeds but the second fails (e.g., due to a constraint violation or connection drop), a transaction ensures the first update is rolled back, leaving the database in its original consistent state. Without a transaction, you could end up with inconsistent data (Alice in Sales but still listed as 'Developer' on the project, or vice-versa depending on the order and failure point).
* **Consistency:** The transaction ensures the database transitions from one valid state to another, respecting all constraints.
* **Isolation:** If other users are querying the data concurrently, a transaction ensures they see the data either *before* the changes or *after* both changes are committed, preventing them from seeing intermediate, inconsistent states (e.g., Alice listed in Sales but still having her old Engineering role on the project).
* **Durability:** Once a transaction is committed, the changes are permanent and will survive system crashes.
* **Syntax Example:**
```sql
BEGIN; -- Start transaction
UPDATE employees
SET department_id = (SELECT id FROM departments WHERE name = 'Sales')
WHERE email = 'alice@company.com';
UPDATE employee_projects
SET role = 'Consultant'
WHERE employee_id = (SELECT id FROM employees WHERE email = 'alice@company.com')
AND project_id = (SELECT id FROM projects WHERE name = 'Project Alpha');
-- Add more related operations if needed...
COMMIT; -- Apply changes permanently if all succeed
-- Or ROLLBACK; if an error occurs or check fails
```
### Algorithmic Problem Solving
Senior Python roles often require solving algorithmic problems efficiently. This involves understanding data structures, common algorithms, and complexity analysis (Big O notation).
#### Common Data Structures Review
Be comfortable using and knowing the trade-offs of:
* **Lists:** Ordered, mutable sequences. O(1) append, O(n) insertion/deletion/search.
* **Tuples:** Ordered, immutable sequences. Used for fixed collections.
* **Dictionaries (Hash Maps):** Key-value pairs. Average O(1) insertion/deletion/lookup. Crucial for many algorithms.
* **Sets:** Unordered collections of unique elements. Average O(1) add/remove/contains check. Useful for uniqueness and membership testing.
* **Strings:** Immutable sequences of characters.
* **(Conceptual) Stacks (LIFO):** Use `list.append()` for push, `list.pop()` for pop.
* **(Conceptual) Queues (FIFO):** Use `collections.deque` for efficient O(1) appends and pops from both ends.
* **(Conceptual) Heaps (Priority Queues):** Use `heapq` module. O(log n) push/pop smallest element.
* **(Conceptual) Trees (Binary Search Trees, Tries):** Know their properties and traversal methods (in-order, pre-order, post-order, level-order/BFS).
* **(Conceptual) Graphs:** Know representations (adjacency list, adjacency matrix) and traversal algorithms (BFS, DFS).
#### Problem-Solving Strategy
1. **Understand:** Clarify the problem, inputs, outputs, constraints, and edge cases. Ask questions!
2. **Plan:** Think about different approaches. Consider data structures. Whiteboard or sketch ideas. Analyze potential time/space complexity.
3. **Code:** Write clean, readable Python code. Use meaningful variable names.
4. **Test:** Test with examples, edge cases (empty input, large input, specific values), and constraints.
5. **Optimize:** If necessary, revisit your plan to improve time or space complexity.
---
#### Practice Problems & Solutions (Python)
**Problem 1: Two Sum**
**Statement:** Given an array of integers `nums` and an integer `target`, return *indices* of the two numbers such that they add up to `target`. Assume that each input would have *exactly one solution*, and you may not use the same element twice.
**Example:** `nums = [2, 7, 11, 15]`, `target = 9` -> Output: `[0, 1]` (because `nums[0] + nums[1] == 9`)
**Approach:** Iterate through the array. For each number `n`, check if `target - n` exists in a hash map (dictionary) that stores numbers encountered so far and their indices. If it exists, we found the pair. If not, add the current number `n` and its index to the map.
**Python Code:**
```python
def two_sum(nums: list[int], target: int) -> list[int]:
"""Finds indices of two numbers that sum to target."""
num_map = {} # Stores {number: index}
for index, num in enumerate(nums):
complement = target - num
if complement in num_map:
return [num_map[complement], index]
num_map[num] = index
return [] # Should not be reached based on problem statement
```
**Complexity:**
* Time: O(n) - We iterate through the list once. Dictionary lookups/insertions are O(1) on average.
* Space: O(n) - In the worst case, the dictionary stores all numbers.
**Problem 2: Valid Parentheses**
**Statement:** Given a string `s` containing just the characters `(`, `)`, `{`, `}`, `[` and `]`, determine if the input string is valid. An input string is valid if:
1. Open brackets must be closed by the same type of brackets.
2. Open brackets must be closed in the correct order.
3. Every close bracket has a corresponding open bracket of the same type.
**Example:** `s = "()[]{}"` -> `True`; `s = "(]"` -> `False`; `s = "{[]}"` -> `True`
**Approach:** Use a stack. Iterate through the string. If an opening bracket (`(`, `{`, `[`) is encountered, push it onto the stack. If a closing bracket (`)`, `}`, `]`) is encountered, check if the stack is empty or if the top of the stack is the corresponding opening bracket. If not, return `False`. If it matches, pop the stack. After iterating through the string, the stack should be empty for the string to be valid.
**Python Code:**
```python
def is_valid_parentheses(s: str) -> bool:
"""Checks if a string of parentheses is valid."""
stack = []
mapping = {")": "(", "}": "{", "]": "["}
for char in s:
if char in mapping: # It's a closing bracket
# Pop from stack if not empty, otherwise use a dummy value
top_element = stack.pop() if stack else '#'
if mapping[char] != top_element:
return False
else: # It's an opening bracket
stack.append(char)
return not stack # Stack should be empty at the end
```
* **Complexity:**
* Time: O(n) - We iterate through the string once. Stack operations are O(1).
* Space: O(n) - In the worst case (e.g., `((((...))))`), the stack stores all opening brackets.
**Problem 3: Longest Substring Without Repeating Characters**
* **Statement:** Given a string `s`, find the length of the longest substring without repeating characters.
* **Example:** `s = "abcabcbb"` -> `3` ("abc"); `s = "bbbbb"` -> `1` ("b"); `s = "pwwkew"` -> `3` ("wke")
* **Approach:** Use a sliding window technique. Maintain a window `[left, right]` and a set (or dictionary) to keep track of characters currently in the window. Expand the window by moving `right`. If `s[right]` is already in the set, shrink the window from the left by moving `left` and removing `s[left]` from the set until `s[right]` is no longer a duplicate. Add `s[right]` to the set. Update the maximum length found so far (`right - left + 1`).
* **Python Code:**
```python
def length_of_longest_substring(s: str) -> int:
"""Finds the length of the longest substring without repeating chars."""
char_set = set()
left = 0
max_length = 0
for right in range(len(s)):
# If char already in window, shrink from left until it's removed
while s[right] in char_set:
char_set.remove(s[left])
left += 1
# Add the new character to the set and update max length
char_set.add(s[right])
max_length = max(max_length, right - left + 1)
return max_length
```
* **Complexity:**
* Time: O(n) - Although there's a nested `while` loop, each character is added and removed from the set at most once. Both `left` and `right` pointers traverse the string linearly.
* Space: O(min(m, n)) - Where `n` is the length of the string and `m` is the size of the character set (e.g., 26 for lowercase English letters, or up to `n`). The space is used by the `char_set`.
**Problem 4: Kth Largest Element in an Array**
* **Statement:** Given an integer array `nums` and an integer `k`, return the `k`th largest element in the array. Note that it is the `k`th largest element in the sorted order, not the `k`th distinct element.
* **Example:** `nums = [3, 2, 1, 5, 6, 4]`, `k = 2` -> `5`; `nums = [3, 2, 3, 1, 2, 4, 5, 5, 6]`, `k = 4` -> `4`
* **Approach (Heap):** Use a min-heap. Iterate through the numbers. Push each number onto the heap. If the heap size exceeds `k`, pop the smallest element (the root of the min-heap). After iterating through all numbers, the root of the heap will be the `k`th largest element.
* **Python Code:**
```python
import heapq
def find_kth_largest(nums: list[int], k: int) -> int:
"""Finds the Kth largest element using a min-heap."""
# Create a min-heap
min_heap = []
for num in nums:
heapq.heappush(min_heap, num)
# If heap size exceeds k, remove the smallest element
if len(min_heap) > k:
heapq.heappop(min_heap)
# The root of the heap is the Kth largest element
return min_heap[0]
# Alternative concise version using heapify:
# def find_kth_largest_concise(nums: list[int], k: int) -> int:
# return heapq.nlargest(k, nums)[-1]
```
* **Complexity:**
* Time: O(n log k) - We process `n` elements. Heap push/pop operations take O(log k) time since the heap size is capped at `k`. (`heapq.nlargest` is often O(n log k) as well). A Quickselect approach offers average O(n) but worst-case O(n^2).
* Space: O(k) - To store the elements in the heap.
**Problem 5: Coin Change**
* **Statement:** You are given an integer array `coins` representing coins of different denominations and an integer `amount` representing a total amount of money. Return the *fewest* number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return `-1`. Assume you have an infinite number of each kind of coin.
* **Example:** `coins = [1, 2, 5]`, `amount = 11` -> `3` (11 = 5 + 5 + 1); `coins = [2]`, `amount = 3` -> `-1`
* **Approach (Dynamic Programming):** Create a DP array `dp` of size `amount + 1`, initialized with a value indicating infinity (e.g., `amount + 1`) except `dp[0] = 0` (0 coins needed for amount 0). Iterate from `1` to `amount`. For each amount `i`, iterate through the `coins`. If a coin `c` is less than or equal to `i`, check if using this coin can lead to a smaller number of coins needed: `dp[i] = min(dp[i], 1 + dp[i - c])`. After the loops, if `dp[amount]` is still the infinity value, it means the amount is unreachable; otherwise, `dp[amount]` holds the minimum number of coins.
* **Python Code:**
```python
def coin_change(coins: list[int], amount: int) -> int:
"""Finds the minimum number of coins to make a given amount (DP)."""
# dp[i] will store the minimum coins needed for amount i
# Initialize with a value larger than any possible result (amount + 1)
dp = [amount + 1] * (amount + 1)
dp[0] = 0 # Base case: 0 coins for amount 0
for i in range(1, amount + 1):
for coin in coins:
if coin <= i:
# If we use 'coin', we need 1 + dp[i - coin] coins
dp[i] = min(dp[i], 1 + dp[i - coin])
# If dp[amount] is still amount + 1, it means amount is unreachable
return dp[amount] if dp[amount] <= amount else -1
```
* **Complexity:**
* Time: O(amount * num_coins) - We have nested loops iterating up to `amount` and through the `coins`.
* Space: O(amount) - We use a DP array of size `amount + 1`.
Here is another solution:
* **Python Code:**
```python
def coin_change(coins, amount):
map_dict = {}
fewest = 1000
for element in coins:
remainder = amount % element
qu = amount // element
if remainder == 0 or remainder in coins:
fewest = min(qu + remainder, fewest)
return -1 if fewest == 1000 else fewest
```
**Problem 6: Merge Intervals**
**Statement:** Given an array of intervals where intervals`[i] = [start_i, end_i]`, merge all overlapping intervals, and return an array of the non-overlapping intervals that cover all the intervals in the input.
**Example:** `intervals = [[1,3],[2,6],[8,10],[15,18]] -> [[1,6],[8,10],[15,18]]` (because `[1,3]` and [2,6] overlap and merge into `[1,6]`). `intervals = [[1,4],[4,5]] -> [[1,5]]` (overlap at the boundary).
**Approach:** First, sort the intervals based on their start times. Initialize a list merged_intervals with the first interval. Iterate through the sorted intervals starting from the second one. If the current interval overlaps with the last interval in merged_intervals (i.e., current_interval.start <= last_merged_interval.end), merge them by updating the end time of the last interval in merged_intervals to the maximum of the two end times. Otherwise, if they don't overlap, add the current interval to merged_intervals.
**Python Code:**
```python
def merge_intervals(intervals: list[list[int]]) -> list[list[int]]:
if not intervals:
return []
# Sort intervals based on the start time
intervals.sort(key=lambda x: x[0])
merged_intervals = [intervals[0]]
for i in range(1, len(intervals)):
current_start, current_end = intervals[i]
last_merged_start, last_merged_end = merged_intervals[-1]
# Check for overlap
if current_start <= last_merged_end:
# Merge: update the end of the last merged interval
merged_intervals[-1][1] = max(last_merged_end, current_end)
else:
# No overlap, add the current interval
merged_intervals.append([current_start, current_end])
return merged_intervals
```
Complexity:
Time: O(n log n) - Dominated by the sorting step. The merging process is O(n).
Space: O(n) or O(log n) - O(n) for the merged_intervals list. The space complexity of sorting depends on the implementation (Timsort in Python is O(n), but sometimes considered O(log n) for recursion stack if not in-place).
**Problem 7: Number of Islands**
**Statement:** Given an **m x n** 2D binary grid grid which represents a map of '1's (land) and '0's (water), return the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are surrounded by water.
**Example:**
`
grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
] -> 1
`
`
grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
] -> 3
`
**Approach (DFS or BFS):** Iterate through each cell of the grid. If a cell contains '1' (land) and hasn't been visited yet (or is part of a found island), increment the island count. Then, start a traversal (DFS or BFS) from this cell to find all connected land cells belonging to this island. Mark these visited cells (e.g., change '1' to '0' or use a separate visited set) to avoid recounting them. Continue iterating through the grid.
**Python Code (DFS):**
```python
def num_islands(grid: list[list[str]]) -> int:
"""Counts the number of islands in a grid using DFS."""
if not grid or not grid[0]:
return 0
rows, cols = len(grid), len(grid[0])
num_islands = 0
def dfs(r, c):
# Check boundaries and if it's water or already visited (marked as '0')
if r < 0 or c < 0 or r >= rows or c >= cols or grid[r][c] == '0':
return
# Mark the current cell as visited (by changing it to water)
grid[r][c] = '0'
# Explore neighbors
dfs(r + 1, c)
dfs(r - 1, c)
dfs(r, c + 1)
dfs(r, c - 1)
for r in range(rows):
for c in range(cols):
if grid[r][c] == '1':
num_islands += 1
dfs(r, c) # Explore and mark the entire island
return num_islands
```
**Complexity:**
Time: `O(m * n)` - Each cell is visited at most a constant number of times.
Space: `O(m * n)` - In the worst case (grid full of land), the recursion depth for DFS could go up to mn. If using BFS, the queue could also store up to `O(mn)` cells. Also `O(m*n)` if using a separate visited set instead of modifying the grid.
**Problem 8: Maximum Subarray**
**Statement:** Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.
**Example:** `nums = [-2,1,-3,4,-1,2,1,-5,4] -> 6` (subarray `[4,-1,2,1]`). `nums = [1] -> 1`. `nums = [5,4,-1,7,8] -> 23` (subarray `[5,4,-1,7,8]`).
**Approach (Kadane's Algorithm):** Iterate through the array, keeping track of the maximum sum ending at the current position (current_max) and the overall maximum sum found so far (global_max). For each number, update current_max to be the maximum of the number itself OR the number plus the current_max from the previous position. This effectively decides whether to extend the previous subarray or start a new one. Update global_max whenever current_max is greater.
**Python Code:**
```python
def max_subarray(nums: list[int]) -> int:
"""Finds the maximum sum of a contiguous subarray using Kadane's Algo."""
if not nums:
return 0 # Or raise error, depends on constraints
current_max = nums[0]
global_max = nums[0]
for i in range(1, len(nums)):
# Decide whether to extend the current subarray or start a new one
current_max = max(nums[i], current_max + nums[i])
# Update the overall maximum sum found so far
global_max = max(global_max, current_max)
return global_max
```
**Complexity:**
Time: O(n) - Single pass through the array.
Space: O(1) - Only a few variables are used.
## Interview Questions
### Problem 1: Database Transactions (Django & SQLAlchemy)
**Statement:** Explain the concept of database transactions, why they are important, and demonstrate how to use them when performing multiple operations involving related models in both Django and SQLAlchemy.
**Concept:**
A database transaction is a sequence of one or more database operations treated as a single logical unit of work. This unit is governed by the ACID properties, primarily **Atomicity**. Atomicity ensures that either all operations within the transaction are successfully completed and the changes are committed to the database, or if any operation fails, the entire transaction is aborted, and all changes are rolled back to their state before the transaction began. This prevents partial updates and maintains data consistency, especially when dealing with interrelated data or multiple steps that must collectively succeed or fail.
1. You need to perform multiple write operations that depend on each other (e.g., debiting one account and crediting another – both must happen).
2. An operation involves modifying data based on the current state, and you want to prevent other processes from changing that state in between.
3. You want a set of operations to be retryable or easily undone if something goes wrong.
**Example Models (Author and Book):**
We'll use simple `Author` and `Book` models where a `Book` has a foreign key relationship to an `Author`. Creating an author and several books for them is a common scenario where a transaction is useful – you want either the author *and* all specified books to be created, or none of them if any step fails (e.g., a validation error on a book title).
**Django ORM:**
Django manages transactions automatically for individual `save()` or `create()` calls (usually implicit), but for a block of multiple operations, you typically use `django.db.transaction.atomic()`. This context manager guarantees atomicity for the operations performed within the `with` block.
```python
# Assume these models are defined in your Django app's models.py
# from django.db import models
# class Author(models.Model):
# name = models.CharField(max_length=100)
# class Book(models.Model):
# title = models.CharField(max_length=200)
# author = models.ForeignKey(Author, on_delete=models.CASCADE)
from django.db import transaction, IntegrityError
def create_author_and_books_django(author_name, book_titles):
"""
Creates an author and multiple books within a single transaction.
If any step fails, the entire operation is rolled back.
"""
try:
# Use the 'atomic' block to ensure the operations are transactional
with transaction.atomic():
print(f"Starting transaction to create {author_name} and {len(book_titles)} books...")
# Operation 1: Create the Author
author = Author.objects.create(name=author_name)
print(f"Created Author: {author.name} (ID: {author.id})")
# Operation 2+: Create Books linked to the Author
for i, title in enumerate(book_titles):
# Simulate a potential error for demonstration (e.g., validation fails)
if "Invalid" in title:
print(f"Simulating error for book '{title}'. This will trigger rollback.")
raise ValueError(f"Invalid book title encountered: {title}")
Book.objects.create(title=title, author=author)
print(f"Created Book: '{title}' for {author.name}")
# If the block finishes without exceptions, the transaction is committed
print("Transaction successful! Changes committed.")
except (ValueError, IntegrityError) as e:
# If an exception occurs within the 'atomic' block, the transaction is rolled back
print(f"An error occurred: {e}. Transaction rolled back.")
# Note: Any partial creations within the block are undone.
# --- Example Usage (Requires Django setup) ---
# Assume you have run 'manage.py migrate' and can import your models
from myapp.models import Author, Book # Adjust import based on your app name
# Example 1: Success
print("\n--- Running successful scenario ---")
create_author_and_books_django("Jane Austen", ["Pride and Prejudice", "Sense and Sensibility"])
print("\n--- Database State After Success ---")
print("Authors:", list(Author.objects.values_list('name', flat=True)))
print("Books:", list(Book.objects.values_list('title', flat=True)))
```
**SQLAlchemy**:
In SQLAlchemy, sessions manage transactions. The recommended way to handle transactions is using the session as a context manager (with session). This automatically handles the commit if the block completes successfully and performs a rollback if an exception occurs.
```python
# Assume these models are defined using SQLAlchemy Declarative Base
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Author(Base):
__tablename__ = 'authors_sqla' # Use different table names if mixing ORMs in one DB
id = Column(Integer, primary_key=True)
name = Column(String)
books = relationship("Book", back_populates="author")
class Book(Base):
__tablename__ = 'books_sqla'
id = Column(Integer, primary_key=True)
title = Column(String)
author_id = Column(Integer, ForeignKey('authors_sqla.id'))
author = relationship("Author", back_populates="books")
# --- Example Setup (needed to run SQLAlchemy code) ---
engine = create_engine('sqlite:///:memory:') # Or your actual database URL
Base.metadata.create_all(engine) # Create tables based on models
SessionLocal = sessionmaker(bind=engine) # Configure a session factory
from sqlalchemy.exc import SQLAlchemyError # Import specific exceptions as needed
def create_author_and_books_sqla(session, author_name, book_titles):
"""
Creates an author and multiple books within a SQLAlchemy transaction
managed by the session context.
"""
try:
# The 'with session:' context manager handles the transaction:
# It calls session.begin(), session.commit() on success,
# and session.rollback() on exception.
with session: # Start a transaction
print(f"Starting SQLAlchemy session/transaction to create {author_name} and {len(book_titles)} books...")
# Operation 1: Create the Author
author = Author(name=author_name)
session.add(author)
# You might need session.flush() here if subsequent operations
# immediately need the author.id before commit, but for relationship
# assignment like book.author = author, it's often handled by SA.
session.flush() # Get the author's ID assigned if needed
print(f"Added Author: {author.name} (ID will be assigned on commit or flush)")
# Operation 2+: Create Books linked to the Author
for i, title in enumerate(book_titles):
# Simulate a potential error
if "Invalid" in title:
print(f"Simulating error for book '{title}'. This will trigger rollback.")
raise ValueError(f"Invalid book title encountered: {title}")
book = Book(title=title, author=author) # Link using the Author object
session.add(book)
print(f"Added Book: '{title}' for {author.name}")
# If the block finishes without exceptions, session.commit() is called automatically
print("Transaction successful! Changes committed.")
except (ValueError, SQLAlchemyError) as e:
# If an exception occurs, session.rollback() is called automatically by the 'with' block
print(f"An error occurred: {e}. Transaction rolled back.")
# Note: The session might be in an invalid state after rollback;
# it's often best to close and get a new one for subsequent operations.
# --- Example Usage (Requires SQLAlchemy setup above) ---
from your_models_file import Author, Book, SessionLocal # Adjust import
session = SessionLocal() # Get a new session instance
Example 1: Success
print("\n--- Running successful scenario (SQLA) ---")
create_author_and_books_sqla(session, "J.R.R. Tolkien", ["The Hobbit", "The Fellowship of the Ring"])
session.close() # Always close the session
```