- Installation guide
- Windows
- Linux
- API Endpoints
- City Weather
- User
- Token authorization
- Swagger
- Celery
- Run scheduled tasks
- Run basic tasks
- Add new scheduled tasks
- Technical decisions
-
Install Python 3.8 , PostgreSQL , Erlang for rabbitmq , RabbitMQ
- Start RabbitMQ:
- Navigate to
C:\Program Files\RabbitMQ Server\rabbitmq_server-3.10.5\sbin - Start
rabbitmq-server.batas Administrator
- Navigate to
- Start RabbitMQ:
-
If you haven't already clone the repository and
cdinto itgit clone https://github.com/antonarnaudov/weather_api.git cd weather_api -
Create local database named
weather-api-dbusing pgAdmin or SQL Shell (psql)
NOTE: Make sure you set proper credentials inDATABASEsettings -
Create
venvby running:python -m venv venv
-
Activate
venvin:- cmd by running:
call venv/scripts/activate - Powershell by running:
& venv/scripts/activate
- cmd by running:
-
Install all dependencies by running:
pip install -r requirements.txt
-
Run migrations with:
python manage.py migrate
-
Try starting the app with:
python manage.py runserver
-
Try running the tests:
python manage.py test
-
Install PostgreSQL
Python3.8:sudo apt-get install python3.8
RabbitMQ:
sudo apt-get install rabbitmq-server sudo systemctl enable rabbitmq-server sudo systemctl start rabbitmq-serverOptional (to make sure it works):
systemctl status rabbitmq-server -
If you haven't already clone the repository and
cdinto itgit clone https://github.com/antonarnaudov/weather_api.git cd weather_api -
Create local database named
weather-api-dbusing pgAdmin or SQL Shell (psql)
NOTE: Make sure you set proper credentials inDATABASEsettings -
Create
venvby running:python3 -m venv venv
-
Activate
venvby runningsource venv/bin/activate -
Install all dependencies with:
pip install -r requirements.txt
-
Run migrations with:
python3 manage.py migrate
-
Try starting the app with:
python3 manage.py runserver
-
Try running the tests:
python3 manage.py test
Provides access to weather data for all cities
Read Only: api/cityweather/
Detailed: api/cityweather/{id}/
Search by city:
api/cityweather/?city=sofia
Provides endpoints for user profile management
CRUD: api/auth/user/
{
"id": 1,
"extended": {
"id": 1,
"phone": "0888666754"
},
"last_login": null,
"is_superuser": true,
"username": "admin",
"first_name": "Admin",
"last_name": "Adminov",
"email": "admin@cityweather.com",
"is_staff": true,
"is_active": true,
"date_joined": "2022-07-09T18:53:58.243512Z",
"groups": [],
"user_permissions": []
} Search:
api/auth/user/?search=admin
Search fields: first_name, last_name, email, username
Register Users on POST
Required fields on POST: username, password, password2, email
Takes a set of user credentials and returns an access and refresh JSON web
token pair to prove the authentication of those credentials.
POST: api/auth/login/
Expects:
{
"username": "admin",
"password": "1234"
}Returns:
{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY1NzY1NTA2NywiaWF0IjoxNjU3NTY4NjY3LCJqdGkiOiIxOTg4NWE1OGIzNTk0NzJiYjI2NmNjY2FjZGZmN2JkNyIsInVzZXJfaWQiOjF9.7zKhghN_UgQQW_7QnjfMckUOfaE3txwYnoXvEVoZQ8E",
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjU3NTY4OTY3LCJpYXQiOjE2NTc1Njg2NjcsImp0aSI6ImVjZTQ5YjdiZTdkNzQ2Y2NhZjlhNWEwZTNlOWQ4MjhjIiwidXNlcl9pZCI6MX0.HqXlOh1RrOxocD2kmpUJy2cqH1lZagtwCl-iJSsm4ro"
}POST: api/auth/refresh/
Expects:
{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY1NzY1NTA2NywiaWF0IjoxNjU3NTY4NjY3LCJqdGkiOiIxOTg4NWE1OGIzNTk0NzJiYjI2NmNjY2FjZGZmN2JkNyIsInVzZXJfaWQiOjF9.7zKhghN_UgQQW_7QnjfMckUOfaE3txwYnoXvEVoZQ8E"
}Returns:
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjU3NTY5MTIwLCJpYXQiOjE2NTc1Njg2NjcsImp0aSI6IjRlZThkZTk5ZDYzYzRiNjdiODAyYmU3ODhlZGY0ZWFjIiwidXNlcl9pZCI6MX0.6i0qPK0KLjRwFMkoO6R-s5IrWBImT5Rt86Xu6GntQc8"
}To use the access token in request, simply add it to request headers as: Authorization: Bearer ${access}
Token Lifespan:
- Access token lifespan is 5 minutes
- Refresh token lifespan is 1 day
- To retain an ongoing session you should refresh the access token on every request
Auto-generated documentation for all accessible endpoints
GET: api/swagger/
Make sure you have started the RabbitMQ Server
NOTE: You can skip those steps if you've already done them
On Linux you should execute the following commands:
sudo systemctl enable rabbitmq-server
sudo systemctl start rabbitmq-serverOn Windows you should:
- Navigate to
C:\Program Files\RabbitMQ Server\rabbitmq_server-3.10.5\sbin - Start
rabbitmq-server.batas Administrator
Open any terminal and execute this line to start the celery workers:
For Windows:
celery -A weather_api worker -l info --pool=soloFor Linux:
celery -A weather_api worker -l infoIn a separate terminal run this line to start the celery beat and all scheduled tasks:
celery -A weather_api beat -l INFO --scheduler django_celery_beat.schedulers:DatabaseSchedulerOpen any terminal and execute this line to start the celery workers:
For Windows:
celery -A weather_api worker -l info --pool=soloFor Linux:
celery -A weather_api worker -l infoNOTE: Go to weather_api > celery and change update_city_weather_data_per_day schedule to 60 seconds to see instant result.
- Create
@app.taskdecorated function in any file or app- Import
appfromweather_api.celery
- Import
- Open
weather_api > celery.pyand register your worker insideapp.conf.beat_schedulefollowing this structure
"example_worker_runs_every_minute": {
"task": "app.file.worker_function",
"schedule": 60.0 # schedule expects seconds
}-
I used RabbitMQ instead of Redis for message broker because I aimed to make the project executable on both Windows and Unix based OS. Redis requires a WSL to run under windows and RabbitMQ can work directly under it, although it requires to manually start the server.
-
I used a custom filter instead of overriding the list method for the search by city, thus keeping the ViewSet super clean and simple
-
I used JsonFiled for storing the third party api data, which worked really nice with the Django ORM, allowing me to tap into the nested fields as if they were written in the model
-
I used created_at/updated_at DateTime fields to keep track of time ;-D, so I know when to refresh the data
-
I extended the user model (although it wasn't necessary) as a matter of good practice, because its nightmare to do it later in the development process, when the app expands
-
I moved all non-weather specific functionality in app named core, and all utility functions in core > utils
-
I added a custom mixin which allows me to dynamically change the serializer, based on the request type
-
The default pagination class is overridden, so it utilizes size parameter, for dynamic single page pagination
-
I added a custom exception handler, to minimize the chance for Server Error 500 response
-
All Open Weather Map API credentials are set as settings
-
Added a blank page on base rout, so it does not throw errors
-
I used PR-s for these projects and did not delete the branches, so you can track the full development history
-
I used WritableNestedSerializers to create Extended objects from the Users serializer
-
I provided console logging on the celery task which logs the time of execution and the next time of execution
-
I added a few model and api tests, for the user, token authentication and city weather using django tests and django-factory_boy