{"id":307954,"date":"2020-08-04T09:01:17","date_gmt":"2020-08-04T09:01:17","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=307954"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=307954","title":{"rendered":"\u041f\u0438\u0448\u0435\u043c \u0432\u0435\u0431 \u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0430 Python \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e FastAPI"},"content":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\" data-io-article-url=\"https:\/\/habr.com\/ru\/post\/513328\/\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/post_images\/76e\/ee9\/e74\/76eee9e74c12fdf22c682fe5475f2ab2.png\" alt=\"image\"><br \/>  \u0417\u043d\u0430\u044e, \u0437\u043d\u0430\u044e, \u043d\u0430\u0432\u0435\u0440\u043d\u043e\u0435 \u0432\u044b \u0441\u0435\u0439\u0447\u0430\u0441 \u0434\u0443\u043c\u0430\u0435\u0442\u0435 \u00ab\u0447\u0442\u043e \u043e\u043f\u044f\u0442\u044c?!\u00bb. <br \/>  \u0414\u0430, \u043d\u0430 \u0445\u0430\u0431\u0440\u0435 <a href=\"https:\/\/habr.com\/ru\/company\/otus\/blog\/511840\/\">\u0443\u0436\u0435<\/a> <a href=\"https:\/\/habr.com\/ru\/post\/488468\/\">\u043d\u0435\u043e\u0434\u043d\u043e\u043a\u0440\u0430\u0442\u043d\u043e<\/a> <a href=\"https:\/\/habr.com\/ru\/post\/478620\/\">\u043f\u0438\u0441\u0430\u043b\u0438<\/a> \u043e \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0435 <a href=\"https:\/\/fastapi.tiangolo.com\" rel=\"nofollow\">FastAPI<\/a>. \u041d\u043e \u044f \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u044d\u0442\u043e\u0442 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c API \u0441\u0432\u043e\u0435\u0433\u043e \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0433\u043e \u043c\u0438\u043d\u0438 \u0425\u0430\u0431\u0440\u0430 \u0431\u0435\u0437 \u043a\u0430\u0440\u043c\u044b \u0438 \u0440\u0435\u0439\u0442\u0438\u043d\u0433\u043e\u0432, \u0437\u0430\u0442\u043e <s>\u0441 \u0431\u043b\u044d\u043a\u0434\u0436\u0435\u043a\u043e\u043c \u0438<\/s> \u0441 \u0442\u0435\u0441\u0442\u0430\u043c\u0438, \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0435\u0439, \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f\u043c\u0438 \u0438 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u043e\u0439 \u0441 \u0411\u0414. <a name=\"habracut\"><\/a><\/p>\n<h2>\u0421\u0445\u0435\u043c\u0430 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438<\/h2>\n<p>  \u041f\u0440\u0435\u0436\u0434\u0435 \u0432\u0441\u0435\u0433\u043e, \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/docs.sqlalchemy.org\/en\/13\/core\/tutorial.html#define-and-create-tables\" rel=\"nofollow\">SQLAlchemy Expression Language<\/a>, \u043e\u043f\u0438\u0448\u0435\u043c \u0441\u0445\u0435\u043c\u0443 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445. \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b <b>models\/users.py<\/b>:  <\/p>\n<pre><code class=\"python\">import sqlalchemy from sqlalchemy.dialects.postgresql import UUID  metadata = sqlalchemy.MetaData()   users_table = sqlalchemy.Table(     &quot;users&quot;,     metadata,     sqlalchemy.Column(&quot;id&quot;, sqlalchemy.Integer, primary_key=True),     sqlalchemy.Column(&quot;email&quot;, sqlalchemy.String(40), unique=True, index=True),     sqlalchemy.Column(&quot;name&quot;, sqlalchemy.String(100)),     sqlalchemy.Column(&quot;hashed_password&quot;, sqlalchemy.String()),     sqlalchemy.Column(         &quot;is_active&quot;,         sqlalchemy.Boolean(),         server_default=sqlalchemy.sql.expression.true(),         nullable=False,     ), )   tokens_table = sqlalchemy.Table(     &quot;tokens&quot;,     metadata,     sqlalchemy.Column(&quot;id&quot;, sqlalchemy.Integer, primary_key=True),     sqlalchemy.Column(         &quot;token&quot;,         UUID(as_uuid=False),         server_default=sqlalchemy.text(&quot;uuid_generate_v4()&quot;),         unique=True,         nullable=False,         index=True,     ),     sqlalchemy.Column(&quot;expires&quot;, sqlalchemy.DateTime()),     sqlalchemy.Column(&quot;user_id&quot;, sqlalchemy.ForeignKey(&quot;users.id&quot;)), ) <\/code><\/pre>\n<p>  \u0418 \u0444\u0430\u0439\u043b <b>models\/posts.py<\/b>:  <\/p>\n<pre><code class=\"python\">import sqlalchemy  from .users import users_table  metadata = sqlalchemy.MetaData()   posts_table = sqlalchemy.Table(     &quot;posts&quot;,     metadata,     sqlalchemy.Column(&quot;id&quot;, sqlalchemy.Integer, primary_key=True),     sqlalchemy.Column(&quot;user_id&quot;, sqlalchemy.ForeignKey(users_table.c.id)),     sqlalchemy.Column(&quot;created_at&quot;, sqlalchemy.DateTime()),     sqlalchemy.Column(&quot;title&quot;, sqlalchemy.String(100)),     sqlalchemy.Column(&quot;content&quot;, sqlalchemy.Text()), ) <\/code><\/pre>\n<p>  \u0427\u0442\u043e\u0431\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c <a href=\"https:\/\/alembic.sqlalchemy.org\/en\/latest\/\" rel=\"nofollow\">alembic<\/a>:  <\/p>\n<pre><code class=\"bash\">$ pip install alembic<\/code><\/pre>\n<p>  \u0414\u043b\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 Alembic \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u043c:  <\/p>\n<pre><code class=\"bash\">$ alembic init migrations<\/code><\/pre>\n<p>  \u042d\u0442\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0430 \u0441\u043e\u0437\u0434\u0430\u0441\u0442 \u0432 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0444\u0430\u0439\u043b <b>alembic.ini<\/b> \u0438 \u043a\u0430\u0442\u0430\u043b\u043e\u0433 <b>migrations<\/b> \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0438\u0439  <\/p>\n<ul>\n<li>\u043a\u0430\u0442\u0430\u043b\u043e\u0433 <b>versions<\/b>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0443\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0444\u0430\u0439\u043b\u044b \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439<\/li>\n<li>\u0441\u043a\u0440\u0438\u043f\u0442 <b>env.py<\/b>, \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0449\u0438\u0439\u0441\u044f \u043f\u0440\u0438 \u0432\u044b\u0437\u043e\u0432\u0435 alembic <\/li>\n<li>\u0444\u0430\u0439\u043b <b>script.py.mako<\/b>, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0438\u0439 \u0448\u0430\u0431\u043b\u043e\u043d \u0434\u043b\u044f \u043d\u043e\u0432\u044b\u0445 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439.<\/li>\n<\/ul>\n<p>  \u0423\u043a\u0430\u0436\u0435\u043c url \u043d\u0430\u0448\u0435\u0439 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445, \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432 \u0444\u0430\u0439\u043b\u0435 <b>alembic.ini<\/b> \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0441\u0442\u0440\u043e\u0447\u043a\u0443:  <\/p>\n<pre><code class=\"bash\">sqlalchemy.url = postgresql:\/\/%(DB_USER)s:%(DB_PASS)s@%(DB_HOST)s:5432\/%(DB_NAME)s<\/code><\/pre>\n<p>  \u0424\u043e\u0440\u043c\u0430\u0442 <b>%(variable_name)s<\/b> \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043d\u0430\u043c \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c \u0440\u0430\u0437\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0441\u0440\u0435\u0434\u044b \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044f \u0438\u0445 \u0432 \u0444\u0430\u0439\u043b\u0435 <b>env.py<\/b> \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0432\u043e\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"python\">from os import environ from alembic import context from app.models import posts, users  # Alembic Config \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f # \u043a \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c \u0438\u0437 \u0444\u0430\u0439\u043b\u0430 alembic.ini config = context.config  section = config.config_ini_section config.set_section_option(section, &quot;DB_USER&quot;, environ.get(&quot;DB_USER&quot;)) config.set_section_option(section, &quot;DB_PASS&quot;, environ.get(&quot;DB_PASS&quot;)) config.set_section_option(section, &quot;DB_NAME&quot;, environ.get(&quot;DB_NAME&quot;)) config.set_section_option(section, &quot;DB_HOST&quot;, environ.get(&quot;DB_HOST&quot;))  fileConfig(config.config_file_name)  target_metadata = [users.metadata, posts.metadata] <\/code><\/pre>\n<p>  \u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0431\u0435\u0440\u0435\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f DB_USER, DB_PASS, DB_NAME \u0438 DB_HOST \u0438\u0437 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f. \u041a\u0440\u043e\u043c\u0435 \u044d\u0442\u043e\u0433\u043e, \u0432 \u0444\u0430\u0439\u043b\u0435 <b>env.py<\/b> \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0430\u0448\u0435\u0439 \u0431\u0430\u0437\u044b \u0432 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0435 <b>target_metadata<\/b>, \u0431\u0435\u0437 \u044d\u0442\u043e\u0433\u043e Alembic \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u0442 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043a\u0430\u043a\u0438\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0441\u0442\u0438 \u0432 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445.<\/p>\n<p>  \u0412\u0441\u0435 \u0433\u043e\u0442\u043e\u0432\u043e \u0438 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0438 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0411\u0414:  <\/p>\n<pre><code class=\"bash\"> $ alembic revision --autogenerate -m &quot;Added required tables&quot; $ alembic upgrade head <\/code><\/pre>\n<h2>\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0411\u0414<\/h2>\n<p>  \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b <b>main.py<\/b>:  <\/p>\n<pre><code class=\"python\">from fastapi import FastAPI   app = FastAPI()   @app.get(&quot;\/&quot;) def read_root():     return {&quot;Hello&quot;: &quot;World&quot;} <\/code><\/pre>\n<p>  \u0418 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0443  <\/p>\n<pre><code class=\"bash\">$ uvicorn main:app --reload<\/code><\/pre>\n<p>  \u0423\u0431\u0435\u0434\u0438\u043c\u0441\u044f, \u0447\u0442\u043e \u0432\u0441\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043a\u0430\u043a \u043d\u0430\u0434\u043e. \u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 <a href=\"http:\/\/127.0.0.1:8000\/\" rel=\"nofollow\">http:\/\/127.0.0.1:8000\/<\/a> \u0438 \u0432\u0438\u0434\u0438\u043c <\/p>\n<pre><code class=\"json\">{&quot;Hello&quot;: &quot;World&quot;}<\/code><\/pre>\n<p>  \u0427\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445, \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u043c\u043e\u0434\u0443\u043b\u0435\u043c <a href=\"https:\/\/www.encode.io\/databases\/\" rel=\"nofollow\">databases<\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e.<br \/>  \u041d\u0430\u0441\u0442\u0440\u043e\u0438\u043c <b>startup<\/b> \u0438 <b>shutdhown<\/b> <a href=\"https:\/\/fastapi.tiangolo.com\/advanced\/events\/\" rel=\"nofollow\">\u0441\u043e\u0431\u044b\u0442\u0438\u044f<\/a> \u043d\u0430\u0448\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430, \u043f\u0440\u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0431\u0443\u0434\u0443\u0442 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0438 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043e\u0442 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445. \u041e\u0442\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c \u0444\u0430\u0439\u043b <b>main.py<\/b>:  <\/p>\n<pre><code class=\"python\">from os import environ  import databases  # \u0431\u0435\u0440\u0435\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0411\u0414 \u0438\u0437 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f DB_USER = environ.get(&quot;DB_USER&quot;, &quot;user&quot;) DB_PASSWORD = environ.get(&quot;DB_PASSWORD&quot;, &quot;password&quot;) DB_HOST = environ.get(&quot;DB_HOST&quot;, &quot;localhost&quot;) DB_NAME = &quot;async-blogs&quot; SQLALCHEMY_DATABASE_URL = (     f&quot;postgresql:\/\/{DB_USER}:{DB_PASSWORD}@{DB_HOST}:5432\/{DB_NAME}&quot; ) # \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 database, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 database = databases.Database(SQLALCHEMY_DATABASE_URL)   app = FastAPI()   @app.on_event(&quot;startup&quot;) async def startup():     # \u043a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0441 \u0411\u0414     await database.connect()   @app.on_event(&quot;shutdown&quot;) async def shutdown():     # \u043a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0440\u0430\u0437\u0440\u044b\u0432\u0430\u0435\u043c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0441 \u0411\u0414     await database.disconnect()   @app.get(&quot;\/&quot;) def read_root():     # \u0438\u0437\u043c\u0435\u043d\u0438\u043c \u0440\u043e\u0443\u0442 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u043e\u043d \u0431\u0440\u0430\u043b \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u0411\u0414     query = (         select(             [                 posts_table.c.id,                 posts_table.c.created_at,                 posts_table.c.title,                 posts_table.c.content,                 posts_table.c.user_id,                 users_table.c.name.label(&quot;user_name&quot;),             ]         )         .select_from(posts_table.join(users_table))         .order_by(desc(posts_table.c.created_at))     )     return await database.fetch_all(query) <\/code><\/pre>\n<p>  \u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c <a href=\"http:\/\/127.0.0.1:8000\/\" rel=\"nofollow\">http:\/\/127.0.0.1:8000\/<\/a> \u0438 \u0435\u0441\u043b\u0438 \u0432\u0438\u0434\u0438\u043c \u0432 \u043e\u0442\u0432\u0435\u0442\u0435 \u043f\u0443\u0441\u0442\u043e\u0439 \u0441\u043f\u0438\u0441\u043e\u043a <b>[]<\/b>, \u0437\u043d\u0430\u0447\u0438\u0442 \u0432\u0441\u0435 \u043f\u0440\u043e\u0448\u043b\u043e \u0445\u043e\u0440\u043e\u0448\u043e \u0438 \u043c\u043e\u0436\u043d\u043e \u0434\u0432\u0438\u0433\u0430\u0442\u044c\u0441\u044f \u0434\u0430\u043b\u044c\u0448\u0435.<\/p>\n<h2>\u0412\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0438 \u043e\u0442\u0432\u0435\u0442\u0430<\/h2>\n<p>  \u0420\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c HTTP \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0438 \u043e\u0442\u0432\u0435\u0442\u044b. \u0414\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u043e\u0439 <a href=\"https:\/\/pydantic-docs.helpmanual.io\" rel=\"nofollow\">pydantic<\/a>:  <\/p>\n<pre><code class=\"bash\">pip install pydantic<\/code><\/pre>\n<p>  \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b <b>schemas\/users.py<\/b> \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043c\u043e\u0434\u0435\u043b\u044c, \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0449\u0443\u044e \u0437\u0430 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e \u0442\u0435\u043b\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430:  <\/p>\n<pre><code class=\"python\">from pydantic import BaseModel, EmailStr  class UserCreate(BaseModel):     &quot;&quot;&quot; \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 sign-up \u0437\u0430\u043f\u0440\u043e\u0441 &quot;&quot;&quot;     email: EmailStr     name: str     password: str <\/code><\/pre>\n<p>  \u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u0442\u0438\u043f\u044b \u043f\u043e\u043b\u0435\u0439 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0430\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u0438 \u0442\u0438\u043f\u043e\u0432. \u041f\u043e\u043c\u0438\u043c\u043e \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445, \u0442\u0430\u043a\u0438\u0445 \u043a\u0430\u043a <b>int<\/b> \u0438 <b>str<\/b>, pydantic \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u0442 \u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0442\u0438\u043f\u043e\u0432, \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u044e\u0449\u0438\u0445 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0442\u0438\u043f <b>EmailStr <\/b> \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u0447\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u2014 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u0439 email. \u0414\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0442\u0438\u043f\u0430 <b>EmailStr <\/b> \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043c\u043e\u0434\u0443\u043b\u044c <b>email-validator<\/b>:  <\/p>\n<pre><code class=\"bash\">pip install email-validator<\/code><\/pre>\n<p>  \u0422\u0435\u043b\u043e \u043e\u0442\u0432\u0435\u0442\u0430 \u0434\u043e\u043b\u0436\u043d\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0441\u0432\u043e\u0438 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u043e\u043b\u044f, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 <b>id<\/b> \u0438 <b>access_token<\/b>, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0432 \u0444\u0430\u0439\u043b <b>schemas\/users.py<\/b> \u043c\u043e\u0434\u0435\u043b\u0438, \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0449\u0438\u0435 \u0437\u0430 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043e\u0442\u0432\u0435\u0442\u0430:  <\/p>\n<pre><code class=\"python\">from typing import Optional from pydantic import UUID4, BaseModel, EmailStr, Field, validator   class UserCreate(BaseModel):     &quot;&quot;&quot; \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 sign-up \u0437\u0430\u043f\u0440\u043e\u0441 &quot;&quot;&quot;     email: EmailStr     name: str     password: str   class UserBase(BaseModel):     &quot;&quot;&quot; \u0424\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442 \u0442\u0435\u043b\u043e \u043e\u0442\u0432\u0435\u0442\u0430 \u0441 \u0434\u0435\u0442\u0430\u043b\u044f\u043c\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f &quot;&quot;&quot;     id: int     email: EmailStr     name: str   class TokenBase(BaseModel):     token: UUID4 = Field(..., alias=&quot;access_token&quot;)     expires: datetime     token_type: Optional[str] = &quot;bearer&quot;      class Config:         allow_population_by_field_name = True      @validator(&quot;token&quot;)     def hexlify_token(cls, value):         &quot;&quot;&quot; \u041a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u0443\u0435\u0442 UUID \u0432 hex \u0441\u0442\u0440\u043e\u043a\u0443 &quot;&quot;&quot;         return value.hex   class User(UserBase):     &quot;&quot;&quot; \u0424\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442 \u0442\u0435\u043b\u043e \u043e\u0442\u0432\u0435\u0442\u0430 \u0441 \u0434\u0435\u0442\u0430\u043b\u044f\u043c\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u0442\u043e\u043a\u0435\u043d\u043e\u043c &quot;&quot;&quot;     token: TokenBase = {} <\/code><\/pre>\n<p>  \u0414\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u043e\u043b\u044f \u043c\u043e\u0434\u0435\u043b\u0438 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c <a href=\"https:\/\/pydantic-docs.helpmanual.io\/usage\/validators\/\" rel=\"nofollow\">\u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0439 \u0432\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440<\/a>. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, <b>hexlify_token<\/b> \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u0435\u0442 UUID \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0432 hex \u0441\u0442\u0440\u043e\u043a\u0443. \u0421\u0442\u043e\u0438\u0442 \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u043b\u0430\u0441\u0441 <b>Field<\/b>, \u043a\u043e\u0433\u0434\u0430 \u043d\u0443\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0435 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u044f \u043c\u043e\u0434\u0435\u043b\u0438. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440,<b> token: UUID4 = Field(&#8230;, alias=\u00abaccess_token\u00bb)<\/b> \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u043f\u0441\u0435\u0432\u0434\u043e\u043d\u0438\u043c <b>access_token<\/b> \u0434\u043b\u044f \u043f\u043e\u043b\u044f <b>token<\/b>. \u0414\u043b\u044f \u043e\u0431\u043e\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f, \u0447\u0442\u043e \u043f\u043e\u043b\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e, \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044f \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u2014 <b>&#8230;<\/b> (<a href=\"https:\/\/docs.python.org\/3\/library\/constants.html#Ellipsis\" rel=\"nofollow\">ellipsis<\/a>).<\/p>\n<p>  \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0444\u0430\u0439\u043b<b> utils\/users.py<\/b>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043c\u0435\u0442\u043e\u0434\u044b, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0434\u043b\u044f \u0437\u0430\u043f\u0438\u0441\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 \u0411\u0414:  <\/p>\n<pre><code class=\"python\">import hashlib import random import string from datetime import datetime, timedelta from sqlalchemy import and_  from app.models.database import database from app.models.users import tokens_table, users_table from app.schemas import users as user_schema  def get_random_string(length=12):     &quot;&quot;&quot; \u0413\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0449\u0443\u044e\u0441\u044f \u043a\u0430\u043a \u0441\u043e\u043b\u044c &quot;&quot;&quot;     return &quot;&quot;.join(random.choice(string.ascii_letters) for _ in range(length))   def hash_password(password: str, salt: str = None):     &quot;&quot;&quot; \u0425\u0435\u0448\u0438\u0440\u0443\u0435\u0442 \u043f\u0430\u0440\u043e\u043b\u044c \u0441 \u0441\u043e\u043b\u044c\u044e &quot;&quot;&quot;     if salt is None:         salt = get_random_string()     enc = hashlib.pbkdf2_hmac(&quot;sha256&quot;, password.encode(), salt.encode(), 100_000)     return enc.hex()   def validate_password(password: str, hashed_password: str):     &quot;&quot;&quot; \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u0447\u0442\u043e \u0445\u0435\u0448 \u043f\u0430\u0440\u043e\u043b\u044f \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0435\u0442 \u0441 \u0445\u0435\u0448\u0435\u043c \u0438\u0437 \u0411\u0414 &quot;&quot;&quot;     salt, hashed = hashed_password.split(&quot;$&quot;)     return hash_password(password, salt) == hashed   async def get_user_by_email(email: str):     &quot;&quot;&quot; \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435 &quot;&quot;&quot;     query = users_table.select().where(users_table.c.email == email)     return await database.fetch_one(query)   async def get_user_by_token(token: str):     &quot;&quot;&quot; \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0432\u043b\u0430\u0434\u0435\u043b\u044c\u0446\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0433\u043e \u0442\u043e\u043a\u0435\u043d\u0430 &quot;&quot;&quot;     query = tokens_table.join(users_table).select().where(         and_(             tokens_table.c.token == token,             tokens_table.c.expires &gt; datetime.now()         )     )     return await database.fetch_one(query)   async def create_user_token(user_id: int):     &quot;&quot;&quot; \u0421\u043e\u0437\u0434\u0430\u0435\u0442 \u0442\u043e\u043a\u0435\u043d \u0434\u043b\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0441 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u043c user_id &quot;&quot;&quot;     query = (         tokens_table.insert()         .values(expires=datetime.now() + timedelta(weeks=2), user_id=user_id)         .returning(tokens_table.c.token, tokens_table.c.expires)     )      return await database.fetch_one(query)   async def create_user(user: user_schema.UserCreate):     &quot;&quot;&quot; \u0421\u043e\u0437\u0434\u0430\u0435\u0442 \u043d\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 \u0411\u0414 &quot;&quot;&quot;     salt = get_random_string()     hashed_password = hash_password(user.password, salt)     query = users_table.insert().values(         email=user.email, name=user.name, hashed_password=f&quot;{salt}${hashed_password}&quot;     )     user_id = await database.execute(query)     token = await create_user_token(user_id)     token_dict = {&quot;token&quot;: token[&quot;token&quot;], &quot;expires&quot;: token[&quot;expires&quot;]}      return {**user.dict(), &quot;id&quot;: user_id, &quot;is_active&quot;: True, &quot;token&quot;: token_dict} <\/code><\/pre>\n<p>  \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b <b>routers\/users.py<\/b> \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u043c <b>sign-up<\/b> \u0440\u043e\u0443\u0442, \u0443\u043a\u0430\u0437\u0430\u0432, \u0447\u0442\u043e \u0432 \u0437\u0430\u043f\u0440\u043e\u0441\u0435 \u043e\u043d \u043e\u0436\u0438\u0434\u0430\u0435\u0442 \u043c\u043e\u0434\u0435\u043b\u044c <b>CreateUser<\/b> \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043c\u043e\u0434\u0435\u043b\u044c <b>User<\/b>:   <\/p>\n<pre><code class=\"python\">from fastapi import APIRouter from app.schemas import users from app.utils import users as users_utils   router = APIRouter()   @router.post(&quot;\/sign-up&quot;, response_model=users.User) async def create_user(user: users.UserCreate):     db_user = await users_utils.get_user_by_email(email=user.email)     if db_user:         raise HTTPException(status_code=400, detail=&quot;Email already registered&quot;)     return await users_utils.create_user(user=user) <\/code><\/pre>\n<p>  \u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0440\u043e\u0443\u0442\u044b \u0438\u0437 \u0444\u0430\u0439\u043b\u0430 <b>routers\/users.py<\/b>. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0432 <b>main.py<\/b> \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0441\u0442\u0440\u043e\u043a\u0438:  <\/p>\n<pre><code class=\"python\">from app.routers import users app.include_router(users.router) <\/code><\/pre>\n<h2>\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0430<\/h2>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c, \u043a\u043e\u0433\u0434\u0430 \u0432 \u043d\u0430\u0448\u0435\u0439 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0435\u0441\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438, \u0432\u0441\u0435 \u0433\u043e\u0442\u043e\u0432\u043e \u0434\u043b\u044f \u0442\u043e\u0433\u043e \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0442\u043e\u043a\u0435\u043d. \u041e\u0431\u043d\u043e\u0432\u0438\u043c \u0444\u0430\u0439\u043b <b>routers\/users.py<\/b>, \u0434\u043e\u0431\u0430\u0432\u0438\u0432 \u0432 \u043d\u0435\u0433\u043e:  <\/p>\n<pre><code class=\"python\">from fastapi import Depends from fastapi.security import OAuth2PasswordRequestForm   @router.post(&quot;\/auth&quot;, response_model=users.TokenBase) async def auth(form_data: OAuth2PasswordRequestForm = Depends()):     user = await users_utils.get_user_by_email(email=form_data.username)      if not user:         raise HTTPException(status_code=400, detail=&quot;Incorrect email or password&quot;)      if not users_utils.validate_password(         password=form_data.password, hashed_password=user[&quot;hashed_password&quot;]     ):         raise HTTPException(status_code=400, detail=&quot;Incorrect email or password&quot;)      return await users_utils.create_user_token(user_id=user[&quot;id&quot;]) <\/code><\/pre>\n<p>  \u041f\u0440\u0438 \u044d\u0442\u043e\u043c, \u043d\u0430\u043c \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u043c\u043e\u0434\u0435\u043b\u044c \u0437\u0430\u043f\u0440\u043e\u0441\u0430, Fastapi \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 <a href=\"https:\/\/fastapi.tiangolo.com\/tutorial\/dependencies\/\" rel=\"nofollow\">dependency<\/a> \u043a\u043b\u0430\u0441\u0441 <b>OAuth2PasswordRequestForm<\/b>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0437\u0430\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0440\u043e\u0443\u0442 \u043e\u0436\u0438\u0434\u0430\u0442\u044c \u0434\u0432\u0430 \u043f\u043e\u043b\u044f username \u0438 password. <\/p>\n<p>  \u0427\u0442\u043e\u0431\u044b \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0438\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c \u0440\u043e\u0443\u0442\u0430\u043c \u0434\u043b\u044f \u043d\u0435\u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043c\u0435\u0442\u043e\u0434-\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c(dependency). \u041e\u043d \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442, \u0447\u0442\u043e \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u0442\u043e\u043a\u0435\u043d \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0438\u0442 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u043c\u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0438 \u0432\u0435\u0440\u043d\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043d\u0430\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435 \u0432\u043e \u0432\u0441\u0435\u0445 \u0440\u043e\u0443\u0442\u0430\u0445, \u0442\u0440\u0435\u0431\u0443\u044e\u0449\u0438\u0445 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438. \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b <b>utils\/dependecies.py<\/b>:  <\/p>\n<pre><code class=\"python\">from app.utils import users as users_utils from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer   oauth2_scheme = OAuth2PasswordBearer(tokenUrl=&quot;\/auth&quot;)   async def get_current_user(token: str = Depends(oauth2_scheme)):     user = await users_utils.get_user_by_token(token)     if not user:         raise HTTPException(             status_code=status.HTTP_401_UNAUTHORIZED,             detail=&quot;Invalid authentication credentials&quot;,             headers={&quot;WWW-Authenticate&quot;: &quot;Bearer&quot;},         )     if not user[&quot;is_active&quot;]:         raise HTTPException(             status_code=status.HTTP_400_BAD_REQUEST, detail=&quot;Inactive user&quot;         )     return user <\/code><\/pre>\n<p>  \u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c \u043c\u043e\u0436\u0435\u0442 \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0437\u0430\u0432\u0438\u0441\u0435\u0442\u044c \u043e\u0442 \u0434\u0440\u0443\u0433\u043e\u0439 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438. \u041a \u043f\u0440\u0438\u043c\u0435\u0440 <b>OAuth2PasswordBearer<\/b> \u2014 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0434\u0430\u0435\u0442 \u043f\u043e\u043d\u044f\u0442\u044c FastAPI, \u0447\u0442\u043e \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u0440\u043e\u0443\u0442 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.<\/p>\n<p>  \u0427\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u0432\u0441\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043a\u0430\u043a \u043d\u0430\u0434\u043e, \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0440\u043e\u0443\u0442 <b>\/users\/me<\/b>, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u044e\u0449\u0438\u0439 \u0434\u0435\u0442\u0430\u043b\u0438 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u0412 \u0444\u0430\u0439\u043b <b>routers\/users.py<\/b> \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0441\u0442\u0440\u043e\u043a\u0438:  <\/p>\n<pre><code class=\"python\">from app.utils.dependencies import get_current_user   @router.get(&quot;\/users\/me&quot;, response_model=users.UserBase) async def read_users_me(current_user: users.User = Depends(get_current_user)):     return current_user <\/code><\/pre>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0440\u043e\u0443\u0442 <b>\/users\/me<\/b> \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u0438\u043c\u0435\u044e\u0442 \u0434\u043e\u0441\u0442\u0443\u043f \u0442\u043e\u043b\u044c\u043a\u043e \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438.<\/p>\n<p>  \u0412\u0441\u0435 \u0433\u043e\u0442\u043e\u0432\u043e \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u043a\u043e\u043d\u0435\u0446 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0438 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438:  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">utils\/posts.py<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"python\">from datetime import datetime  from app.models.database import database from app.models.posts import posts_table from app.models.users import users_table from app.schemas import posts as post_schema from sqlalchemy import desc, func, select   async def create_post(post: post_schema.PostModel, user):     query = (         posts_table.insert()         .values(             title=post.title,             content=post.content,             created_at=datetime.now(),             user_id=user[&quot;id&quot;],         )         .returning(             posts_table.c.id,             posts_table.c.title,             posts_table.c.content,             posts_table.c.created_at,         )     )     post = await database.fetch_one(query)      # Convert to dict and add user_name key to it     post = dict(zip(post, post.values()))     post[&quot;user_name&quot;] = user[&quot;name&quot;]     return post   async def get_post(post_id: int):     query = (         select(             [                 posts_table.c.id,                 posts_table.c.created_at,                 posts_table.c.title,                 posts_table.c.content,                 posts_table.c.user_id,                 users_table.c.name.label(&quot;user_name&quot;),             ]         )         .select_from(posts_table.join(users_table))         .where(posts_table.c.id == post_id)     )     return await database.fetch_one(query)   async def get_posts(page: int):     max_per_page = 10     offset1 = (page - 1) * max_per_page     query = (         select(             [                 posts_table.c.id,                 posts_table.c.created_at,                 posts_table.c.title,                 posts_table.c.content,                 posts_table.c.user_id,                 users_table.c.name.label(&quot;user_name&quot;),             ]         )         .select_from(posts_table.join(users_table))         .order_by(desc(posts_table.c.created_at))         .limit(max_per_page)         .offset(offset1)     )     return await database.fetch_all(query)   async def get_posts_count():     query = select([func.count()]).select_from(posts_table)     return await database.fetch_val(query)   async def update_post(post_id: int, post: post_schema.PostModel):     query = (         posts_table.update()         .where(posts_table.c.id == post_id)         .values(title=post.title, content=post.content)     )     return await database.execute(query)  <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">routers\/posts.py<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"python\">from app.schemas.posts import PostDetailsModel, PostModel from app.schemas.users import User from app.utils import posts as post_utils from app.utils.dependencies import get_current_user from fastapi import APIRouter, Depends, HTTPException, status  router = APIRouter()   @router.post(&quot;\/posts&quot;, response_model=PostDetailsModel, status_code=201) async def create_post(post: PostModel, current_user: User = Depends(get_current_user)):     post = await post_utils.create_post(post, current_user)     return post   @router.get(&quot;\/posts&quot;) async def get_posts(page: int = 1):     total_cout = await post_utils.get_posts_count()     posts = await post_utils.get_posts(page)     return {&quot;total_count&quot;: total_cout, &quot;results&quot;: posts}   @router.get(&quot;\/posts\/{post_id}&quot;, response_model=PostDetailsModel) async def get_post(post_id: int):     return await post_utils.get_post(post_id)   @router.put(&quot;\/posts\/{post_id}&quot;, response_model=PostDetailsModel) async def update_post(     post_id: int, post_data: PostModel, current_user=Depends(get_current_user) ):     post = await post_utils.get_post(post_id)     if post[&quot;user_id&quot;] != current_user[&quot;id&quot;]:         raise HTTPException(             status_code=status.HTTP_403_FORBIDDEN,             detail=&quot;You don't have access to modify this post&quot;,         )      await post_utils.update_post(post_id=post_id, post=post_data)     return await post_utils.get_post(post_id)  <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<p>  \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u043d\u043e\u0432\u044b\u0435 \u0440\u043e\u0443\u0442\u044b, \u0434\u043e\u0431\u0430\u0432\u0438\u0432 \u0432 <b>main.py<\/b>  <\/p>\n<pre><code class=\"python\">from app.routers import posts app.include_router(posts.router) <\/code><\/pre>\n<h2>\u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435<\/h2>\n<p>  \u0422\u0435\u0441\u0442\u044b \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 <a href=\"https:\/\/docs.pytest.org\/en\/stable\/getting-started.html\" rel=\"nofollow\">pytest<\/a>:  <\/p>\n<pre><code class=\"bash\">$ pip install pytest<\/code><\/pre>\n<p>  \u0414\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u043e\u0432 FastAPI \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 <a href=\"https:\/\/fastapi.tiangolo.com\/tutorial\/testing\/\" rel=\"nofollow\">TestClient<\/a>.<br \/>  \u041d\u0430\u043f\u0438\u0448\u0435\u043c \u0442\u0435\u0441\u0442 \u0434\u043b\u044f \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445:  <\/p>\n<pre><code class=\"python\">from app.main import app from fastapi.testclient import TestClient  client = TestClient(app)   def test_health_check():     response = client.get(&quot;\/&quot;)     assert response.status_code == 200     assert response.json() == {&quot;Hello&quot;: &quot;World&quot;} <\/code><\/pre>\n<p>  \u041a\u0430\u043a \u0432\u0438\u0434\u0438\u0442\u0435, \u0432\u0441\u0435 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e. \u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c <b>TestClient<\/b>, \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f HTTP \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432.<\/p>\n<p>  \u0414\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0445 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u043e\u0432, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u043e\u0432\u0443\u044e \u0411\u0414. \u041e\u0442\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c \u0444\u0430\u0439\u043b <b>main.py<\/b>, \u0434\u043e\u0431\u0430\u0432\u0438\u0432 \u0432 \u043d\u0435\u0433\u043e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0439 \u0431\u0430\u0437\u044b:  <\/p>\n<pre><code class=\"python\">from os import environ  import databases  DB_USER = environ.get(&quot;DB_USER&quot;, &quot;user&quot;) DB_PASSWORD = environ.get(&quot;DB_PASSWORD&quot;, &quot;password&quot;) DB_HOST = environ.get(&quot;DB_HOST&quot;, &quot;localhost&quot;)  TESTING = environ.get(&quot;TESTING&quot;)  if TESTING:     # \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432     DB_NAME = &quot;async-blogs-temp-for-test&quot;     TEST_SQLALCHEMY_DATABASE_URL = (         f&quot;postgresql:\/\/{DB_USER}:{DB_PASSWORD}@{DB_HOST}:5432\/{DB_NAME}&quot;     )     database = databases.Database(TEST_SQLALCHEMY_DATABASE_URL) else:     DB_NAME = &quot;async-blogs&quot;     SQLALCHEMY_DATABASE_URL = (         f&quot;postgresql:\/\/{DB_USER}:{DB_PASSWORD}@{DB_HOST}:5432\/{DB_NAME}&quot;     )     database = databases.Database(SQLALCHEMY_DATABASE_URL) <\/code><\/pre>\n<p>  \u041c\u044b \u043f\u043e-\u043f\u0440\u0435\u0436\u043d\u0435\u043c\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0411\u0414 <b>\u00abasync-blogs\u00bb<\/b> \u0434\u043b\u044f \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u041d\u043e \u0435\u0441\u043b\u0438 \u0437\u0430\u0434\u0430\u043d\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0435 TESTING, \u0442\u043e\u0433\u0434\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0443\u0435\u0442\u0441\u044f \u0411\u0414 <b>\u00abasync-blogs-temp-for-test\u00bb<\/b>.<\/p>\n<p>  \u0427\u0442\u043e\u0431\u044b \u0431\u0430\u0437\u0430 <b>\u00abasync-blogs-temp-for-test\u00bb<\/b> \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u043b\u0430\u0441\u044c \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u0443\u0434\u0430\u043b\u044f\u043b\u0430\u0441\u044c \u043f\u043e\u0441\u043b\u0435 \u0438\u0445 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f, \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0438\u043a\u0441\u0442\u0443\u0440\u0443 \u0432 \u0444\u0430\u0439\u043b\u0435 <b>tests\/conftest.py<\/b>:  <\/p>\n<pre><code class=\"python\">import os  import pytest  # \u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c `os.environ`, \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u043e\u0432\u0443\u044e \u0411\u0414 os.environ['TESTING'] = 'True'  from alembic import command from alembic.config import Config from app.models import database from sqlalchemy_utils import create_database, drop_database   @pytest.fixture(scope=&quot;module&quot;) def temp_db():     create_database(database.TEST_SQLALCHEMY_DATABASE_URL) # \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0411\u0414     base_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))     alembic_cfg = Config(os.path.join(base_dir, &quot;alembic.ini&quot;)) # \u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e alembic      command.upgrade(alembic_cfg, &quot;head&quot;) # \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438      try:         yield database.TEST_SQLALCHEMY_DATABASE_URL     finally:         drop_database(database.TEST_SQLALCHEMY_DATABASE_URL) # \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u0411\u0414 <\/code><\/pre>\n<p>  \u0414\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0411\u0414 \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u043e\u0439 <a href=\"https:\/\/sqlalchemy-utils.readthedocs.io\/en\/latest\/\" rel=\"nofollow\">sqlalchemy_utils <\/a>.<\/p>\n<p>  \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0444\u0438\u043a\u0441\u0442\u0443\u0440\u0443 <b>temp_db<\/b> \u0432 \u0442\u0435\u0441\u0442\u0430\u0445, \u043c\u044b \u0441\u043c\u043e\u0436\u0435\u043c \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u0435 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u044b \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f:  <\/p>\n<pre><code class=\"python\">def test_sign_up(temp_db):     request_data = {         &quot;email&quot;: &quot;vader@deathstar.com&quot;,         &quot;name&quot;: &quot;Darth Vader&quot;,         &quot;password&quot;: &quot;rainbow&quot;     }     with TestClient(app) as client:         response = client.post(&quot;\/sign-up&quot;, json=request_data)     assert response.status_code == 200     assert response.json()[&quot;id&quot;] == 1     assert response.json()[&quot;email&quot;] == &quot;vader@deathstar.com&quot;     assert response.json()[&quot;name&quot;] == &quot;Darth&quot;     assert response.json()[&quot;token&quot;][&quot;expires&quot;] is not None     assert response.json()[&quot;token&quot;][&quot;access_token&quot;] is not None <\/code><\/pre>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">tests\/test_posts.py<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"python\">import asyncio  from app.main import app from app.schemas.users import UserCreate from app.utils.users import create_user, create_user_token from fastapi.testclient import TestClient   def test_create_post(temp_db):     user = UserCreate(         email=&quot;vader@deathstar.com&quot;,         name=&quot;Darth&quot;,         password=&quot;rainbow&quot;     )     request_data = {       &quot;title&quot;: &quot;42&quot;,       &quot;content&quot;: &quot;Don't panic!&quot;     }     with TestClient(app) as client:         # Create user and use his token to add new post         loop = asyncio.get_event_loop()         user_db = loop.run_until_complete(create_user(user))         response = client.post(             &quot;\/posts&quot;,             json=request_data,             headers={&quot;Authorization&quot;: f&quot;Bearer {user_db['token']['token']}&quot;}         )     assert response.status_code == 201     assert response.json()[&quot;id&quot;] == 1     assert response.json()[&quot;title&quot;] == &quot;42&quot;     assert response.json()[&quot;content&quot;] == &quot;Don't panic!&quot;   def test_create_post_forbidden_without_token(temp_db):     request_data = {       &quot;title&quot;: &quot;42&quot;,       &quot;content&quot;: &quot;Don't panic!&quot;     }     with TestClient(app) as client:         response = client.post(&quot;\/posts&quot;, json=request_data)     assert response.status_code == 401   def test_posts_list(temp_db):     with TestClient(app) as client:         response = client.get(&quot;\/posts&quot;)     assert response.status_code == 200     assert response.json()[&quot;total_count&quot;] == 1     assert response.json()[&quot;results&quot;][0][&quot;id&quot;] == 1     assert response.json()[&quot;results&quot;][0][&quot;title&quot;] == &quot;42&quot;     assert response.json()[&quot;results&quot;][0][&quot;content&quot;] == &quot;Don't panic!&quot;   def test_post_detail(temp_db):     post_id = 1     with TestClient(app) as client:         response = client.get(f&quot;\/posts\/{post_id}&quot;)     assert response.status_code == 200     assert response.json()[&quot;id&quot;] == 1     assert response.json()[&quot;title&quot;] == &quot;42&quot;     assert response.json()[&quot;content&quot;] == &quot;Don't panic!&quot;   def test_update_post(temp_db):     post_id = 1     request_data = {       &quot;title&quot;: &quot;42&quot;,       &quot;content&quot;: &quot;Life? Don't talk to me about life.&quot;     }     with TestClient(app) as client:         # Create user token to add new post         loop = asyncio.get_event_loop()         token = loop.run_until_complete(create_user_token(user_id=1))         response = client.put(             f&quot;\/posts\/{post_id}&quot;,             json=request_data,             headers={&quot;Authorization&quot;: f&quot;Bearer {token['token']}&quot;}         )     assert response.status_code == 200     assert response.json()[&quot;id&quot;] == 1     assert response.json()[&quot;title&quot;] == &quot;42&quot;     assert response.json()[&quot;content&quot;] == &quot;Life? Don't talk to me about life.&quot;   def test_update_post_forbidden_without_token(temp_db):     post_id = 1     request_data = {       &quot;title&quot;: &quot;42&quot;,       &quot;content&quot;: &quot;Life? Don't talk to me about life.&quot;     }     with TestClient(app) as client:         response = client.put(f&quot;\/posts\/{post_id}&quot;, json=request_data)     assert response.status_code == 401 <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">tests\/test_users.py<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"python\">import asyncio import pytest  from app.main import app from app.schemas.users import UserCreate from app.utils.users import create_user, create_user_token from fastapi.testclient import TestClient   def test_sign_up(temp_db):     request_data = {         &quot;email&quot;: &quot;vader@deathstar.com&quot;,         &quot;name&quot;: &quot;Darth&quot;,         &quot;password&quot;: &quot;rainbow&quot;     }     with TestClient(app) as client:         response = client.post(&quot;\/sign-up&quot;, json=request_data)     assert response.status_code == 200     assert response.json()[&quot;id&quot;] == 1     assert response.json()[&quot;email&quot;] == &quot;vader@deathstar.com&quot;     assert response.json()[&quot;name&quot;] == &quot;Darth&quot;     assert response.json()[&quot;token&quot;][&quot;expires&quot;] is not None     assert response.json()[&quot;token&quot;][&quot;token&quot;] is not None   def test_login(temp_db):     request_data = {&quot;username&quot;: &quot;vader@deathstar.com&quot;, &quot;password&quot;: &quot;rainbow&quot;}     with TestClient(app) as client:         response = client.post(&quot;\/auth&quot;, data=request_data)     assert response.status_code == 200     assert response.json()[&quot;token_type&quot;] == &quot;bearer&quot;     assert response.json()[&quot;expires&quot;] is not None     assert response.json()[&quot;access_token&quot;] is not None   def test_login_with_invalid_password(temp_db):     request_data = {&quot;username&quot;: &quot;vader@deathstar.com&quot;, &quot;password&quot;: &quot;unicorn&quot;}     with TestClient(app) as client:         response = client.post(&quot;\/auth&quot;, data=request_data)     assert response.status_code == 400     assert response.json()[&quot;detail&quot;] == &quot;Incorrect email or password&quot;   def test_user_detail(temp_db):     with TestClient(app) as client:         # Create user token to see user info         loop = asyncio.get_event_loop()         token = loop.run_until_complete(create_user_token(user_id=1))         response = client.get(             &quot;\/users\/me&quot;,             headers={&quot;Authorization&quot;: f&quot;Bearer {token['token']}&quot;}         )     assert response.status_code == 200     assert response.json()[&quot;id&quot;] == 1     assert response.json()[&quot;email&quot;] == &quot;vader@deathstar.com&quot;     assert response.json()[&quot;name&quot;] == &quot;Darth&quot;   def test_user_detail_forbidden_without_token(temp_db):     with TestClient(app) as client:         response = client.get(&quot;\/users\/me&quot;)     assert response.status_code == 401   @pytest.mark.freeze_time(&quot;2015-10-21&quot;) def test_user_detail_forbidden_with_expired_token(temp_db, freezer):     user = UserCreate(         email=&quot;sidious@deathstar.com&quot;,         name=&quot;Palpatine&quot;,         password=&quot;unicorn&quot;     )     with TestClient(app) as client:         # Create user and use expired token         loop = asyncio.get_event_loop()         user_db = loop.run_until_complete(create_user(user))         freezer.move_to(&quot;'2015-11-10'&quot;)         response = client.get(             &quot;\/users\/me&quot;,             headers={&quot;Authorization&quot;: f&quot;Bearer {user_db['token']['token']}&quot;}         )     assert response.status_code == 401 <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<h2>P.S. \u0418\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0438<\/h2>\n<p>  \u0412\u043e\u0442 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0438 \u0432\u0441\u0435, \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0441 \u0438\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0430\u043c\u0438 \u0438\u0437 \u043f\u043e\u0441\u0442\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c <a href=\"https:\/\/github.com\/NeverWalkAloner\/async-blogs\" rel=\"nofollow\">\u043d\u0430 GitHub<\/a>.<\/div>\n<p> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/513328\/\"> https:\/\/habr.com\/ru\/post\/513328\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\" data-io-article-url=\"https:\/\/habr.com\/ru\/post\/513328\/\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/post_images\/76e\/ee9\/e74\/76eee9e74c12fdf22c682fe5475f2ab2.png\" alt=\"image\"><br \/>  \u0417\u043d\u0430\u044e, \u0437\u043d\u0430\u044e, \u043d\u0430\u0432\u0435\u0440\u043d\u043e\u0435 \u0432\u044b \u0441\u0435\u0439\u0447\u0430\u0441 \u0434\u0443\u043c\u0430\u0435\u0442\u0435 \u00ab\u0447\u0442\u043e \u043e\u043f\u044f\u0442\u044c?!\u00bb. <br \/>  \u0414\u0430, \u043d\u0430 \u0445\u0430\u0431\u0440\u0435 <a href=\"https:\/\/habr.com\/ru\/company\/otus\/blog\/511840\/\">\u0443\u0436\u0435<\/a> <a href=\"https:\/\/habr.com\/ru\/post\/488468\/\">\u043d\u0435\u043e\u0434\u043d\u043e\u043a\u0440\u0430\u0442\u043d\u043e<\/a> <a href=\"https:\/\/habr.com\/ru\/post\/478620\/\">\u043f\u0438\u0441\u0430\u043b\u0438<\/a> \u043e \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0435 <a href=\"https:\/\/fastapi.tiangolo.com\" rel=\"nofollow\">FastAPI<\/a>. \u041d\u043e \u044f \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u044d\u0442\u043e\u0442 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c API \u0441\u0432\u043e\u0435\u0433\u043e \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0433\u043e \u043c\u0438\u043d\u0438 \u0425\u0430\u0431\u0440\u0430 \u0431\u0435\u0437 \u043a\u0430\u0440\u043c\u044b \u0438 \u0440\u0435\u0439\u0442\u0438\u043d\u0433\u043e\u0432, \u0437\u0430\u0442\u043e <s>\u0441 \u0431\u043b\u044d\u043a\u0434\u0436\u0435\u043a\u043e\u043c \u0438<\/s> \u0441 \u0442\u0435\u0441\u0442\u0430\u043c\u0438, \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0435\u0439, \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f\u043c\u0438 \u0438 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u043e\u0439 \u0441 \u0411\u0414. <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-307954","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/307954","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=307954"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/307954\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=307954"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=307954"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=307954"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}