Создайте молниеносную систему напоминаний по SMS с Vonage SMS API и FastAPI Backend!
2 марта 2023 г.В этой статье вы узнаете, как создать серверную систему смс-напоминаний с помощью Vonage SMS API.
FastAPI — это современная, быстрая (высокопроизводительная) веб-инфраструктура для создания API с Python 3.6+ на основе стандартных подсказок типов Python. Он построен на основе Starlette для веб-частей и Pydantic для частей данных. Его преимущества заключаются в возможности быстрого создания конечных точек, встроенной поддержке веб-сокетов и асинхронности.
Я бы использовал Python для серверной системы из-за его простоты и масштабируемости, а также его обширной экосистемы и библиотек, которые можно использовать для широкого круга задач.
<цитата>Отказ от ответственности: я рекомендую вам ознакомиться с официальной документацией FastAPI или видео tutorial, чтобы начать работу с фреймворком; так как это поможет ускорить процесс.
Vonage SMS API позволяет отправлять и получать текстовые сообщения пользователям по всему миру, используя наши REST API. Начните работу здесь.
Настройка учетной записи Vonage
Чтобы следовать этому руководству, я настоятельно рекомендую вам создать учетную запись Vonage. Вы можете начать с регистрации здесь и начать работу с бесплатным кредитом. После этого возьмите свой API и секретный ключ в верхней части панели инструментов Vonage API.
Настройка и установка проекта
1). Создайте и активируйте виртуальную среду
Для начала начните с создания каталога, виртуального окружения (желательно pipenv) и активируйте его:
mkdir sms_reminder && cd sms_reminder
python3.9 -m pipenv shell
2). Установите необходимые зависимости
Создайте файл requirements.txt
в каталоге и продолжите, скопировав и вставив приведенный ниже фрагмент кода в файл requirements.txt
:
aiosqlite==0.18.0
alembic==1.9.2
anyio==3.6.2
apscheduler==3.9.1.post1
backoff==2.2.1
certifi==2022.12.7
click==8.1.3
databases==0.7.0
fastapi==0.89.1
greenlet==2.0.1; python_version >= '3' and (platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32'))))))
h11==0.14.0
httpcore==0.16.3
httptools==0.5.0
httpx==0.23.3
idna==3.4
mako==1.2.4
markupsafe==2.1.2
pydantic==1.10.4
python-decouple==3.7
python-dotenv==0.21.1
pytz-deprecation-shim==0.1.0.post0
pytz==2022.7.1
pyyaml==6.0
rfc3986==1.5.0
six==1.16.0
sniffio==1.3.0
sqlalchemy==1.4.46
starlette==0.22.0
typing-extensions==4.4.0; python_version < '3.10'
tzdata==2022.7; python_version >= '3.6'
tzlocal==4.2
uvicorn==0.20.0
uvloop==0.17.0
watchfiles==0.18.1
websockets==10.4
Приступайте к немедленной установке необходимых пакетов с помощью следующей команды:
pipenv install -r requirements.txt
3). Создайте модуль main
Теперь, когда вы установили необходимые зависимости, давайте создадим файл main.py
и вставим в него приведенный ниже код.
Это точка входа серверной системы, которая будет отвечать за построение приложения FastAPI, регистрацию маршрутизаторов API, событий для подключения и отключения базы данных, запуска планировщиков заданий и запуска приложения.
# Uvicorn Imports
import uvicorn
# FastAPI Imports
from fastapi import FastAPI
# construct application
app = FastAPI(
title="SMS Reminder System",
description="An SMS reminder system that allows users to text a specific number to set reminders for themselves.",
verison=1.0,
)
@app.on_event("startup")
async def startup():
pass # connect to database will come here
@app.on_event("shutdown")
async def disconnect():
pass # disconnect from the database will come here
if __name__ == "__main__":
uvicorn.run(
"sms_reminder.main:app", host="0.0.0.0", port=3030, reload=True
)
4). Создайте каталог models
Мы создали main.py
, точку входа нашего приложения. Далее будет создан каталог моделей, и в основном там будут таблицы нашей базы данных, определенные как классы.
Давайте начнем, создадим файл __init__.py
, чтобы пометить этот каталог как модуль Python.
«Чистый код пишется не по набору правил. Вы не станете мастером программного обеспечения, изучив список эвристик. Профессионализм и мастерство исходят из ценностей, определяющих дисциплину».
Создайте файл base.py, содержащий класс с именем ObjectTracker, который служит базовым классом для всех отслеживаемых объектов в базе данных.
# Stdlib Imports
from datetime import datetime
# SQLAlchemy Imports
from sqlalchemy import Column, Integer, DateTime
class ObjectTracker(object):
id = Column(Integer, primary_key=True, index=True)
date_created = Column(DateTime, default=datetime.now)
date_updated = Column(DateTime, onupdate=datetime.now)
Чтобы создать базовый класс для отслеживания объектов в базе данных, мы импортируем классы Column
, Integer
и DateTime
из библиотеки sqlalchemy, а также импортировать datetime
из стандартной библиотеки Python, чтобы записывать время создания и обновления объектов.
Далее создадим файл sms.py
, скопируем и вставим в него приведенный ниже код:
# SQLAlchemy Imports
from sqlalchemy import Column, String, DateTime
# Own Imports
from sms_reminder.models.base import ObjectTracker
class Reminder(ObjectTracker):
phone_number = Column(String)
message = Column(String)
remind_when = Column(DateTime)
Класс Reminder представляет таблицу напоминаний в нашей базе данных, но мы должны настроить нашу базу данных, чтобы преобразовать этот объект в таблицу.
5). Создайте каталог config
Чтобы гарантировать, что Python рассматривает этот каталог как модуль, мы создаем файл __init__.py
. Затем мы создаем файл database.py
, который содержит информацию для настройки базы данных, построения создателя сеанса, коннектора базы данных (для подключения и закрытия нашей базы данных) и создания декларативного базового класса, который преобразует наши модели в таблицы базы данных.
Давайте продолжим; продолжите, скопировав приведенный ниже код и вставив его в файл database.py
:
# SQLAlchemy Imports
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy.ext.declarative import declarative_base
# Third Imports
from databases import Database
# create a database and engine
DATABASE_URL = "sqlite:///./sms_reminder.sqlite"
DATABASE_ENGINE = create_engine(
DATABASE_URL, connect_args={"check_same_thread": False}
)
# construct a session maker
session_factory = sessionmaker(
autocommit=False, autoflush=False, bind=DATABASE_ENGINE
)
# construct a scoped session
SessionLocal = scoped_session(session_factory)
# Construct a base class for declarative class definitions
Base = declarative_base()
# Construct a db connector to connect, and shutdown the database
db_connect = Database(DATABASE_URL)
Вы можете подумать: "Сейчас все может показаться довольно сложным". Но не волнуйтесь, я объясню магию, свидетелем которой вы являетесь.
* create_engine
: этот метод принимает URL-адрес соединения (базы данных) и возвращает механизм sqlalchemy, который ссылается как на диалект, так и на пул, которые вместе интерпретируют функции модуля DBAPI, а также поведение базы данных ( Источник).
* session_maker
: чтобы объяснить это, нам нужно начать с понимания того, что такое Session
. Сессия в sqlalchemy устанавливает все диалоги с базой данных, это обычный класс Python, который может быть создан напрямую. session_maker
используется для создания конфигурации Session
верхнего уровня, которую затем можно использовать во всем приложении без необходимости повторения определенных конфигурационных аргументов. Подробнее читайте здесь и здесь.
* scoped_session
: согласно официальной документации sqlalchemy, объект scoped_session
создает управляемый потоком реестр объектов Session. Он обычно используется в веб-приложениях, так что одну глобальную переменную можно использовать для безопасного представления транзакционных сеансов с наборами объектов, локализованных в одном потоке. Короче говоря, они пытаются сказать, чтобы использовать scoped_session
в вашем приложении для безопасности потоков. Поскольку он открывает и закрывает объект сеанса для каждого запроса, это безопасный и рекомендуемый способ работы. Подробнее читайте здесь.
* declarative_base
: эта функция используется для определения классов, сопоставленных с таблицами реляционной базы данных.
* База данных
: этот класс создает коннектор базы данных, который поможет нам подключиться и закрыть нашу базу данных.
Теперь, когда вы понимаете, что происходит, давайте добавим Base
в класс Reminder
в моделях sms:
# SQLAlchemy Imports
from sqlalchemy import Column, String, DateTime
# Own Imports
from sms_reminder.config.database import Base # new line
from sms_reminder.models.base import ObjectTracker
class Reminder(ObjectTracker, Base): # added Base
__tablename__ = "reminders" # new line
phone_number = Column(String)
message = Column(String)
remind_when = Column(DateTime)
Сделав это, sqlalchemy сопоставит определенный класс с таблицей реляционной базы данных. Затем мы обновляем точку входа main.py
, чтобы подключаться и выключать нашу базу данных всякий раз, когда наш внутренний сервер генерирует событие запуска и завершения работы.
# Uvicorn Imports
import uvicorn
# FastAPI Imports
from fastapi import FastAPI
# Own Imports
from sms_reminder.config.database import db_connect # new line
# construct application
app = FastAPI(
title="SMS Reminder System",
description="An SMS reminder system that allows users to text a specific number to set reminders for themselves.",
verison=1.0,
)
@app.on_event("startup")
async def startup():
scheduler.start()
await db_connect.connect() # new line
@app.on_event("shutdown")
async def disconnect():
await db_connect.disconnect() # new line
if __name__ == "__main__":
uvicorn.run(
"sms_reminder.main:app", host="0.0.0.0", port=3030, reload=True
)
Продолжайте, создав файл settings.py
для хранения и хранения наших переменных окружения. Скопируйте и вставьте следующий код в файл:
# Stdlib Imports
from functools import lru_cache
from pydantic import BaseSettings
# Third Party Imports
from decouple import config as environ
class Settings(BaseSettings):
"""
Settings to hold environmental variables.
"""
VOYAGE_API_KEY: str = environ("VOYAGE_API_KEY", cast=str)
VOYAGE_SECRET_KEY: str = environ("VOYAGE_SECRET_KEY", cast=str)
@lru_cache
def get_setting_values() -> Settings:
env_var = Settings()
return env_var
Давайте пробежимся по тому, что мы сделали:
lru_cache
: этот декоратор создаст объектSettings
один раз. После этого он будет кэширован и использован повторно, вместо того чтобы создавать объектSettings
каждый раз, когда мы хотим получить доступ к значениям.BaseSettings
: позволяет переопределять значения переменными среды. Это полезно в производстве для секретов, которые вы не хотите сохранять в коде, это прекрасно работает с docker(-compose), Heroku и любым 12-факторным дизайном приложения.
Объект Settings отвечает за получение переменных среды, нам нужно создать файл .env
, который будет загружен environ
. Скопируйте и вставьте приведенный ниже код в файл:
VOYAGE_API_KEY=value
VOYAGE_SECRET_KEY=value
Замените value
правильными ключами из вашей панели инструментов Vonage.
6). Инициализируйте alembic
для обработки миграции базы данных
Alembic – это облегченный инструмент миграции базы данных, который можно использовать с набором инструментов SQLAlchemy Database Toolkit для Python. Он широко используется для миграции баз данных. Давайте приступим к его использованию.
Мы установили alembic в начале нашего проекта, поэтому теперь нам нужно инициализировать его для нашего рабочего каталога проекта. Запустите следующую команду в своем терминале:
alembic init migrations
Я использовал миграции для хранения всех миграций базы данных в этой папке. Выполнение приведенной выше команды создаст папку с именем миграции, которая содержит следующие файлы/папки:
- env.py
- README
- script.py.mako
- versions (directory)
В каталоге проекта также будет создан файл alembic.ini
. В вашем каталоге versions
не будет файлов версий, потому что мы еще не выполнили миграцию. Теперь, чтобы использовать перегонный куб, нам нужно внести некоторые изменения.
Сначала измените sqlalchemy.url
в файле alembic.ini
:
sqlalchemy.url = sqlite:///./sms_reminder.sqlite
Затем нам нужно предоставить нашу модель базы данных для alembic и получить доступ к метаданным из модели.
Отредактируйте файл env.py
в папке migrations
:
... # this means there are other imports here
# Own Imports
from sms_reminder.models.sms import Reminder # new line
... # this means there are other codes above
target_metadata = Reminder.metadata # update
Как показано выше, мы должны предоставить нашу модель базы данных для alembic. Запустите следующую команду в своем терминале:
alembic revision --autogenerate -m "Create reminder table"
Выполнение приведенной выше команды скажет alembic сгенерировать наш файл миграции и сохранить его в папке versions
. После создания этого файла выполните следующую команду:
alembic upgrade head
Ваши таблицы будут сгенерированы в вашей базе данных. Это все к магии. Подробнее о перегонном кубе читайте здесь.
7). Создать каталог схем
Мы успешно создали классы models
, представленные в виде таблиц базы данных, и выполнили миграцию базы данных. Далее будет создание наших пидантических моделей. Pydantic в основном используется для анализа и проверки данных с использованием аннотации типа.
Интересно отметить, что pydantic принудительно указывает тип во время выполнения и предоставляет удобные для пользователя ошибки, когда данные недействительны. Для начала создайте файл __init__.py
, чтобы указать Python для обработки каталога как модуля, создайте другой файл с именем crud.py
и вставьте в него следующие коды:
# Stdlib Imports
import pytz
from datetime import datetime
from pydantic import BaseModel, Field
class BaseReminderSchema(BaseModel):
phone_number: str = Field(
description="What's your phone number? Ensure you include your country code and is valid. E.g 234xxxxxxxxxx"
)
message: str = Field(
description="What message do you want to remind yourself with? E.g Time to go to the gym!"
)
remind_when: datetime = Field(
description="When should I send this message to you?",
default=datetime.now(tz=pytz.timezone("Africa/Lagos")),
)
class CreateReminderSchema(BaseReminderSchema):
pass
class ReminderSchema(BaseReminderSchema):
id: int
date_created: datetime
class Config:
orm_mode = True
Давайте рассмотрим, что происходит в приведенном выше коде. Мы импортируем библиотеку часовых поясов python (pytz
), дату и время из библиотеки datetime
и импортируем BaseModel и Field из pydantic
.
pytz
: эта библиотека позволяет выполнять точные межплатформенные расчеты часовых поясов с использованием Python 2.4 или более поздней версии. Это также решает проблему неоднозначного времени в конце летнего времени. Подробнее читайте здесь.BaseModel
: это класс, используемый для определения объектов в pydantic. Вы можете думать о моделях как о типах в строго типизированных языках или как о требованиях одной конечной точки в API. Подробнее читайте здесь.Поле
: это класс, используемый для предоставления дополнительной информации о поле либо для схемы модели, либо для комплексной проверки.BaseReminderSchema
: это класс, наследуемый отBaseModel
для создания полей, которые будут использоваться в других схемах.CreateReminderSchema
: это класс, наследуемый отBaseReminderSchema
, который будет использоваться в качестве требования для создания напоминания.ReminderSchema
: это класс, наследуемый отBaseReminderSchema
, который будет использоваться в качестве требования для составления списка созданных напоминаний.orm_mode
(также известный как экземпляры произвольного класса) поддерживает модели, которые сопоставляются с объектами ORM.
8). Создать каталог interface
Нам нужен интерфейс, который находится между нашим уровнем доступа к данным (базой данных) и уровнем бизнес-логики (сервисами) приложения. Лучший способ сделать это — использовать шаблон проектирования репозитория. Видите ли, шаблон репозитория абстрагирует способ, которым ваши данные запрашиваются или создаются для вас.
Двигаясь дальше, создайте файл __init__.py
, чтобы указать Python рассматривать каталог как модуль, а затем создайте файл с именем sms.py
. Скопируйте и вставьте приведенный ниже код в файл:
# Stdlib Imports
from typing import List
from datetime import datetime
# SQLAlchemy Imports
from sqlalchemy.orm import Session
# Own Imports
from sms_reminder.models.sms import Reminder
from sms_reminder.config.database import SessionLocal
class ReminderORMInterface:
"""Reminder ORM that interface with the database."""
def __init__(self) -> None:
self.db: Session = self.get_db_session().__next__()
def get_db_session(self):
"""
This method creates a database session,
yields it to the caller, rollback the session
if an exception occurs; otherwise, close the session.
"""
session = SessionLocal()
try:
yield session
except Exception:
session.rollback()
finally:
session.close()
async def get(self) -> List[Reminder]:
"""This method gets a list of reminders."""
reminders = self.db.query(Reminder).all()
return reminders
async def create(
self, phone_number: str, message: str, remind_when: datetime
) -> Reminder:
"""This method creates a new reminder to the database."""
reminder = Reminder(
phone_number=phone_number,
message=message,
remind_when=remind_when,
)
self.db.add(reminder)
self.db.commit()
self.db.refresh(reminder)
return reminder
reminder_orm = ReminderORMInterface()
Давайте разберем, что происходит в приведенном выше коде, у нас есть четыре метода:
get_db_session
: этот метод создает для нас сеанс базы данных, возвращает его и откатывает сеанс в случае возникновения исключения; в противном случае закройте сеанс.__init__
: этот метод инициализирует сеанс базы данных.get
: этот метод запрашивает модель Reminder (таблицу), получая список всех объектов в ней.create
: этот метод создает новый экземпляр напоминания, сохраняет его в состоянии (которое будет либо откатываться, либо двигаться вперед), сбрасывать ожидающие изменения и фиксировать текущую транзакцию, а также обновлять вновь созданный экземпляр в стол.
Чтобы начать использовать наш интерфейс, нам нужно инициализировать наш класс переменной.
9). Создайте каталог services
В предыдущем разделе мы рассмотрели, что такое интерфейс (шаблон репозитория); и как он расположен между нашим уровнем доступа к данным и уровнем бизнес-логики нашего приложения. В этом разделе мы создадим уровень бизнес-логики. Двигаясь дальше, создайте файл __init__.py
, чтобы указать Python рассматривать каталог как модуль.
я). Создайте файл с именем vonage.py
, скопируйте и вставьте в него приведенный ниже код:
# Stdlib Imports
from typing import Dict
# FastAPI Imports
from fastapi import HTTPException
# Own Imports
from sms_reminder.config.settings import get_setting_values as settings
# Third Party Imports
import httpx
import backoff
class VonageSMS:
"""SMS API Service provider to handle sending text messages."""
def __init__(self) -> None:
"""
The method is used to initialize the class
and set the base_url, settings, secret_key and api_key.
"""
self.base_url = "https://rest.nexmo.com/sms/json"
self.settings = settings()
self.secret_key = self.settings.VOYAGE_SECRET_KEY
self.api_key = self.settings.VOYAGE_API_KEY
def headers(self) -> Dict[str, str]:
"""
This method returns a header with a key of "Content-Type" and a value of
"application/x-www-form-urlencoded".
"""
return {"Content-Type": "application/x-www-form-urlencoded"}
@backoff.on_exception(backoff.expo, httpx.ConnectTimeout, max_time=100)
async def send(self, phone_number: str, message: str) -> True:
"""
This method sends a message to the provided phone number using the VonageSMS API.
:param phone_number: The phone number to send the message to
:type phone_number: str
:param message: The message you want to send
:type message: str
:return: True
"""
async with httpx.AsyncClient() as client:
request_data = f"from=Send Reminder!&text={message}&to={phone_number}&api_key={self.api_key}&api_secret={self.secret_key}"
response = await client.post(
url=self.base_url, headers=self.headers(), data=request_data
)
response_data = response.json()["messages"][0]
if response_data["status"] == "0":
return True
raise HTTPException(
500,
{
"source": "VonageSMS",
"message": response_data["error-text"],
},
)
vonage_sms = VonageSMS()
"Ух ты. Абрам, много кода!"
Не беспокойтесь об этом, давайте рассмотрим это вместе. Один импорт, метод за раз. Я не буду объяснять общие вещи, которые, как я полагаю, вы уже должны знать, такие как Dict из typing
.
* HTTPException
: это базовый класс для всех исключений http. Вы можете переопределить его для создания пользовательских исключений, таких как ServerException, NotFoundException и т. д.
* settings
: это функция из модуля config/settings.py
, которая при вызове кэширует наши переменные среды, а также предоставляет нам доступ к значению.
* httpx
: это полнофункциональная клиентская http-библиотека для python3. Он поддерживает как http/1.1, так и http/2 и предоставляет как синхронизирующие, так и асинхронные API.
* backoff
: эта библиотека предоставляет декораторы функций, которые можно использовать для обертывания функции таким образом, что она будет повторяться до тех пор, пока не будет выполнено какое-либо условие.
Вы видите это сейчас? Не нужно было паниковать, я здесь, чтобы все правильно объяснить. Далее, что делает каждый метод:
* __init__
: этот метод используется для инициализации нашего класса VonageSMS
и установки base_url и настроек; secret_key и api_key смс-сервиса vonage.
* headers
: этот метод используется для установки типа контента, который мы хотим в наших заголовках, когда мы делаем запрос к службе смс Vonage через API.
* send
: этот метод отправляет сообщение на номер телефона, указанный с помощью Vonage sms api. Чтобы иметь возможность отправить смс, нам нужен номер телефона и сообщение, которое мы хотим отправить.
Обратите внимание на использование httpx.AsyncClient()
вместо httpx.Client
в методе. Это связано с тем, что AsyncClient
является асинхронным, а его диспетчер контекста использует асинхронный с
, а не только с
.
* Двигаясь вперед, мы кодируем форму в URL-адрес и отправляем почтовый запрос на наш base_url
, который мы установили в нашем методе __init__
. Затем мы проверяем, есть ли у нас статус, равный "0"
, возвращаем True
, иначе вызываем исключение 500, сообщающее клиенту, что пошло не так.< /p>
ii). Создайте файл с именем tasks.py
, скопируйте и вставьте в него приведенный ниже код:
# Stdlib Imports
from random import randint
from datetime import datetime
# APScheduler Imports
from apscheduler.schedulers.asyncio import AsyncIOScheduler
# Own Imports
from sms_reminder.services.vonage import vonage_sms
# initialize ayncio scheduler
scheduler = AsyncIOScheduler()
async def create_reminder_job(
phone_number: str, message: str, remind_when: datetime
) -> dict:
"""
This function creates a job that will send a message to the provided
phone number at a specific time.
:param phone_number: The phone number to send the message to
:type phone_number: str
:param message: The message to be sent
:type message: str
:param remind_when: The datetime object that represents the time when the reminder should be sent
:type remind_when: datetime
:return: A dictionary with the keys "scheduled" and "job_id".
"""
job_uid = randint(0, 9999)
reminder_job = scheduler.add_job(
func=vonage_sms.send,
trigger="date",
args=(phone_number, message),
name=f"reminder_set_{phone_number}_{job_uid}",
id=f"{phone_number}_{job_uid}",
next_run_time=remind_when,
)
return {"scheduled": True, "job_id": reminder_job.id}
Мы импортируем AsyncIOScheduler
из библиотеки apscheduler
и импортируем vonage_sms
из нашего модуля services/vonage
. Функция create_reminder_job
отвечает за создание задания, которое отправит сообщение на указанный номер телефона по определенному номеру.
III). Создайте файл с именем sms.py
, скопируйте и вставьте в него следующий код:
# Stdlib Imports
from datetime import datetime
from typing import List
# FastAPI Imports
from fastapi import HTTPException
# Own Imports
from sms_reminder.services.tasks import create_reminder_job
from sms_reminder.interface.sms import reminder_orm, Reminder
async def create_user_reminder(
phone_number: str, message: str, remind_when: datetime
) -> Reminder:
"""
This function creates a new reminder in the database.
:param phone_number: The phone number of the user who will receive the reminder
:type phone_number: str
:param message: The message that will be sent to the user
:type message: str
:param reminder_when: datetime
:type reminder_when: datetime
:return: A reminder object
"""
reminder = await reminder_orm.create(phone_number, message, remind_when)
reminder_job = await create_reminder_job(
phone_number, message, remind_when
)
if reminder_job["scheduled"]:
return reminder
raise HTTPException(400, {"message": "Was not able to set reminder!"})
async def get_user_reminders() -> List[Reminder]:
"""
This function gets the list of user reminders.
:return: A list of reminder objects.
"""
reminders = await reminder_orm.get()
return reminders
Мы импортируем create_reminder_job
из модуля services/tasks
, класса модели reminder_orm
и Reminder
из interface/sms
модуль. Функция create_user_reminder
отвечает за создание нового напоминания в базе данных. Когда мы создаем новое напоминание, мы также создаем задание, которое отправит сообщение с напоминанием на указанный номер телефона. Исключение 400 возникает, если мы не смогли установить задание.
Далее следует функция get_user_reminders
, по сути, мы получаем список напоминаний из базы данных.
10). Создайте каталог api
В предыдущем разделе мы создали уровень бизнес-логики. Следующим шагом будет создание конечных точек нашего API. Для начала создайте файл __init__.py
, чтобы указать Python рассматривать каталог как модуль.
я). Создайте файл с именем index.py
, скопируйте и вставьте в него приведенный ниже код:
# FastAPI Imports
from fastapi import APIRouter
# initialize api router
router = APIRouter(tags=["Root"])
@router.get("/")
async def root() -> dict:
"""
SMS Reminder System root
Returns: dict: version and description
"""
return {
"version": 1.0,
"description": "An SMS reminder system that allows users to text a specific number to set reminders for themselves.",
}
APIRouter
— это класс, импортированный из fastapi
, мы используем его для объявления наших операций с путями. root
— это представление API, которое возвращает версию и описание нашего серверного приложения.
ii). Создайте файл с именем crud.py
, скопируйте и вставьте в него приведенный ниже код:
# Stdlib Imports
from typing import List
from datetime import datetime, timezone
# FastAPI Imports
from fastapi import APIRouter, status, HTTPException
# Own Imports
from sms_reminder.services.sms import create_user_reminder, get_user_reminders
from sms_reminder.schemas.crud import CreateReminderSchema, ReminderSchema
# initialize the api router
router = APIRouter(tags=["Reminder"], prefix="/reminders")
@router.post("/", status_code=status.HTTP_201_CREATED)
async def create_reminder(payload: CreateReminderSchema):
"""
This API view creates and sets a reminder.
:param payload: CreateReminderScheman
:type payload: CreateReminderSchema
:return: The reminder is serialized to a JSON.
"""
if payload.remind_when <= datetime.now(timezone.utc):
raise HTTPException(
400,
{
"message": "Kindly set a date and time that exceeds the past and now."
},
)
reminder = await create_user_reminder(
payload.phone_number, payload.message, payload.remind_when
)
return {"message": "Reminder set!", "data": reminder}
@router.get("/", response_model=List[ReminderSchema])
async def get_reminders():
"""
This API view gets the total available reminders.
:return: A list of reminders.
"""
reminders = await get_user_reminders()
return reminders
Мы рассмотрели, что такое APIRouter
и HTTPException
; двигаясь дальше, мы объявили HTTP-код status
, который мы хотели бы получить в нашем ответе.
Мы также импортируем нашу бизнес-логику, функции create_user_remeinder
и get_user_reminders
из модуля services/sms
и, наконец; импорт CreateReminderSchema
и ReminderSchema
из модуля schemas/crud
.
Представление API create_reminder
отвечает за создание и настройку напоминания. Чтобы объявить метод HTTP, который мы хотим использовать, мы используем маршрутизатор в качестве декоратора для доступа к методу post
.
Ключевое слово async
в определении функции сообщает FastAPI, что она должна выполняться асинхронно, то есть без блокировки текущего потока выполнения. Мы используем CreateReminderSchema
в качестве аннотации типа для параметра payload
.
Оператор if
является проверкой, чтобы убедиться, что мы не можем создать напоминание с прошедшей датой и временем. Двигаясь вперед, create_user_reminder
вызывается для управления созданием и настройкой напоминания. Если исключение не возникло, ответом API будет словарь с сообщением и напоминанием.
12). Запустить сервер Запустите сервер uvicorn с помощью следующей команды в вашем терминале:
python sms_reminder/main.py
ПРИМЕЧАНИЕ. Если вы получаете следующую ошибку:
Traceback (most recent call last):
File "/.../sms-based-reminder-system/sms_reminder/main.py", line 8, in from sms_reminder.services.tasks import scheduler
ModuleNotFoundError: No module named 'sms_reminder'
Запустите приведенный ниже код в своем терминале и снова запустите сервер uvicorn:
export PYTHONPATH=$PWD
В консоли терминала вы получите что-то похожее на приведенное ниже сообщение:
INFO: Will watch for changes in these directories: [‘/…/sms-based-reminder-system’]
INFO: Uvicorn running on [http://0.0.0.0:3030](http://0.0.0.0:3030/) (Press CTRL+C to quit)
INFO: Started reloader process [1203267] using WatchFiles
INFO: Started server process [1203280]
INFO: Waiting for application startup.
INFO: Application startup complete.
Заключение
Поздравляем! Вы узнали, как создать серверную систему SMS-напоминаний с использованием FastAPI и Vonage SMS API. Есть много других вещей, которые вы можете сделать, например, ограничить API создания напоминаний для обработки 10 запросов в минуту, чтобы уменьшить количество спам-ботов. Вы можете найти исходный код этого учебного проекта на моем Github.
Оригинал