{"id":343652,"date":"2023-01-08T15:01:57","date_gmt":"2023-01-08T15:01:57","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=343652"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=343652","title":{"rendered":"<span>\u041a\u0430\u043a \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u043f\u0438\u0441\u0430\u0442\u044c API \u0430\u0432\u0442\u043e \u0442\u0435\u0441\u0442\u044b \u043d\u0430 Python<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<h2>\u0412\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0435<\/h2>\n<p>\u042d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u043a\u0430\u043a \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0435\u043d\u0438\u0435 \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/708932\/\" rel=\"noopener noreferrer nofollow\"><u>\u041a\u0430\u043a \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u043f\u0438\u0441\u0430\u0442\u044c UI \u0430\u0432\u0442\u043e \u0442\u0435\u0441\u0442\u044b \u043d\u0430 Python<\/u><\/a>. \u0415\u0441\u043b\u0438 \u043c\u044b \u0433\u043e\u0432\u043e\u0440\u0438\u043c \u043f\u0440\u043e UI \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b, \u0442\u043e \u0442\u0443\u0442 \u0445\u043e\u0442\u044f \u0431\u044b \u0435\u0441\u0442\u044c \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u044b Page Object, Pagefactory; \u0434\u043b\u044f API \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u043e\u0432 \u0442\u0430\u043a\u0438\u0445 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u043e\u0432 \u043d\u0435\u0442. \u0414\u0430, \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0442 \u043e\u0431\u0449\u0438\u0435 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u044b, \u043f\u043e \u0442\u0438\u043f\u0443 <a href=\"https:\/\/refactoring.guru\/design-patterns\/decorator\" rel=\"noopener noreferrer nofollow\"><u>Decorator<\/u><\/a>, <a href=\"https:\/\/refactoring.guru\/design-patterns\/singleton\" rel=\"noopener noreferrer nofollow\"><u>SIngletone<\/u><\/a>, <a href=\"https:\/\/refactoring.guru\/design-patterns\/facade\" rel=\"noopener noreferrer nofollow\"><u>Facade<\/u><\/a>, <a href=\"https:\/\/refactoring.guru\/design-patterns\/abstract-factory\" rel=\"noopener noreferrer nofollow\"><u>Abstract Factory<\/u><\/a>, \u043d\u043e \u044d\u0442\u043e \u043d\u0435 \u0442\u043e, \u0447\u0442\u043e \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c <strong>\u0431\u0438\u0437\u043d\u0435\u0441 \u043b\u043e\u0433\u0438\u043a\u0443<\/strong>. \u041a\u043e\u0433\u0434\u0430 \u043c\u044b \u043f\u0438\u0448\u0435\u043c API \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b, \u0442\u043e \u043d\u0430\u043c \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0431\u044b, \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u0438 \u043e\u0442\u0432\u0435\u0447\u0430\u043b\u0438 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f\u043c:<\/p>\n<ol>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u043f\u043e\u043b\u043d\u044b\u043c\u0438, \u0442\u043e \u0435\u0441\u0442\u044c \u043c\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0441\u0442\u0430\u0442\u0443\u0441 \u043a\u043e\u0434 \u043e\u0442\u0432\u0435\u0442\u0430, \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u0442\u0435\u043b\u0435 \u043e\u0442\u0432\u0435\u0442\u0430, \u043f\u0440\u043e\u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c JSON \u0441\u0445\u0435\u043c\u0443;<\/p>\n<\/li>\n<li>\n<p>\u0410\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c\u0438 \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u043c\u0438. \u0427\u0442\u043e\u0431\u044b \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b \u043c\u043e\u0433 \u0447\u0438\u0442\u0430\u0442\u044c \u0438 \u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e QA Automation, \u043d\u043e \u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a;<\/p>\n<\/li>\n<li>\n<p>\u0425\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0431\u044b, \u0447\u0442\u043e\u0431\u044b JSON \u0441\u0445\u0435\u043c\u0430 \u0438 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043b\u0438\u0441\u044c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438;<\/p>\n<\/li>\n<li>\n<p>\u041e\u0442\u0447\u0435\u0442 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0447\u0438\u0442\u0430\u0431\u0435\u043b\u044c\u043d\u044b\u043c, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0432 \u0432 \u0441\u0435\u0431\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0441\u0441\u044b\u043b\u043a\u0430\u0445, \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u0445, \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u0445, \u0441 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u043b\u044f\u0442\u044c \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u043b\u043e\u0433\u0438.<\/p>\n<\/li>\n<\/ol>\n<p>\u0414\u043b\u044f \u043c\u0435\u043d\u044f \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u044b\u0448\u0435 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0431\u0430\u0437\u043e\u0439, \u0432\u0430\u0448\u0438 \u0436\u0435 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0434\u0440\u0443\u0433\u0438\u0435 \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0430.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043e\u0447\u0435\u043d\u044c \u0432\u0430\u0436\u043d\u043e \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u0435\u0441\u043b\u0438 \u043f\u0440\u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0438 \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u043e\u0432 \u0432\u044b \u0432\u044b\u0431\u0435\u0440\u0435\u0442\u0435 \u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0434\u0445\u043e\u0434, \u0442\u043e \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u043f\u043e\u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043d\u0435 \u0441\u0440\u0430\u0437\u0443, \u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0447\u0435\u0440\u0435\u0437 100-150 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u043e\u0432. \u0422\u043e\u0433\u0434\u0430 \u0444\u0438\u043a\u0441\u044b \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u043e\u0432 \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u044f\u0442\u0441\u044f \u0432 \u0430\u0434, \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u043e\u0432\u044b\u0445 \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u043e\u0432 \u0431\u0443\u0434\u0435\u0442 \u0432\u0441\u0435 \u0441\u043b\u043e\u0436\u043d\u0435\u0435 \u0438 \u0441\u043b\u043e\u0436\u043d\u0435\u0435, \u0430 \u0447\u0438\u0442\u0430\u0442\u044c \u0442\u0430\u043a\u0438\u0435 \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b \u043d\u0438\u043a\u0442\u043e \u043a\u0440\u043e\u043c\u0435 \u0432\u0430\u0441 \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u0442, \u0447\u0442\u043e \u043f\u043b\u043e\u0445\u043e. \u0412 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435 \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u044e\u0442\u0441\u044f \u0441\u043b\u0443\u0447\u0430\u0438, \u043a\u043e\u0433\u0434\u0430 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044f \u043f\u0440\u043e\u0441\u0438\u0442 \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u0430\u0442\u044c \u0438\u0445 \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b \u0438 \u043e\u0447\u0435\u043d\u044c \u0447\u0430\u0441\u0442\u043e \u043c\u043e\u0442\u0438\u0432\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f: \u201c\u041d\u0430\u0448 QA Automation \u0443\u0448\u0435\u043b, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0442\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u043c \u0434\u0430\u0436\u0435 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b \u0438 \u043d\u0435\u043f\u043e\u043d\u044f\u0442\u043d\u043e, \u0447\u0442\u043e \u0432 \u043d\u0438\u0445 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442\u201d. \u042d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u0447\u0435\u043b\u043e\u0432\u0435\u043a, \u043d\u0430\u043f\u0438\u0441\u0430\u0432\u0448\u0438\u0439 \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b, \u043f\u0438\u0441\u0430\u043b \u0438\u0445 \u043a\u043e\u0441\u0442\u044b\u043b\u044c\u043d\u043e, \u043a\u0430\u043a \u0431\u044b \u043f\u043e\u0432\u044b\u0448\u0430\u044f \u0441\u0432\u043e\u044e <strong>\u0446\u0435\u043d\u043d\u043e\u0441\u0442\u044c<\/strong> (\u0432 \u043f\u043b\u043e\u0445\u043e\u043c \u0441\u043c\u044b\u0441\u043b\u0435, \u0447\u0442\u043e \u043d\u0438\u043a\u0442\u043e, \u043a\u0440\u043e\u043c\u0435 \u043d\u0435\u0433\u043e, \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043d\u044f\u0442\u044c \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u043f\u043e\u0441\u043b\u0435 \u0435\u0433\u043e \u0443\u0445\u043e\u0434\u0430 \u0438\u043b\u0438 \u0431\u0430\u043d\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0443\u0445\u043e\u0434\u0430 \u043d\u0430 \u0431\u043e\u043b\u044c\u043d\u0438\u0447\u043d\u044b\u0439), \u043a\u0430\u043a \u0441\u043e\u0442\u0440\u0443\u0434\u043d\u0438\u043a\u0430, \u0447\u0442\u043e \u043e\u0447\u0435\u043d\u044c \u043f\u043b\u043e\u0445\u043e \u0434\u043b\u044f \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438. \u0412 \u0438\u0442\u043e\u0433\u0435 \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0442\u0440\u0430\u0447\u0435\u043d\u043e, \u0434\u0435\u043d\u044c\u0433\u0438 \u043f\u043e\u0442\u0440\u0430\u0447\u0435\u043d\u043e.<\/p>\n<p>\u0415\u0449\u0435 \u043e\u0434\u0438\u043d \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0439 \u043a\u0435\u0439\u0441 &#8212; \u044d\u0442\u043e \u043a\u043e\u0433\u0434\u0430 \u043d\u043e\u0432\u044b\u0439 QA Automation \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043d\u0430 \u043f\u0440\u043e\u0435\u043a\u0442 \u0438 \u0441\u0440\u0430\u0437\u0443 \u0436\u0435 \u0445\u043e\u0447\u0435\u0442 \u0432\u0441\u0435 \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u0430\u0442\u044c. \u041e\u043a\u0430\u0439, \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442, \u0441\u0443\u0442\u044c \u043d\u0435 \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f, \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u044f \u0442\u0430\u043a\u0436\u0435 \u0441\u0442\u0440\u0430\u0434\u0430\u0435\u0442. \u041f\u043e &#171;\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u043c\u0443&#187; \u043c\u043d\u0435\u043d\u0438\u044e \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u0441\u0435 \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u0430\u043b, \u0432\u0438\u043d\u043e\u0432\u0430\u0442 \u043f\u0440\u043e\u0434\u0443\u043a\u0442, \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438, \u043d\u043e \u043d\u0435 \u043e\u043d \u0441\u0430\u043c. \u041a\u043e\u043c\u043f\u0430\u043d\u0438\u044f \u0432 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0432\u044b\u0441\u0442\u0443\u043f\u0430\u0435\u0442 \u0442\u0440\u0435\u043d\u0430\u0436\u0435\u0440\u043e\u043c\/\u043f\u043b\u0435\u0439\u0433\u0440\u0430\u0443\u043d\u0434\u043e\u043c \u0434\u043b\u044f \u043d\u0435\u043e\u043f\u044b\u0442\u043d\u043e\u0433\u043e QA Automation. \u0412 \u0438\u0442\u043e\u0433\u0435 \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0442\u0440\u0430\u0447\u0435\u043d\u043e, \u0434\u0435\u043d\u044c\u0433\u0438 \u043f\u043e\u0442\u0440\u0430\u0447\u0435\u043d\u043e.<\/p>\n<h2>Requirements<\/h2>\n<p>\u0414\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f API \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u043e\u0432 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c:<\/p>\n<ul>\n<li>\n<p>pytest &#8212; pip install pytest;<\/p>\n<\/li>\n<li>\n<p>httpx &#8212; pip install httpx, &#8212; \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 HTTP \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u043e\u043c;<\/p>\n<\/li>\n<li>\n<p>allure &#8212; pip install allure-pytest, &#8212; \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043b\u044e\u0431\u043e\u0439 \u0434\u0440\u0443\u0433\u043e\u0439 \u0440\u0435\u043f\u043e\u0440\u0442\u0435\u0440;<\/p>\n<\/li>\n<li>\n<p>jsonschema &#8212; pip install jsonschema, &#8212; \u0434\u043b\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 JSON \u0441\u0445\u0435\u043c\u044b;<\/p>\n<\/li>\n<li>\n<p>pydantic, python-dotenv &#8212; pip install pydantic python-dotenv, &#8212; \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445, \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c\u0438, \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 JSON \u0441\u0445\u0435\u043c\u044b;<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u0447\u0435\u043c\u0443 \u043d\u0435 <a href=\"https:\/\/requests.readthedocs.io\/en\/latest\/\" rel=\"noopener noreferrer nofollow\"><u>requests<\/u><\/a>? \u041c\u043d\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f httpx, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043e\u043d \u0443\u043c\u0435\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e \u0438 \u0443 \u043d\u0435\u0433\u043e \u0435\u0441\u0442\u044c AsyncClient. \u0422\u0430\u043a\u0436\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f <a href=\"https:\/\/www.python-httpx.org\/\" rel=\"noopener noreferrer nofollow\"><u>httpx<\/u><\/a> \u0432 \u0441\u0442\u0438\u043b\u0435 Material Design \u043c\u043d\u0435 \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f, \u0447\u0435\u043c \u0443 requests. \u0412 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u043c requests \u0437\u0430\u043c\u0435\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430, \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0435\u0435 \u0438  \u0440\u0430\u0437\u043d\u0438\u0446\u044b \u043d\u0438\u043a\u0430\u043a\u043e\u0439 \u043d\u0435\u0442.<\/p>\n<p>\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <a href=\"https:\/\/docs.pydantic.dev\/\" rel=\"noopener noreferrer nofollow\"><u>pydantic<\/u><\/a> \u0441\u043b\u0443\u0436\u0438\u0442 \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u201c\u0441\u0442\u0440\u043e\u0433\u043e\u0439 \u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u0438\u201d \u0432 python. \u041e\u043d\u0430 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u0430 \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 JSON \u0441\u0445\u0435\u043c\u044b, \u0434\u043b\u044f \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u0434\u0430\u043d\u043d\u044b\u0445, \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445. \u0423 \u044d\u0442\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0435\u0441\u0442\u044c \u043c\u043d\u043e\u0433\u043e \u043f\u043b\u044e\u0441\u043e\u0432 \u043f\u043e \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044e \u0441 \u043e\u0431\u044b\u0447\u043d\u044b\u043c\u0438 dataclass-\u0441\u0430\u043c\u0438 \u0432 python. \u0415\u0441\u043b\u0438 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u0440 \u0438\u0437 \u0436\u0438\u0437\u043d\u0438, \u0442\u043e pydantic &#8212; \u044d\u0442\u043e \u043a\u0430\u043a \u0435\u0445\u0430\u0442\u044c \u043d\u0430 \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0438\u043b\u0435, \u0430 dataclass&#8217;\u044b &#8212; \u044d\u0442\u043e \u0438\u0434\u0442\u0438 \u043f\u0435\u0448\u043a\u043e\u043c.\u00a0<\/p>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u044b pydantic \u043c\u043e\u0436\u043d\u043e \u0432\u0437\u044f\u0442\u044c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <a href=\"https:\/\/github.com\/Nikita-Filonov\/models_manager\" rel=\"noopener noreferrer nofollow\"><u>models-manager<\/u><\/a>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0434\u0435\u043b\u0430\u0435\u0442 \u0432\u0441\u0435 \u0442\u043e\u0436\u0435 \u0441\u0430\u043c\u043e\u0435, \u0447\u0442\u043e \u0438 pydantic, \u0442.\u0435. \u0443\u043c\u0435\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 \u0431\u0430\u0437\u043e\u0439 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438, \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0440\u0430\u043d\u0434\u043e\u043c\u043d\u044b\u0435 \u043d\u0435\u0433\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u043c\u043e\u0434\u0435\u043b\u0438. \u042d\u0442\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0431\u043e\u043b\u044c\u0448\u0435 \u043f\u043e\u0434\u043e\u0439\u0434\u0435\u0442 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u0432\u0445\u043e\u0434\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432\u0430\u0448\u0435\u0433\u043e API. \u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044e \u043f\u043e models-manager \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 <a href=\"https:\/\/nikita-filonov.github.io\/models_manager\/\" rel=\"noopener noreferrer nofollow\"><u>\u0442\u0443\u0442<\/u><\/a>. \u041c\u044b \u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c models-manager, \u0442\u0430\u043a \u043a\u0430\u043a \u043d\u0430\u043c \u043d\u0435 \u043d\u0443\u0436\u043d\u0430 \u0431\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u043c\u044b \u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e.<\/p>\n<p>\u041d\u043e \u0443 pydantic \u0442\u043e\u0436\u0435 \u0435\u0441\u0442\u044c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <a href=\"https:\/\/sqlmodel.tiangolo.com\/\" rel=\"noopener noreferrer nofollow\"><u>SQLModel<\/u><\/a> \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0431\u0430\u0437\u043e\u0439 \u0434\u0430\u043d\u043d\u044b\u0445. \u0415\u0441\u043b\u0438 \u0432\u0430\u043c \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u043e\u0432 \u043d\u0443\u0436\u043d\u0430 \u0431\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445, \u0442\u043e \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c: SQLAlchemy + pydantic, SQLModel, models-manager. \u0412 \u043d\u0430\u0448\u0435\u043c \u0436\u0435 \u0441\u043b\u0443\u0447\u0430\u0435 \u0440\u0430\u0431\u043e\u0442\u0430 \u0441 \u0431\u0430\u0437\u043e\u0439 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0435 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f.<\/p>\n<p>\u0422\u0435\u0441\u0442\u044b \u0431\u0443\u0434\u0435\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 API <a href=\"https:\/\/sampleapis.com\/api-list\/futurama\" rel=\"noopener noreferrer nofollow\">https:\/\/sampleapis.com\/api-list\/futurama<\/a>. \u0414\u0430\u043d\u043d\u044b\u0439 API \u0432\u0441\u0435\u0433\u043e \u043b\u0438\u0448\u044c \u043f\u0440\u0438\u043c\u0435\u0440. \u041d\u0430 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445 API \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0433\u043e\u0440\u0430\u0437\u0434\u043e \u0441\u043b\u043e\u0436\u043d\u0435\u0435, \u043d\u043e <strong>\u0441\u0443\u0442\u044c<\/strong> \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u043e\u0432 \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u0442\u0430 \u0436\u0435. <\/p>\n<h2>Settings<\/h2>\n<p>\u041e\u043f\u0438\u0448\u0435\u043c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u043b\u0430\u0441\u0441 BaseSettings \u0438\u0437 pydantic, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043e\u043d \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u0443\u0434\u043e\u0431\u043d\u044b\u0439, \u0443\u043c\u0435\u0435\u0442 \u0447\u0438\u0442\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0437 .env \u0444\u0430\u0439\u043b\u0430, \u0443\u043c\u0435\u0435\u0442 \u0447\u0438\u0442\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0437 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u0443\u043c\u0435\u0435\u0442 \u0447\u0438\u0442\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0437 .txt \u0444\u0430\u0439\u043b\u0430, \u0443\u043c\u0435\u0435\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0430\u043c\u0438 \u043d\u0430 \u0440\u0435\u0434\u0438\u0441 \u0438\u043b\u0438 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u043c\u043d\u043e\u0433\u043e \u0447\u0435\u0433\u043e \u0435\u0449\u0435, \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0442\u0443\u0442 <a href=\"https:\/\/docs.pydantic.dev\/usage\/settings\/\" rel=\"noopener noreferrer nofollow\"><u>https:\/\/docs.pydantic.dev\/usage\/settings\/<\/u><\/a>. \u042d\u0442\u043e \u043e\u0447\u0435\u043d\u044c \u0443\u0434\u043e\u0431\u043d\u043e \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043d\u0430 <strong>CI\/CD<\/strong>, \u0438\u043b\u0438 \u043a\u043e\u0433\u0434\u0430 \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u043c\u043d\u043e\u0433\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0440\u0430\u0437\u0431\u0440\u043e\u0441\u0430\u043d\u044b \u043f\u043e \u0432\u0441\u0435\u043c\u0443 \u043f\u0440\u043e\u0435\u043a\u0442\u0443 + \u0441 BaseSettings \u0432\u0441\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043c\u043e\u0436\u043d\u043e \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0432 \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442.<\/p>\n<pre><code class=\"python\">from pydantic import BaseModel, BaseSettings, Field   class TestUser(BaseModel):     email: str     password: str   class Settings(BaseSettings):     base_url: str = Field(..., env='BASE_URL')     user_email: str = Field(..., env='TEST_USER_EMAIL')     user_password: str = Field(..., env='TEST_USER_PASSWORD')      class Config:         env_file = '.env'         env_file_encoding = 'utf-8'      @property     def api_url(self) -> str:         return f'{self.base_url}\/futurama'      @property     def user(self) -> TestUser:         return TestUser(             email=self.user_email,             password=self.user_password         )   base_settings = Settings()<\/code><\/pre>\n<p>\u041c\u044b \u0431\u0443\u0434\u0435\u043c \u0447\u0438\u0442\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0437 .env \u0444\u0430\u0439\u043b\u0430.<\/p>\n<h2>Models<\/h2>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u043f\u0438\u0448\u0435\u043c \u043c\u043e\u0434\u0435\u043b\u0438, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f pydantic, \u043f\u0435\u0440\u0435\u0434 \u044d\u0442\u0438\u043c \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0431\u0430\u0437\u043e\u0432\u0443\u044e \u043c\u043e\u0434\u0435\u043b\u044c \u0438\u0437 pydantic. \u041d\u0430\u043c \u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u043a\u0440\u044b\u0442\u044c \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 <strong>\u043b\u0438\u043c\u0438\u0442\u0430\u0446\u0438\u0438 \u0438 \u0431\u0430\u0433\u0438<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0435\u0441\u0442\u044c \u0432 pydantic. <\/p>\n<p>utils\\models\\base_model.py<\/p>\n<pre><code class=\"python\">from pydantic import BaseModel as PydanticBaseModel   class BaseModel(PydanticBaseModel):     class Config:         @staticmethod         def schema_extra(schema: dict, model: PydanticBaseModel):             \"\"\"             https:\/\/github.com\/pydantic\/pydantic\/issues\/1270#issuecomment-729555558             \"\"\"             for prop, value in schema.get('properties', {}).items():                 field = [                     model_field for model_field in model.__fields__.values()                     if model_field.alias == prop                 ][0]                  if field.allow_none:                     if 'type' in value:                         value['anyOf'] = [{'type': value.pop('type')}]                      elif '$ref' in value:                         if issubclass(field.type_, BaseModel):                             value['title'] = field.type_.__config__.title or field.type_.__name__                         value['anyOf'] = [{'$ref': value.pop('$ref')}]                     value['anyOf'].append({'type': 'null'})      def __hash__(self):         \"\"\"         https:\/\/github.com\/pydantic\/pydantic\/issues\/1303#issuecomment-599712964         \"\"\"         return hash((type(self),) + tuple(self.__dict__.values())) <\/code><\/pre>\n<p>\u042f \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u0438\u043b \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 github issue, \u0433\u0434\u0435 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435, \u043a\u0430\u043a\u0430\u044f \u0438\u043c\u0435\u043d\u043d\u043e \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f. \u0415\u0441\u043b\u0438 \u0431\u0443\u0434\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c pydantic, \u0442\u043e \u0432\u0430\u043c \u044d\u0442\u043e \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u0441\u044f. \u041d\u0443 \u0438\u043b\u0438 \u0436\u0435 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <a href=\"https:\/\/github.com\/Nikita-Filonov\/models_manager\" rel=\"noopener noreferrer nofollow\">models-manager<\/a>, \u0438\u0431\u043e \u0442\u0430\u043c \u043d\u0435\u0442 \u044d\u0442\u0438\u0445 \u043f\u0440\u043e\u0431\u043b\u0435\u043c.<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u043f\u0438\u0448\u0435\u043c \u043c\u043e\u0434\u0435\u043b\u044c \u0434\u043b\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438:<\/p>\n<p>models\\<a href=\"http:\/\/authentication.py\" rel=\"noopener noreferrer nofollow\">authentication.py<\/a><\/p>\n<pre><code class=\"python\">from pydantic import Field  from settings import base_settings from utils.models.base_model import BaseModel   class AuthUser(BaseModel):     email: str = Field(default=base_settings.user.email)     password: str = Field(default=base_settings.user.password)   class Authentication(BaseModel):     auth_token: str | None     user: AuthUser | None = AuthUser()<\/code><\/pre>\n<p>\u041d\u0430\u043f\u0438\u0448\u0435\u043c \u043c\u043e\u0434\u0435\u043b\u044c \u0434\u043b\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u0430 question \u0438\u0437 API <a href=\"https:\/\/sampleapis.com\/api-list\/futurama\" rel=\"noopener noreferrer nofollow\">https:\/\/sampleapis.com\/api-list\/futurama<\/a>. \u0421\u0430\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a:<\/p>\n<pre><code class=\"json\">{   \"id\": 1,   \"question\": \"What is Fry's first name?\",   \"possibleAnswers\": [     \"Fred\",     \"Philip\",     \"Will\",     \"John\"   ],   \"correctAnswer\": \"Philip\" }<\/code><\/pre>\n<p>models\\<a href=\"http:\/\/questions.py\" rel=\"noopener noreferrer nofollow\">questions.py<\/a><\/p>\n<pre><code class=\"python\">from typing import TypedDict  from pydantic import BaseModel, Field  from utils.fakers import random_list_of_strings, random_number, random_string   class UpdateQuestion(BaseModel):     question: str | None = Field(default_factory=random_string)     possible_answers: list[str] | None = Field(         alias='possibleAnswers',         default_factory=random_list_of_strings     )     correct_answer: str | None = Field(         alias='correctAnswer',         default_factory=random_string     )   class DefaultQuestion(BaseModel):     id: int = Field(default_factory=random_number)     question: str = Field(default_factory=random_string)     possible_answers: list[str] = Field(         alias='possibleAnswers',         default_factory=random_list_of_strings     )     correct_answer: str = Field(         alias='correctAnswer',         default_factory=random_string     )   class DefaultQuestionsList(BaseModel):     __root__: list[DefaultQuestion]   class QuestionDict(TypedDict):     id: int     question: str     possibleAnswers: list[str]     correct_answer: str <\/code><\/pre>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442 <strong>alias<\/strong> \u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 Field. \u041e\u043d \u0441\u043b\u0443\u0436\u0438\u0442 \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043c\u044b \u043c\u043e\u0433\u043b\u0438 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441\u043e <strong>snake_case<\/strong> \u0432 python \u0438 \u0441 \u043b\u044e\u0431\u044b\u043c \u0434\u0440\u0443\u0433\u0438\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u043e\u043c \u0438\u0437\u0432\u043d\u0435. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432 python \u043d\u0430\u043c \u0431\u044b \u043d\u0435 \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c &#8212; possibleAnswers, \u0442.\u043a. \u044d\u0442\u043e \u043d\u0430\u0440\u0443\u0448\u0430\u0435\u0442 PEP8, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c <strong>alias<\/strong>. Pydantic \u0441\u0430\u043c \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u0442\u0441\u044f, \u043a\u0430\u043a \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c JSON \u043e\u0431\u044a\u0435\u043a\u0442 \u0438 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c \u0435\u0433\u043e \u043f\u043e \u043d\u0443\u0436\u043d\u044b\u043c \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430\u043c \u0432 \u043c\u043e\u0434\u0435\u043b\u0438. \u0422\u0430\u043a \u0436\u0435 \u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 Field \u0435\u0441\u0442\u044c \u043e\u0447\u0435\u043d\u044c \u043c\u043d\u043e\u0433\u043e \u043a\u0440\u0443\u0442\u044b\u0445 \u0444\u0438\u0447 \u043f\u043e \u0442\u0438\u043f\u0443: max_length, min_length, gt, ge, lt, le \u0438 \u043c\u043e\u0436\u043d\u043e \u043f\u0438\u0441\u0430\u0442\u044c \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043a\u0438. \u0415\u0441\u0442\u044c <a href=\"https:\/\/docs.pydantic.dev\/usage\/model_config\/\" rel=\"noopener noreferrer nofollow\">\u043a\u0443\u0447\u0430 \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0445 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a<\/a> \u0434\u043b\u044f \u0432\u0430\u0448\u0438\u0445 \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u0438 \u0435\u0441\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c <a href=\"https:\/\/docs.pydantic.dev\/usage\/types\/\" rel=\"noopener noreferrer nofollow\">\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0435 \u0442\u0438\u043f\u044b<\/a> \u0438\u043b\u0438 \u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0432\u043e\u0438. \u041a\u043e\u0440\u043e\u0447\u0435, \u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435\u0441\u044c.<\/p>\n<p>\u0414\u0430\u043d\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438: random_list_of_strings, random_number, random_string \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f, \u0447\u0442\u043e\u0431\u044b \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u0440\u0430\u043d\u0434\u043e\u043c\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435. \u041c\u044b \u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u0443\u0441\u043b\u043e\u0436\u043d\u044f\u0442\u044c \u0438 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u044d\u0442\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0435 \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430 python, \u0432 \u0441\u0432\u043e\u0438\u0445 \u0436\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <a href=\"https:\/\/faker.readthedocs.io\/en\/master\/\" rel=\"noopener noreferrer nofollow\">faker<\/a>.<\/p>\n<p>utils\\<a href=\"http:\/\/fakers.py\" rel=\"noopener noreferrer nofollow\">fakers.py<\/a><\/p>\n<pre><code class=\"python\">from random import choice, randint from string import ascii_letters, digits   def random_number(start: int = 100, end: int = 1000) -> int:     return randint(start, end)   def random_string(start: int = 9, end: int = 15) -> str:     return ''.join(choice(ascii_letters + digits) for _ in range(randint(start, end)))   def random_list_of_strings(start: int = 9, end: int = 15) -> list[str]:     return [random_string() for _ in range(randint(start, end))] <\/code><\/pre>\n<p>\u0413\u043e\u0442\u043e\u0432\u043e, \u043c\u044b \u043e\u043f\u0438\u0441\u0430\u043b\u0438 \u043d\u0443\u0436\u043d\u044b\u0435 \u043d\u0430\u043c \u043c\u043e\u0434\u0435\u043b\u0438. \u0421 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043d\u0438\u0445 \u043c\u043e\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435:<\/p>\n<pre><code class=\"python\">DefaultQuestion().dict(by_alias=True)  {   'id': 859,    'question': 'a5mii6xsAmxZ',    'possibleAnswers': ['3HW4gA0HW', 'dcp07Wm2EHM9X4', '4oSm5xSIF', 'SSQXoUrYc', 'xeCV3GGduHjI', '9ScfUI2pF', 'b5ezRFJ8m8', '9fY1nKTNlp', '4BbKZUamwJjDnG', 'PRdHxVgH0lmSL', 'b4budMBfz', 'Oe62YMnC7wRb', 'BI6DUSsct4aCE', 'WIxX0efx6t5IPxd', 'x3ZKlXXTGEd'],    'correctAnswer': 'fX7nXClR6nS' }<\/code><\/pre>\n<p>JSON \u0441\u0445\u0435\u043c\u0430 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u043c\u043e\u0434\u0435\u043b\u0438. \u0412 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435 \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u043b \u043b\u044e\u0434\u0435\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0438\u0441\u0430\u043b\u0438 JSON \u0441\u0445\u0435\u043c\u0443 <strong>\u0440\u0443\u043a\u0430\u043c\u0438<\/strong>, \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0441\u0447\u0438\u0442\u0430\u043b\u0438 \u044d\u0442\u043e \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u043c \u0432\u0435\u0440\u043d\u044b\u043c \u043f\u043e\u0434\u0445\u043e\u0434\u043e\u043c, \u043d\u043e \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u0442\u0430\u043a. \u0412\u0435\u0434\u044c \u0435\u0441\u043b\u0438 \u043e\u0431\u044a\u0435\u043a\u0442 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 4-\u0445 \u043f\u043e\u043b\u0435\u0439, \u043a\u0430\u043a \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u0442\u043e \u0435\u0449\u0435 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c JSON \u0441\u0445\u0435\u043c\u0443 \u0440\u0443\u043a\u0430\u043c\u0438, \u0430 \u0447\u0442\u043e \u0435\u0441\u043b\u0438 \u043e\u0431\u044a\u0435\u043a\u0442 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0445 30-\u0442\u0438 \u043f\u043e\u043b\u0435\u0439? \u0422\u0443\u0442 \u0443\u0436\u0435 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0438 \u043a\u0443\u0447\u0430 \u043f\u043e\u0442\u0440\u0430\u0447\u0435\u043d\u043d\u043e\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u044b \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0441\u043a\u0438\u0434\u044b\u0432\u0430\u0435\u043c \u044d\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0443 \u043d\u0430 pydantic:<\/p>\n<pre><code class=\"python\">DefaultQuestion().schema()  {   'title': 'DefaultQuestion',    'type': 'object',    'properties': {     'id': {'title': 'Id', 'type': 'integer'},      'question': {'title': 'Question', 'type': 'string'},      'possibleAnswers': {'title': 'Possibleanswers', 'type': 'array', 'items': {'type': 'string'}},      'correctAnswer': {'title': 'Correctanswer', 'type': 'string'}   } }<\/code><\/pre>\n<h2>API Client<\/h2>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u043f\u0438\u0448\u0435\u043c \u0431\u0430\u0437\u043e\u0432\u044b\u0439 API httpx \u043a\u043b\u0438\u0435\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f HTTP \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432:<\/p>\n<p>base\\<a href=\"http:\/\/client.py\" rel=\"noopener noreferrer nofollow\">client.py<\/a><\/p>\n<pre><code class=\"python\">import typing from functools import lru_cache  import allure from httpx import Client as HttpxClient from httpx import Response from httpx._client import UseClientDefault from httpx._types import (AuthTypes, CookieTypes, HeaderTypes, QueryParamTypes,                           RequestContent, RequestData, RequestExtensions,                           RequestFiles, TimeoutTypes, URLTypes)  from base.api.authentication_api import get_auth_token from models.authentication import Authentication from settings import base_settings   class Client(HttpxClient):     @allure.step('Making GET request to \"{url}\"')     def get(         self,         url: URLTypes,         *,         params: typing.Optional[QueryParamTypes] = None,         headers: typing.Optional[HeaderTypes] = None,         cookies: typing.Optional[CookieTypes] = None,         auth: typing.Union[AuthTypes, UseClientDefault] = None,         follow_redirects: typing.Union[bool, UseClientDefault] = None,         timeout: typing.Union[TimeoutTypes, UseClientDefault] = None,         extensions: typing.Optional[RequestExtensions] = None     ) -> Response:         return super().get(             url=url,             params=params,             headers=headers,             cookies=cookies,             auth=auth,             follow_redirects=follow_redirects,             timeout=timeout,             extensions=extensions         )      @allure.step('Making POST request to \"{url}\"')     def post(         self,         url: URLTypes,         *,         content: typing.Optional[RequestContent] = None,         data: typing.Optional[RequestData] = None,         files: typing.Optional[RequestFiles] = None,         json: typing.Optional[typing.Any] = None,         params: typing.Optional[QueryParamTypes] = None,         headers: typing.Optional[HeaderTypes] = None,         cookies: typing.Optional[CookieTypes] = None,         auth: typing.Union[AuthTypes, UseClientDefault] = None,         follow_redirects: typing.Union[bool, UseClientDefault] = None,         timeout: typing.Union[TimeoutTypes, UseClientDefault] = None,         extensions: typing.Optional[RequestExtensions] = None     ) -> Response:         return super().post(             url=url,             content=content,             data=data,             files=files,             json=json,             params=params,             headers=headers,             cookies=cookies,             auth=auth,             follow_redirects=follow_redirects,             timeout=timeout,             extensions=extensions         )      @allure.step('Making PATCH request to \"{url}\"')     def patch(         self,         url: URLTypes,         *,         content: typing.Optional[RequestContent] = None,         data: typing.Optional[RequestData] = None,         files: typing.Optional[RequestFiles] = None,         json: typing.Optional[typing.Any] = None,         params: typing.Optional[QueryParamTypes] = None,         headers: typing.Optional[HeaderTypes] = None,         cookies: typing.Optional[CookieTypes] = None,         auth: typing.Union[AuthTypes, UseClientDefault] = None,         follow_redirects: typing.Union[bool, UseClientDefault] = None,         timeout: typing.Union[TimeoutTypes, UseClientDefault] = None,         extensions: typing.Optional[RequestExtensions] = None     ) -> Response:         return super().patch(             url=url,             content=content,             data=data,             files=files,             json=json,             params=params,             headers=headers,             cookies=cookies,             auth=auth,             follow_redirects=follow_redirects,             timeout=timeout,             extensions=extensions         )      @allure.step('Making DELETE request to \"{url}\"')     def delete(         self,         url: URLTypes,         *,         params: typing.Optional[QueryParamTypes] = None,         headers: typing.Optional[HeaderTypes] = None,         cookies: typing.Optional[CookieTypes] = None,         auth: typing.Union[AuthTypes, UseClientDefault] = None,         follow_redirects: typing.Union[bool, UseClientDefault] = None,         timeout: typing.Union[TimeoutTypes, UseClientDefault] = None,         extensions: typing.Optional[RequestExtensions] = None     ) -> Response:         return super().delete(             url=url,             params=params,             headers=headers,             cookies=cookies,             auth=auth,             follow_redirects=follow_redirects,             timeout=timeout,             extensions=extensions         )   @lru_cache(maxsize=None) def get_client(     auth: Authentication | None = None,     base_url: str = base_settings.api_url ) -> Client:     headers: dict[str, str] = {}      if auth is None:         return Client(base_url=base_url, trust_env=True)      if (not auth.auth_token) and (not auth.user):         raise NotImplementedError(             'Please provide \"username\" and \"password\" or \"auth_token\"'         )      if (not auth.auth_token) and auth.user:         token = get_auth_token(auth.user)         headers = {**headers, 'Authorization': f'Token {token}'}      if auth.auth_token and (not auth.user):         headers = {**headers, 'Authorization': f'Token {auth.auth_token}'}      return Client(base_url=base_url, headers=headers, trust_env=True) <\/code><\/pre>\n<p>\u041c\u044b \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u0441\u0432\u043e\u0439 \u043a\u043b\u0430\u0441\u0441 Client, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043b\u0438 \u043e\u0442 httpx.Client \u0438 \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043b\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043d\u0430\u043c \u043c\u0435\u0442\u043e\u0434\u044b, \u0434\u043e\u0431\u0430\u0432\u0438\u0432 \u043a \u043d\u0438\u043c allure.step. \u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0440\u0438 http-\u0437\u0430\u043f\u0440\u043e\u0441\u0435 \u0447\u0435\u0440\u0435\u0437 Client \u0432 \u043e\u0442\u0447\u0435\u0442\u0435 \u0443 \u043d\u0430\u0441 \u0431\u0443\u0434\u0443\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u0442\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u043b\u0438. \u041c\u044b \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0438 allure.step, \u043a\u0430\u043a \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440, \u0447\u0442\u043e\u0431\u044b \u0432 \u043e\u0442\u0447\u0435\u0442 \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u043f\u0430\u043b\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0432\u043d\u0443\u0442\u0440\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043c\u0435\u0442\u043e\u0434\u0430. \u041f\u043e\u0437\u0436\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u043a\u0430\u043a \u044d\u0442\u043e \u0432\u0441\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0432 \u043e\u0442\u0447\u0435\u0442\u0435. \u0412\u043d\u0443\u0442\u0440\u044c Client \u043c\u044b \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0437\u0430\u043f\u0438\u0441\u044c \u043b\u043e\u0433\u043e\u0432 \u0438\u043b\u0438 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044c, \u043d\u043e \u0432 \u0434\u0430\u043d\u043d\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043e\u0431\u043e\u0439\u0434\u0435\u043c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e allure.step, \u043d\u0430 \u0441\u0432\u043e\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u044e get_client, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u043e\u0431\u044a\u0435\u043a\u0442 Client. \u042d\u0442\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b, \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438, base_url \u043e\u0442 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0431\u0443\u0434\u0435\u043c \u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043a API. \u0412 \u044d\u0442\u043e\u043c API <a href=\"https:\/\/sampleapis.com\/api-list\/futurama\" rel=\"noopener noreferrer nofollow\">https:\/\/sampleapis.com\/api-list\/futurama<\/a> \u043d\u0435\u0442 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438, \u044f \u0443\u043a\u0430\u0437\u0430\u043b \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0434\u043b\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u043f\u043e API Key \u0440\u0430\u0434\u0438 \u043f\u0440\u0438\u043c\u0435\u0440\u0430. \u0421\u043a\u043e\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043e \u043d\u0430 \u0432\u0430\u0448\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0443 \u0432\u0430\u0441 \u0431\u0443\u0434\u0435\u0442 \u0434\u0440\u0443\u0433\u043e\u0439 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0434\u043b\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438. <\/p>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0438 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440 lru_cache \u0434\u043b\u044f \u043a\u0435\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0430, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430.<\/p>\n<h2>API endpoints<\/h2>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u043f\u0438\u0448\u0435\u043c \u043c\u0435\u0442\u043e\u0434\u044b \u0434\u043b\u044f \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 API. <\/p>\n<p>\u0414\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u043e\u043f\u0438\u0448\u0435\u043c \u043c\u0435\u0442\u043e\u0434\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0435\u0439. \u0414\u043b\u044f <a href=\"https:\/\/sampleapis.com\/api-list\/futurama\" rel=\"noopener noreferrer nofollow\">https:\/\/sampleapis.com\/api-list\/futurama<\/a> \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f, \u043d\u043e \u0432 \u0441\u0432\u043e\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0432\u0430\u0448\u0438 \u043c\u0435\u0442\u043e\u0434\u044b \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430.<\/p>\n<p>base\\api\\authentication_api.py<\/p>\n<pre><code class=\"python\">from functools import lru_cache  from httpx import Client, Response  from models.authentication import AuthUser from settings import base_settings from utils.constants.routes import APIRoutes   def get_auth_token_api(payload: AuthUser) -> Response:     client = Client(base_url=base_settings.api_url)     return client.post(f'{APIRoutes.AUTH}\/token', json=payload.dict())   @lru_cache(maxsize=None) def get_auth_token(payload: AuthUser) -> str:     \"\"\"     Should be used like this:      response = get_auth_token_api(payload)     json_response = response.json()      assert response.status_code == HTTPStatus.OK     assert json_response.get('token')      return json_response['token']     \"\"\"     return 'token' <\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u043f\u0438\u0448\u0435\u043c \u043c\u0435\u0442\u043e\u0434\u044b \u0440\u0430\u0431\u043e\u0442\u044b \u0441 questions:<\/p>\n<pre><code class=\"python\">import allure from httpx import Response  from base.client import get_client from models.authentication import Authentication from models.questions import DefaultQuestion, UpdateQuestion from utils.constants.routes import APIRoutes   @allure.step(f'Getting all questions') def get_questions_api(auth: Authentication = Authentication()) -> Response:     client = get_client(auth=auth)     return client.get(APIRoutes.QUESTIONS)   @allure.step('Getting question with id \"{question_id}\"') def get_question_api(     question_id: int,     auth: Authentication = Authentication() ) -> Response:     client = get_client(auth=auth)     return client.get(f'{APIRoutes.QUESTIONS}\/{question_id}')   @allure.step('Creating question') def create_question_api(     payload: DefaultQuestion,     auth: Authentication = Authentication() ) -> Response:     client = get_client(auth=auth)     return client.post(APIRoutes.QUESTIONS, json=payload.dict(by_alias=True))   @allure.step('Updating question with id \"{question_id}\"') def update_question_api(     question_id: int,     payload: UpdateQuestion,     auth: Authentication = Authentication() ) -> Response:     client = get_client(auth=auth)     return client.patch(         f'{APIRoutes.QUESTIONS}\/{question_id}',         json=payload.dict(by_alias=True)     )   @allure.step('Deleting question with id \"{question_id}\"') def delete_question_api(     question_id: int,     auth: Authentication = Authentication() ) -> Response:     client = get_client(auth=auth)     return client.delete(f'{APIRoutes.QUESTIONS}\/{question_id}')   def create_question(auth: Authentication = Authentication()) -> DefaultQuestion:     payload = DefaultQuestion()      response = create_question_api(payload=payload, auth=auth)     return DefaultQuestion(**response.json()) <\/code><\/pre>\n<p>\u0421 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u043e\u0432 \u0432\u044b\u0448\u0435 \u0441\u043c\u043e\u0436\u0435\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u043f\u0440\u043e\u0441\u0442\u044b\u0435 CRUD \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043a API.<\/p>\n<h2>Utils<\/h2>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0443\u0442\u0438\u043b\u0438\u0442\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u043c\u043e\u0433\u0443\u0442 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u043b\u0443\u0447\u0448\u0435:<\/p>\n<p>utils\\constants\\<a href=\"http:\/\/routes.py\" rel=\"noopener noreferrer nofollow\">routes.py<\/a><\/p>\n<pre><code class=\"python\">from enum import Enum   class APIRoutes(str, Enum):     AUTH = '\/auth'     INFO = '\/info'     CAST = '\/cast'     EPISODES = '\/episodes'     QUESTIONS = '\/questions'     INVENTORY = '\/inventory'     CHARACTERS = '\/characters'      def __str__(self) -> str:         return self.value <\/code><\/pre>\n<p>\u041b\u0443\u0447\u0448\u0435 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0440\u043e\u0443\u0442\u0438\u043d\u0433\u0438 \u0432 enum, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u0434 \u0438 \u043d\u0430\u0433\u043b\u044f\u0434\u043d\u043e \u0432\u0438\u0434\u0435\u0442\u044c, \u043a\u0430\u043a\u0438\u0435 \u0440\u043e\u0443\u0442\u0438\u043d\u0433\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f:<\/p>\n<p>utils\\fixtures\\<a href=\"http:\/\/questions.py\" rel=\"noopener noreferrer nofollow\">questions.py<\/a><\/p>\n<pre><code class=\"python\">import pytest  from base.api.questions_api import create_question, delete_question_api from models.questions import DefaultQuestion   @pytest.fixture(scope='function') def function_question() -> DefaultQuestion:     question = create_question()     yield question      delete_question_api(question.id) <\/code><\/pre>\n<p>\u0414\u043b\u044f \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0442\u0435\u0441\u0442\u043e\u0432, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043d\u0430 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0438\u043b\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435, \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0444\u0438\u043a\u0441\u0442\u0443\u0440\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c question. \u041f\u043e\u0441\u043b\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u043e\u0431\u044a\u0435\u043a\u0442 DefaultQuestion \u0438 \u043a\u043e\u0433\u0434\u0430 \u0442\u0435\u0441\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f, \u0442\u043e \u0443\u0434\u0430\u043b\u0438\u043c \u0435\u0433\u043e delete_question_api(<a href=\"http:\/\/question.id\" rel=\"noopener noreferrer nofollow\">question.id<\/a>).<\/p>\n<p>conftest.py<\/p>\n<pre><code class=\"python\">pytest_plugins = (     'utils.fixtures.questions', )<\/code><\/pre>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0435 \u0437\u0430\u0431\u0443\u0434\u0435\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u0430\u0448\u0443 API \u0444\u0438\u043a\u0441\u0442\u0443\u0440\u0443 \u0432 pytest_plugins. \u0412 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0435 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0438\u0441\u0430\u0442\u044c API \u0444\u0438\u043a\u0441\u0442\u0443\u0440\u044b \u043f\u0440\u044f\u043c\u043e \u0440\u044f\u0434\u043e\u043c \u0441 \u0432\u0430\u0448\u0438\u043c\u0438 \u0442\u0435\u0441\u0442\u0430\u043c\u0438, \u043d\u043e, \u043a\u0430\u043a \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043c\u043e\u044f \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0430, \u044d\u0442\u043e \u043d\u0435 \u0434\u043e\u043b\u0433\u043e\u0438\u0433\u0440\u0430\u044e\u0449\u0430\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u044f. \u041d\u0430 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445 \u0431\u0438\u0437\u043d\u0435\u0441 \u043b\u043e\u0433\u0438\u043a\u0430 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0433\u043e\u0440\u0430\u0437\u0434\u043e \u0441\u043b\u043e\u0436\u043d\u0435\u0435, \u0433\u0434\u0435 \u0444\u0438\u043a\u0441\u0442\u0443\u0440\u044b \u043c\u043e\u0433\u0443\u0442 \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u0440\u0443\u0433 \u043e\u0442 \u0434\u0440\u0443\u0433\u0430. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u0440\u0430\u0437\u0443 \u0432\u044b\u043d\u043e\u0441\u0438\u043c \u044d\u0442\u0443 \u0444\u0438\u043a\u0441\u0442\u0443\u0440\u0443 \u0432 pytest_plugins<\/p>\n<p>utils\\assertions\\<a href=\"http:\/\/schema.py\" rel=\"noopener noreferrer nofollow\">schema.py<\/a><\/p>\n<pre><code class=\"python\">import allure from jsonschema import validate   @allure.step('Validating schema') def validate_schema(instance: dict, schema: dict) -> None:     validate(instance=instance, schema=schema) <\/code><\/pre>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u044f validate_schema \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u0441\u0445\u0435\u043c\u044b. \u041c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c validate \u0438\u0437 jsonschema, \u043d\u043e \u0442\u043e\u0433\u0434\u0430 \u043c\u044b \u043f\u043e\u0442\u0435\u0440\u044f\u0435\u043c allure.step. <\/p>\n<p>\u0414\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043e\u043a \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u044b\u0447\u043d\u044b\u0439 assert \u0432 python, \u043b\u0438\u0431\u043e \u0436\u0435 \u043e\u0434\u043d\u0443 \u0438\u0437 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a: <a href=\"https:\/\/pypi.org\/project\/assertpy\/\" rel=\"noopener noreferrer nofollow\">assertpy<\/a>, <a href=\"https:\/\/nikita-filonov.github.io\/assertions\/\" rel=\"noopener noreferrer nofollow\">pytest-assertions<\/a>. \u041d\u043e \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u0443\u044e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e expect, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u0432\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0432 \u0441\u0435\u0431\u044f allure.step \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u043e\u0439 \u0443\u0434\u043e\u0431\u043d\u044b\u0439 \u0434\u043b\u044f \u0432\u0430\u0441 \u0440\u0435\u043f\u043e\u0440\u0442\u0435\u0440. \u0421\u0442\u043e\u0438\u0442 \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u0432 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0435 <a href=\"https:\/\/nikita-filonov.github.io\/assertions\/\" rel=\"noopener noreferrer nofollow\">pytest-assertions<\/a> \u0442\u0430\u043a\u0436\u0435 \u0435\u0441\u0442\u044c \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0435 allure.step. <\/p>\n<p>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e expect \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0442\u0443\u0442 <a href=\"https:\/\/github.com\/Nikita-Filonov\/sample_api_testing\/tree\/main\/utils\/assertions\/base\" rel=\"noopener noreferrer nofollow\">https:\/\/github.com\/Nikita-Filonov\/sample_api_testing\/tree\/main\/utils\/assertions\/base<\/a>. \u041f\u043e \u044d\u0442\u043e\u0439 \u0441\u0441\u044b\u043b\u043a\u0435 \u043a\u043e\u0434 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043e\u0431\u044a\u0435\u043c\u043d\u044b\u0439, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u043d\u0435 \u0431\u0443\u0434\u0443 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0442\u044c \u0435\u0433\u043e \u0432 \u0441\u0442\u0430\u0442\u044c\u0435. <\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0441\u0442\u044c \u043e\u0431\u044a\u0435\u043a\u0442\u0430 question, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u0435\u0440\u043d\u0443\u043b\u043e \u043d\u0430 API.<\/p>\n<p>utils\\assertions\\api\\<a href=\"http:\/\/questions.py\" rel=\"noopener noreferrer nofollow\">questions.py<\/a><\/p>\n<pre><code class=\"python\">from models.questions import DefaultQuestion, QuestionDict, UpdateQuestion from utils.assertions.base.expect import expect   def assert_question(     expected_question: QuestionDict,     actual_question: DefaultQuestion | UpdateQuestion ):     if isinstance(actual_question, DefaultQuestion):         expect(expected_question['id']) \\             .set_description('Question \"id\"')\\             .to_be_equal(actual_question.id)      expect(expected_question['question']) \\         .set_description('Question \"question\"') \\         .to_be_equal(actual_question.question)      expect(expected_question['possibleAnswers']) \\         .set_description('Question \"possibleAnswers\"') \\         .to_be_equal(actual_question.possible_answers)      expect(expected_question['correctAnswer']) \\         .set_description('Question \"correctAnswer\"') \\         .to_be_equal(actual_question.correct_answer) <\/code><\/pre>\n<p>\u042d\u0442\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0441\u043b\u0443\u0436\u0438\u0442 \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u043c \u043d\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u043b\u043e\u0441\u044c \u0432 \u043a\u0430\u0436\u0434\u043e\u043c \u0442\u0435\u0441\u0442\u0435 \u043f\u0438\u0441\u0430\u0442\u044c \u0437\u0430\u043d\u043e\u0432\u043e \u0432\u0441\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0434\u043b\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u0430 question \u0438 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044e assert_question. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u043e\u0431\u044a\u0435\u043a\u0442 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0430 \u043a\u043b\u044e\u0447\u0435\u0439 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, 20), \u0442\u043e \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u043f\u0438\u0441\u0430\u0442\u044c \u0442\u0430\u043a\u0438\u0435 \u043e\u0431\u0435\u0440\u0442\u043a\u0438, \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u0445 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 QuestionDict &#8212; \u044d\u0442\u043e \u043d\u0435 \u043c\u043e\u0434\u0435\u043b\u044c, \u044d\u0442\u043e <a href=\"https:\/\/peps.python.org\/pep-0589\/\" rel=\"noopener noreferrer nofollow\">TypedDict<\/a> \u0438 \u043e\u043d \u0441\u043b\u0443\u0436\u0438\u0442 \u0434\u043b\u044f \u0430\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u0438 dict \u0432 python. \u041b\u0443\u0447\u0448\u0435 \u0441\u0442\u0430\u0440\u0430\u0442\u044c\u0441\u044f \u043f\u0438\u0441\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0435 \u0442\u0438\u043f\u044b \u0432\u043c\u0435\u0441\u0442\u043e \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0442\u043d\u043e\u0433\u043e dict, \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u044f, \u0447\u0442\u043e \u0430\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u0438 \u0432 python &#8212; \u044d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f \u0438 \u043d\u0435 \u0431\u043e\u043b\u0435\u0435. \u0418\u0431\u043e \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0442\u043d\u044b\u0435 \u0430\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u0438 \u0431\u0443\u0434\u0443\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u0442\u0440\u0443\u0434\u043d\u044f\u0442\u044c \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043a\u043e\u0434\u0430. \u0414\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0432\u044b \u043f\u0438\u0448\u0435\u0442\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u0442\u0438\u043f int, \u0442\u043e \u043b\u0443\u0447\u0448\u0435 \u043f\u0438\u0441\u0430\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0435 \u043f\u043e \u0442\u0438\u043f\u0443 MyScoreInt = int.<\/p>\n<h2>Testing<\/h2>\n<p>\u041c\u044b \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043b\u0438 \u0432\u0441\u044e \u0431\u0430\u0437\u0443 \u0434\u043b\u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0442\u0435\u0441\u0442\u043e\u0432. \u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0430\u043c\u0438 \u0442\u0435\u0441\u0442\u044b:<\/p>\n<p>tests\\test_futurama_questions.py<\/p>\n<pre><code class=\"python\">from http import HTTPStatus  import allure import pytest  from base.api.questions_api import (create_question_api, delete_question_api,                                     get_question_api, get_questions_api,                                     update_question_api) from models.questions import (DefaultQuestion, DefaultQuestionsList,                               QuestionDict, UpdateQuestion) from utils.assertions.api.questions import assert_question from utils.assertions.base.solutions import assert_status_code from utils.assertions.schema import validate_schema   @pytest.mark.questions @allure.feature('Questions') @allure.story('Questions API') class TestQuestions:     @allure.title('Get questions')     def test_get_questions(self):         response = get_questions_api()         json_response: list[QuestionDict] = response.json()          assert_status_code(response.status_code, HTTPStatus.OK)          validate_schema(json_response, DefaultQuestionsList.schema())      @allure.title('Create question')     def test_create_question(self):         payload = DefaultQuestion()          response = create_question_api(payload)         json_response: QuestionDict = response.json()          assert_status_code(response.status_code, HTTPStatus.CREATED)         assert_question(             expected_question=json_response,             actual_question=payload         )          validate_schema(json_response, DefaultQuestion.schema())      @allure.title('Get question')     def test_get_question(self, function_question: DefaultQuestion):         response = get_question_api(function_question.id)         json_response: QuestionDict = response.json()          assert_status_code(response.status_code, HTTPStatus.OK)         assert_question(             expected_question=json_response,             actual_question=function_question         )          validate_schema(json_response, DefaultQuestion.schema())      @allure.title('Update question')     def test_update_question(self, function_question: DefaultQuestion):         payload = UpdateQuestion()          response = update_question_api(function_question.id, payload)         json_response: QuestionDict = response.json()          assert_status_code(response.status_code, HTTPStatus.OK)         assert_question(             expected_question=json_response,             actual_question=payload         )          validate_schema(json_response, DefaultQuestion.schema())      @allure.title('Delete question')     def test_delete_question(self, function_question: DefaultQuestion):         delete_question_response = delete_question_api(function_question.id)         get_question_response = get_question_api(function_question.id)          assert_status_code(delete_question_response.status_code, HTTPStatus.OK)         assert_status_code(             get_question_response.status_code, HTTPStatus.NOT_FOUND         ) <\/code><\/pre>\n<p>\u0422\u0443\u0442 5-\u0442\u044c \u0442\u0435\u0441\u0442\u043e\u0432 \u043d\u0430 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0435 CRUD \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f questions API <a href=\"https:\/\/api.sampleapis.com\/futurama\/questions\" rel=\"noopener noreferrer nofollow\">https:\/\/api.sampleapis.com\/futurama\/questions<\/a>. <\/p>\n<p>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u044f\u0441\u044c \u043a \u043d\u0430\u0448\u0438\u043c \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f\u043c: <\/p>\n<ol>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0441\u0442\u0430\u0442\u0443\u0441 \u043a\u043e\u0434 \u043e\u0442\u0432\u0435\u0442\u0430, \u0442\u0435\u043b\u043e \u043e\u0442\u0432\u0435\u0442\u0430, JSON \u0441\u0445\u0435\u043c\u0443;<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0432\u043d\u0443\u0442\u0440\u0438 \u043c\u0435\u0442\u043e\u0434\u0430 create_question \u0443 \u043d\u0430\u0441 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 <strong>\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f<\/strong> \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 pydantic DefaultQuestion(**response.json()). \u042d\u0442\u043e <strong>\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438<\/strong> \u0438\u0437\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u043d\u0430\u0441 \u043e\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u043f\u0438\u0441\u0430\u0442\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0434\u043b\u044f \u043e\u0442\u0432\u0435\u0442\u0430 API;<\/p>\n<\/li>\n<li>\n<p>\u0410\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u044b \u0438 \u043b\u0435\u0433\u043a\u043e \u0447\u0438\u0442\u0430\u044e\u0442\u0441\u044f. \u0422\u0435\u043f\u0435\u0440\u044c \u0434\u0440\u0443\u0433\u043e\u0439 QA Automation \u0438\u043b\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a, \u043a\u043e\u0433\u0434\u0430 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u0442 \u043d\u0430 \u043d\u0430\u0448\u0438 \u0442\u0435\u0441\u0442\u044b, \u0441\u043c\u043e\u0436\u0435\u0442 \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u0430\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u044e \u0432 \u0432\u0438\u0434\u0435 \u043c\u043e\u0434\u0435\u043b\u0435\u0439. \u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0432 \u043d\u0430 \u043c\u043e\u0434\u0435\u043b\u0438, \u043e\u043d \u0441\u043c\u043e\u0436\u0435\u0442 \u043b\u0435\u0433\u043a\u043e \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0441 \u043a\u0430\u043a\u0438\u043c\u0438 \u0438\u043c\u0435\u043d\u043d\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c\u0438 \u043c\u044b \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c. \u0412 pydantic \u0438\u043c\u0435\u0435\u0442\u0441\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c description \u043a \u0444\u0443\u043d\u043a\u0446\u0438\u0438 Field, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0440\u0438 \u0436\u0435\u043b\u0430\u043d\u0438\u0438 \u0432\u044b \u0441\u043c\u043e\u0436\u0435\u0442\u0435 \u043e\u043f\u0438\u0441\u0430\u0442\u044c \u043a\u0430\u0436\u0434\u043e\u0435 \u043f\u043e\u043b\u0435 \u0432\u0430\u0448\u0435\u0439 \u043c\u043e\u0434\u0435\u043b\u0438;<\/p>\n<\/li>\n<li>\n<p>JSON \u0441\u0445\u0435\u043c\u0430 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438, \u0440\u0430\u043d\u0434\u043e\u043c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0442\u043e\u0436\u0435 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u043c\u043e\u0434\u0435\u043b\u0438. \u041f\u0440\u0438 \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043c\u043e\u0442\u0438\u0432\u0430\u0446\u0438\u0438 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u0437\u044f\u0442\u044c \u0432\u0430\u0448 Swagger \u0438 \u0432\u044b\u0442\u0430\u0449\u0438\u0442\u044c \u0438\u0437 \u043d\u0435\u0433\u043e JSON \u0441\u0445\u0435\u043c\u0443 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/github.com\/instrumenta\/openapi2jsonschema\" rel=\"noopener noreferrer nofollow\">https:\/\/github.com\/instrumenta\/openapi2jsonschema<\/a>. \u0414\u0430\u043b\u0435\u0435 y pydantic \u0435\u0441\u0442\u044c \u0443\u0431\u043e\u0439\u043d\u0430\u044f \u0444\u0438\u0447\u0430 <a href=\"https:\/\/docs.pydantic.dev\/datamodel_code_generator\/\" rel=\"noopener noreferrer nofollow\">https:\/\/docs.pydantic.dev\/datamodel_code_generator\/<\/a> \u0438 <strong>\u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 JSON \u0441\u0445\u0435\u043c\u044b<\/strong> pydantic <strong>\u0441\u0430\u043c<\/strong> <strong>\u0441\u0434\u0435\u043b\u0430\u0435\u0442<\/strong> \u043d\u0443\u0436\u043d\u044b\u0435 \u043c\u043e\u0434\u0435\u043b\u0438. \u042d\u0442\u043e\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u043c.<\/p>\n<\/li>\n<\/ol>\n<h2>Report<\/h2>\n<p>\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u0442\u0435\u0441\u0442\u044b \u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0430 \u043e\u0442\u0447\u0435\u0442:<\/p>\n<pre><code class=\"bash\">python -m pytest --alluredir=.\/allure-results<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u043e\u0442\u0447\u0435\u0442:<\/p>\n<pre><code class=\"bash\">allure serve<\/code><\/pre>\n<p>\u041b\u0438\u0431\u043e \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u043e\u0442\u0447\u0435\u0442 \u0438 \u0432 \u043f\u0430\u043f\u043a\u0435 allure-reports \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0444\u0430\u0439\u043b index.html:<\/p>\n<pre><code>allure generate<\/code><\/pre>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/95c\/fd3\/ca1\/95cfd3ca1fee036b5ed7a8494bb67d48.png\" width=\"1891\" height=\"932\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/95c\/fd3\/ca1\/95cfd3ca1fee036b5ed7a8494bb67d48.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043f\u0440\u0435\u043a\u0440\u0430\u0441\u043d\u044b\u0439 \u043e\u0442\u0447\u0435\u0442, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u0432\u0441\u044f \u043d\u0443\u0436\u043d\u0430\u044f \u043d\u0430\u043c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043c\u043e\u0434\u0438\u0444\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0448\u0430\u0433\u0438 \u043f\u043e\u0434 \u0441\u0432\u043e\u0438 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f \u0438\u043b\u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e HTTP \u0437\u0430\u043f\u0440\u043e\u0441\u0430. <\/p>\n<p>\u041f\u043e\u043b\u043d\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e \u043e\u0442\u0447\u0435\u0442\u0430 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0442\u0443\u0442 <a href=\"https:\/\/nikita-filonov.github.io\/sample_api_testing\/\" rel=\"noopener noreferrer nofollow\">https:\/\/nikita-filonov.github.io\/sample_api_testing\/<\/a><\/p>\n<h2>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h2>\n<p>\u0412\u0435\u0441\u044c \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d \u043d\u0430 \u043c\u043e\u0435\u043c github:\u00a0<a href=\"https:\/\/github.com\/Nikita-Filonov\/sample_api_testing\" rel=\"noopener noreferrer nofollow\">https:\/\/github.com\/Nikita-Filonov\/sample_api_testing<\/a><\/p>\n<p>\u0412\u0441\u0435\u0433\u0434\u0430 \u0441\u0442\u0430\u0440\u0430\u0439\u0442\u0435\u0441\u044c \u043f\u0438\u0441\u0430\u0442\u044c \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0441\u043b\u0435 \u0432\u0430\u0441 \u0438\u0445 \u0441\u043c\u043e\u0433 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u043b\u044e\u0431\u043e\u0439 \u0434\u0440\u0443\u0433\u043e\u0439 QA Automation \u0438\u043b\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a; \u0436\u0435\u043b\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0438 \u043f\u043e\u043d\u044f\u0442\u044c, \u043d\u043e \u0438 \u043b\u0435\u0433\u043a\u043e \u043f\u043e\u0447\u0438\u043d\u0438\u0442\u044c, \u0435\u0441\u043b\u0438 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f. \u041d\u0435 \u043f\u043e\u0432\u044b\u0448\u0430\u0439\u0442\u0435 \u0441\u0432\u043e\u044e \u0446\u0435\u043d\u043d\u043e\u0441\u0442\u044c \u0434\u043b\u044f \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438 \u0447\u0435\u0440\u0435\u0437 &#171;\u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043a\u043e\u0434&#187; \u043f\u043e\u043d\u044f\u0442\u043d\u044b\u0439 \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u0430\u043c.<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p> <!----> <!----><\/div>\n<p> <!----> <!----><br \/> \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\/709380\/\"> https:\/\/habr.com\/ru\/post\/709380\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<h2>\u0412\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0435<\/h2>\n<p>\u042d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u043a\u0430\u043a \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0435\u043d\u0438\u0435 \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/708932\/\" rel=\"noopener noreferrer nofollow\"><u>\u041a\u0430\u043a \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u043f\u0438\u0441\u0430\u0442\u044c UI \u0430\u0432\u0442\u043e \u0442\u0435\u0441\u0442\u044b \u043d\u0430 Python<\/u><\/a>. \u0415\u0441\u043b\u0438 \u043c\u044b \u0433\u043e\u0432\u043e\u0440\u0438\u043c \u043f\u0440\u043e UI \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b, \u0442\u043e \u0442\u0443\u0442 \u0445\u043e\u0442\u044f \u0431\u044b \u0435\u0441\u0442\u044c \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u044b Page Object, Pagefactory; \u0434\u043b\u044f API \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u043e\u0432 \u0442\u0430\u043a\u0438\u0445 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u043e\u0432 \u043d\u0435\u0442. \u0414\u0430, \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0442 \u043e\u0431\u0449\u0438\u0435 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u044b, \u043f\u043e \u0442\u0438\u043f\u0443 <a href=\"https:\/\/refactoring.guru\/design-patterns\/decorator\" rel=\"noopener noreferrer nofollow\"><u>Decorator<\/u><\/a>, <a href=\"https:\/\/refactoring.guru\/design-patterns\/singleton\" rel=\"noopener noreferrer nofollow\"><u>SIngletone<\/u><\/a>, <a href=\"https:\/\/refactoring.guru\/design-patterns\/facade\" rel=\"noopener noreferrer nofollow\"><u>Facade<\/u><\/a>, <a href=\"https:\/\/refactoring.guru\/design-patterns\/abstract-factory\" rel=\"noopener noreferrer nofollow\"><u>Abstract Factory<\/u><\/a>, \u043d\u043e \u044d\u0442\u043e \u043d\u0435 \u0442\u043e, \u0447\u0442\u043e \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c <strong>\u0431\u0438\u0437\u043d\u0435\u0441 \u043b\u043e\u0433\u0438\u043a\u0443<\/strong>. \u041a\u043e\u0433\u0434\u0430 \u043c\u044b \u043f\u0438\u0448\u0435\u043c API \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b, \u0442\u043e \u043d\u0430\u043c \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0431\u044b, \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u0438 \u043e\u0442\u0432\u0435\u0447\u0430\u043b\u0438 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f\u043c:<\/p>\n<ol>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u043f\u043e\u043b\u043d\u044b\u043c\u0438, \u0442\u043e \u0435\u0441\u0442\u044c \u043c\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0441\u0442\u0430\u0442\u0443\u0441 \u043a\u043e\u0434 \u043e\u0442\u0432\u0435\u0442\u0430, \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u0442\u0435\u043b\u0435 \u043e\u0442\u0432\u0435\u0442\u0430, \u043f\u0440\u043e\u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c JSON \u0441\u0445\u0435\u043c\u0443;<\/p>\n<\/li>\n<li>\n<p>\u0410\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c\u0438 \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u043c\u0438. \u0427\u0442\u043e\u0431\u044b \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b \u043c\u043e\u0433 \u0447\u0438\u0442\u0430\u0442\u044c \u0438 \u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e QA Automation, \u043d\u043e \u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a;<\/p>\n<\/li>\n<li>\n<p>\u0425\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0431\u044b, \u0447\u0442\u043e\u0431\u044b JSON \u0441\u0445\u0435\u043c\u0430 \u0438 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043b\u0438\u0441\u044c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438;<\/p>\n<\/li>\n<li>\n<p>\u041e\u0442\u0447\u0435\u0442 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0447\u0438\u0442\u0430\u0431\u0435\u043b\u044c\u043d\u044b\u043c, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0432 \u0432 \u0441\u0435\u0431\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0441\u0441\u044b\u043b\u043a\u0430\u0445, \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u0445, \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u0445, \u0441 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u043b\u044f\u0442\u044c \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u043b\u043e\u0433\u0438.<\/p>\n<\/li>\n<\/ol>\n<p>\u0414\u043b\u044f \u043c\u0435\u043d\u044f \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u044b\u0448\u0435 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0431\u0430\u0437\u043e\u0439, \u0432\u0430\u0448\u0438 \u0436\u0435 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0434\u0440\u0443\u0433\u0438\u0435 \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0430.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043e\u0447\u0435\u043d\u044c \u0432\u0430\u0436\u043d\u043e \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u0435\u0441\u043b\u0438 \u043f\u0440\u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0438 \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u043e\u0432 \u0432\u044b \u0432\u044b\u0431\u0435\u0440\u0435\u0442\u0435 \u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0434\u0445\u043e\u0434, \u0442\u043e \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u043f\u043e\u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043d\u0435 \u0441\u0440\u0430\u0437\u0443, \u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0447\u0435\u0440\u0435\u0437 100-150 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u043e\u0432. \u0422\u043e\u0433\u0434\u0430 \u0444\u0438\u043a\u0441\u044b \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u043e\u0432 \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u044f\u0442\u0441\u044f \u0432 \u0430\u0434, \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u043e\u0432\u044b\u0445 \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u043e\u0432 \u0431\u0443\u0434\u0435\u0442 \u0432\u0441\u0435 \u0441\u043b\u043e\u0436\u043d\u0435\u0435 \u0438 \u0441\u043b\u043e\u0436\u043d\u0435\u0435, \u0430 \u0447\u0438\u0442\u0430\u0442\u044c \u0442\u0430\u043a\u0438\u0435 \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b \u043d\u0438\u043a\u0442\u043e \u043a\u0440\u043e\u043c\u0435 \u0432\u0430\u0441 \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u0442, \u0447\u0442\u043e \u043f\u043b\u043e\u0445\u043e. \u0412 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435 \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u044e\u0442\u0441\u044f \u0441\u043b\u0443\u0447\u0430\u0438, \u043a\u043e\u0433\u0434\u0430 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044f \u043f\u0440\u043e\u0441\u0438\u0442 \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u0430\u0442\u044c \u0438\u0445 \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b \u0438 \u043e\u0447\u0435\u043d\u044c \u0447\u0430\u0441\u0442\u043e \u043c\u043e\u0442\u0438\u0432\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f: \u201c\u041d\u0430\u0448 QA Automation \u0443\u0448\u0435\u043b, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0442\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u043c \u0434\u0430\u0436\u0435 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b \u0438 \u043d\u0435\u043f\u043e\u043d\u044f\u0442\u043d\u043e, \u0447\u0442\u043e \u0432 \u043d\u0438\u0445 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442\u201d. \u042d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u0447\u0435\u043b\u043e\u0432\u0435\u043a, \u043d\u0430\u043f\u0438\u0441\u0430\u0432\u0448\u0438\u0439 \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b, \u043f\u0438\u0441\u0430\u043b \u0438\u0445 \u043a\u043e\u0441\u0442\u044b\u043b\u044c\u043d\u043e, \u043a\u0430\u043a \u0431\u044b \u043f\u043e\u0432\u044b\u0448\u0430\u044f \u0441\u0432\u043e\u044e <strong>\u0446\u0435\u043d\u043d\u043e\u0441\u0442\u044c<\/strong> (\u0432 \u043f\u043b\u043e\u0445\u043e\u043c \u0441\u043c\u044b\u0441\u043b\u0435, \u0447\u0442\u043e \u043d\u0438\u043a\u0442\u043e, \u043a\u0440\u043e\u043c\u0435 \u043d\u0435\u0433\u043e, \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043d\u044f\u0442\u044c \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u043f\u043e\u0441\u043b\u0435 \u0435\u0433\u043e \u0443\u0445\u043e\u0434\u0430 \u0438\u043b\u0438 \u0431\u0430\u043d\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0443\u0445\u043e\u0434\u0430 \u043d\u0430 \u0431\u043e\u043b\u044c\u043d\u0438\u0447\u043d\u044b\u0439), \u043a\u0430\u043a \u0441\u043e\u0442\u0440\u0443\u0434\u043d\u0438\u043a\u0430, \u0447\u0442\u043e \u043e\u0447\u0435\u043d\u044c \u043f\u043b\u043e\u0445\u043e \u0434\u043b\u044f \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438. \u0412 \u0438\u0442\u043e\u0433\u0435 \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0442\u0440\u0430\u0447\u0435\u043d\u043e, \u0434\u0435\u043d\u044c\u0433\u0438 \u043f\u043e\u0442\u0440\u0430\u0447\u0435\u043d\u043e.<\/p>\n<p>\u0415\u0449\u0435 \u043e\u0434\u0438\u043d \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0439 \u043a\u0435\u0439\u0441 &#8212; \u044d\u0442\u043e \u043a\u043e\u0433\u0434\u0430 \u043d\u043e\u0432\u044b\u0439 QA Automation \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043d\u0430 \u043f\u0440\u043e\u0435\u043a\u0442 \u0438 \u0441\u0440\u0430\u0437\u0443 \u0436\u0435 \u0445\u043e\u0447\u0435\u0442 \u0432\u0441\u0435 \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u0430\u0442\u044c. \u041e\u043a\u0430\u0439, \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442, \u0441\u0443\u0442\u044c \u043d\u0435 \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f, \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u044f \u0442\u0430\u043a\u0436\u0435 \u0441\u0442\u0440\u0430\u0434\u0430\u0435\u0442. \u041f\u043e &#171;\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u043c\u0443&#187; \u043c\u043d\u0435\u043d\u0438\u044e \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u0441\u0435 \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u0430\u043b, \u0432\u0438\u043d\u043e\u0432\u0430\u0442 \u043f\u0440\u043e\u0434\u0443\u043a\u0442, \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438, \u043d\u043e \u043d\u0435 \u043e\u043d \u0441\u0430\u043c. \u041a\u043e\u043c\u043f\u0430\u043d\u0438\u044f \u0432 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0432\u044b\u0441\u0442\u0443\u043f\u0430\u0435\u0442 \u0442\u0440\u0435\u043d\u0430\u0436\u0435\u0440\u043e\u043c\/\u043f\u043b\u0435\u0439\u0433\u0440\u0430\u0443\u043d\u0434\u043e\u043c \u0434\u043b\u044f \u043d\u0435\u043e\u043f\u044b\u0442\u043d\u043e\u0433\u043e QA Automation. \u0412 \u0438\u0442\u043e\u0433\u0435 \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0442\u0440\u0430\u0447\u0435\u043d\u043e, \u0434\u0435\u043d\u044c\u0433\u0438 \u043f\u043e\u0442\u0440\u0430\u0447\u0435\u043d\u043e.<\/p>\n<h2>Requirements<\/h2>\n<p>\u0414\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f API \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u043e\u0432 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c:<\/p>\n<ul>\n<li>\n<p>pytest &#8212; pip install pytest;<\/p>\n<\/li>\n<li>\n<p>httpx &#8212; pip install httpx, &#8212; \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 HTTP \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u043e\u043c;<\/p>\n<\/li>\n<li>\n<p>allure &#8212; pip install allure-pytest, &#8212; \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043b\u044e\u0431\u043e\u0439 \u0434\u0440\u0443\u0433\u043e\u0439 \u0440\u0435\u043f\u043e\u0440\u0442\u0435\u0440;<\/p>\n<\/li>\n<li>\n<p>jsonschema &#8212; pip install jsonschema, &#8212; \u0434\u043b\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 JSON \u0441\u0445\u0435\u043c\u044b;<\/p>\n<\/li>\n<li>\n<p>pydantic, python-dotenv &#8212; pip install pydantic python-dotenv, &#8212; \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445, \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c\u0438, \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 JSON \u0441\u0445\u0435\u043c\u044b;<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u0447\u0435\u043c\u0443 \u043d\u0435 <a href=\"https:\/\/requests.readthedocs.io\/en\/latest\/\" rel=\"noopener noreferrer nofollow\"><u>requests<\/u><\/a>? \u041c\u043d\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f httpx, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043e\u043d \u0443\u043c\u0435\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e \u0438 \u0443 \u043d\u0435\u0433\u043e \u0435\u0441\u0442\u044c AsyncClient. \u0422\u0430\u043a\u0436\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f <a href=\"https:\/\/www.python-httpx.org\/\" rel=\"noopener noreferrer nofollow\"><u>httpx<\/u><\/a> \u0432 \u0441\u0442\u0438\u043b\u0435 Material Design \u043c\u043d\u0435 \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f, \u0447\u0435\u043c \u0443 requests. \u0412 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u043c requests \u0437\u0430\u043c\u0435\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430, \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0435\u0435 \u0438  \u0440\u0430\u0437\u043d\u0438\u0446\u044b \u043d\u0438\u043a\u0430\u043a\u043e\u0439 \u043d\u0435\u0442.<\/p>\n<p>\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <a href=\"https:\/\/docs.pydantic.dev\/\" rel=\"noopener noreferrer nofollow\"><u>pydantic<\/u><\/a> \u0441\u043b\u0443\u0436\u0438\u0442 \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u201c\u0441\u0442\u0440\u043e\u0433\u043e\u0439 \u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u0438\u201d \u0432 python. \u041e\u043d\u0430 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u0430 \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 JSON \u0441\u0445\u0435\u043c\u044b, \u0434\u043b\u044f \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u0434\u0430\u043d\u043d\u044b\u0445, \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445. \u0423 \u044d\u0442\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0435\u0441\u0442\u044c \u043c\u043d\u043e\u0433\u043e \u043f\u043b\u044e\u0441\u043e\u0432 \u043f\u043e \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044e \u0441 \u043e\u0431\u044b\u0447\u043d\u044b\u043c\u0438 dataclass-\u0441\u0430\u043c\u0438 \u0432 python. \u0415\u0441\u043b\u0438 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u0440 \u0438\u0437 \u0436\u0438\u0437\u043d\u0438, \u0442\u043e pydantic &#8212; \u044d\u0442\u043e \u043a\u0430\u043a \u0435\u0445\u0430\u0442\u044c \u043d\u0430 \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0438\u043b\u0435, \u0430 dataclass&#8217;\u044b &#8212; \u044d\u0442\u043e \u0438\u0434\u0442\u0438 \u043f\u0435\u0448\u043a\u043e\u043c.\u00a0<\/p>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u044b pydantic \u043c\u043e\u0436\u043d\u043e \u0432\u0437\u044f\u0442\u044c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <a href=\"https:\/\/github.com\/Nikita-Filonov\/models_manager\" rel=\"noopener noreferrer nofollow\"><u>models-manager<\/u><\/a>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0434\u0435\u043b\u0430\u0435\u0442 \u0432\u0441\u0435 \u0442\u043e\u0436\u0435 \u0441\u0430\u043c\u043e\u0435, \u0447\u0442\u043e \u0438 pydantic, \u0442.\u0435. \u0443\u043c\u0435\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 \u0431\u0430\u0437\u043e\u0439 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438, \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0440\u0430\u043d\u0434\u043e\u043c\u043d\u044b\u0435 \u043d\u0435\u0433\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u043c\u043e\u0434\u0435\u043b\u0438. \u042d\u0442\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0431\u043e\u043b\u044c\u0448\u0435 \u043f\u043e\u0434\u043e\u0439\u0434\u0435\u0442 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u0432\u0445\u043e\u0434\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432\u0430\u0448\u0435\u0433\u043e API. \u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044e \u043f\u043e models-manager \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 <a href=\"https:\/\/nikita-filonov.github.io\/models_manager\/\" rel=\"noopener noreferrer nofollow\"><u>\u0442\u0443\u0442<\/u><\/a>. \u041c\u044b \u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c models-manager, \u0442\u0430\u043a \u043a\u0430\u043a \u043d\u0430\u043c \u043d\u0435 \u043d\u0443\u0436\u043d\u0430 \u0431\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u043c\u044b \u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e.<\/p>\n<p>\u041d\u043e \u0443 pydantic \u0442\u043e\u0436\u0435 \u0435\u0441\u0442\u044c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <a href=\"https:\/\/sqlmodel.tiangolo.com\/\" rel=\"noopener noreferrer nofollow\"><u>SQLModel<\/u><\/a> \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0431\u0430\u0437\u043e\u0439 \u0434\u0430\u043d\u043d\u044b\u0445. \u0415\u0441\u043b\u0438 \u0432\u0430\u043c \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u043e\u0432 \u043d\u0443\u0436\u043d\u0430 \u0431\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445, \u0442\u043e \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c: SQLAlchemy + pydantic, SQLModel, models-manager. \u0412 \u043d\u0430\u0448\u0435\u043c \u0436\u0435 \u0441\u043b\u0443\u0447\u0430\u0435 \u0440\u0430\u0431\u043e\u0442\u0430 \u0441 \u0431\u0430\u0437\u043e\u0439 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0435 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f.<\/p>\n<p>\u0422\u0435\u0441\u0442\u044b \u0431\u0443\u0434\u0435\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 API <a href=\"https:\/\/sampleapis.com\/api-list\/futurama\" rel=\"noopener noreferrer nofollow\">https:\/\/sampleapis.com\/api-list\/futurama<\/a>. \u0414\u0430\u043d\u043d\u044b\u0439 API \u0432\u0441\u0435\u0433\u043e \u043b\u0438\u0448\u044c \u043f\u0440\u0438\u043c\u0435\u0440. \u041d\u0430 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445 API \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0433\u043e\u0440\u0430\u0437\u0434\u043e \u0441\u043b\u043e\u0436\u043d\u0435\u0435, \u043d\u043e <strong>\u0441\u0443\u0442\u044c<\/strong> \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u043e\u0432 \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u0442\u0430 \u0436\u0435. <\/p>\n<h2>Settings<\/h2>\n<p>\u041e\u043f\u0438\u0448\u0435\u043c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u043b\u0430\u0441\u0441 BaseSettings \u0438\u0437 pydantic, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043e\u043d \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u0443\u0434\u043e\u0431\u043d\u044b\u0439, \u0443\u043c\u0435\u0435\u0442 \u0447\u0438\u0442\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0437 .env \u0444\u0430\u0439\u043b\u0430, \u0443\u043c\u0435\u0435\u0442 \u0447\u0438\u0442\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0437 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u0443\u043c\u0435\u0435\u0442 \u0447\u0438\u0442\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0437 .txt \u0444\u0430\u0439\u043b\u0430, \u0443\u043c\u0435\u0435\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0430\u043c\u0438 \u043d\u0430 \u0440\u0435\u0434\u0438\u0441 \u0438\u043b\u0438 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u043c\u043d\u043e\u0433\u043e \u0447\u0435\u0433\u043e \u0435\u0449\u0435, \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0442\u0443\u0442 <a href=\"https:\/\/docs.pydantic.dev\/usage\/settings\/\" rel=\"noopener noreferrer nofollow\"><u>https:\/\/docs.pydantic.dev\/usage\/settings\/<\/u><\/a>. \u042d\u0442\u043e \u043e\u0447\u0435\u043d\u044c \u0443\u0434\u043e\u0431\u043d\u043e \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043d\u0430 <strong>CI\/CD<\/strong>, \u0438\u043b\u0438 \u043a\u043e\u0433\u0434\u0430 \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u043c\u043d\u043e\u0433\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0440\u0430\u0437\u0431\u0440\u043e\u0441\u0430\u043d\u044b \u043f\u043e \u0432\u0441\u0435\u043c\u0443 \u043f\u0440\u043e\u0435\u043a\u0442\u0443 + \u0441 BaseSettings \u0432\u0441\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043c\u043e\u0436\u043d\u043e \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0432 \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442.<\/p>\n<pre><code class=\"python\">from pydantic import BaseModel, BaseSettings, Field   class TestUser(BaseModel):     email: str     password: str   class Settings(BaseSettings):     base_url: str = Field(..., env='BASE_URL')     user_email: str = Field(..., env='TEST_USER_EMAIL')     user_password: str = Field(..., env='TEST_USER_PASSWORD')      class Config:         env_file = '.env'         env_file_encoding = 'utf-8'      @property     def api_url(self) -> str:         return f'{self.base_url}\/futurama'      @property     def user(self) -> TestUser:         return TestUser(             email=self.user_email,             password=self.user_password         )   base_settings = Settings()<\/code><\/pre>\n<p>\u041c\u044b \u0431\u0443\u0434\u0435\u043c \u0447\u0438\u0442\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0437 .env \u0444\u0430\u0439\u043b\u0430.<\/p>\n<h2>Models<\/h2>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u043f\u0438\u0448\u0435\u043c \u043c\u043e\u0434\u0435\u043b\u0438, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f pydantic, \u043f\u0435\u0440\u0435\u0434 \u044d\u0442\u0438\u043c \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0431\u0430\u0437\u043e\u0432\u0443\u044e \u043c\u043e\u0434\u0435\u043b\u044c \u0438\u0437 pydantic. \u041d\u0430\u043c \u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u043a\u0440\u044b\u0442\u044c \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 <strong>\u043b\u0438\u043c\u0438\u0442\u0430\u0446\u0438\u0438 \u0438 \u0431\u0430\u0433\u0438<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0435\u0441\u0442\u044c \u0432 pydantic. <\/p>\n<p>utils\\models\\base_model.py<\/p>\n<pre><code class=\"python\">from pydantic import BaseModel as PydanticBaseModel   class BaseModel(PydanticBaseModel):     class Config:         @staticmethod         def schema_extra(schema: dict, model: PydanticBaseModel):             \"\"\"             https:\/\/github.com\/pydantic\/pydantic\/issues\/1270#issuecomment-729555558             \"\"\"             for prop, value in schema.get('properties', {}).items():                 field = [                     model_field for model_field in model.__fields__.values()                     if model_field.alias == prop                 ][0]                  if field.allow_none:                     if 'type' in value:                         value['anyOf'] = [{'type': value.pop('type')}]                      elif '$ref' in value:                         if issubclass(field.type_, BaseModel):                             value['title'] = field.type_.__config__.title or field.type_.__name__                         value['anyOf'] = [{'$ref': value.pop('$ref')}]                     value['anyOf'].append({'type': 'null'})      def __hash__(self):         \"\"\"         https:\/\/github.com\/pydantic\/pydantic\/issues\/1303#issuecomment-599712964         \"\"\"         return hash((type(self),) + tuple(self.__dict__.values())) <\/code><\/pre>\n<p>\u042f \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u0438\u043b \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 github issue, \u0433\u0434\u0435 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435, \u043a\u0430\u043a\u0430\u044f \u0438\u043c\u0435\u043d\u043d\u043e \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f. \u0415\u0441\u043b\u0438 \u0431\u0443\u0434\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c pydantic, \u0442\u043e \u0432\u0430\u043c \u044d\u0442\u043e \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u0441\u044f. \u041d\u0443 \u0438\u043b\u0438 \u0436\u0435 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <a href=\"https:\/\/github.com\/Nikita-Filonov\/models_manager\" rel=\"noopener noreferrer nofollow\">models-manager<\/a>, \u0438\u0431\u043e \u0442\u0430\u043c \u043d\u0435\u0442 \u044d\u0442\u0438\u0445 \u043f\u0440\u043e\u0431\u043b\u0435\u043c.<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u043f\u0438\u0448\u0435\u043c \u043c\u043e\u0434\u0435\u043b\u044c \u0434\u043b\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438:<\/p>\n<p>models\\<a href=\"http:\/\/authentication.py\" rel=\"noopener noreferrer nofollow\">authentication.py<\/a><\/p>\n<pre><code class=\"python\">from pydantic import Field  from settings import base_settings from utils.models.base_model import BaseModel   class AuthUser(BaseModel):     email: str = Field(default=base_settings.user.email)     password: str = Field(default=base_settings.user.password)   class Authentication(BaseModel):     auth_token: str | None     user: AuthUser | None = AuthUser()<\/code><\/pre>\n<p>\u041d\u0430\u043f\u0438\u0448\u0435\u043c \u043c\u043e\u0434\u0435\u043b\u044c \u0434\u043b\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u0430 question \u0438\u0437 API <a href=\"https:\/\/sampleapis.com\/api-list\/futurama\" rel=\"noopener noreferrer nofollow\">https:\/\/sampleapis.com\/api-list\/futurama<\/a>. \u0421\u0430\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a:<\/p>\n<pre><code class=\"json\">{   \"id\": 1,   \"question\": \"What is Fry's first name?\",   \"possibleAnswers\": [     \"Fred\",     \"Philip\",     \"Will\",     \"John\"   ],   \"correctAnswer\": \"Philip\" }<\/code><\/pre>\n<p>models\\<a href=\"http:\/\/questions.py\" rel=\"noopener noreferrer nofollow\">questions.py<\/a><\/p>\n<pre><code class=\"python\">from typing import TypedDict  from pydantic import BaseModel, Field  from utils.fakers import random_list_of_strings, random_number, random_string   class UpdateQuestion(BaseModel):     question: str | None = Field(default_factory=random_string)     possible_answers: list[str] | None = Field(         alias='possibleAnswers',         default_factory=random_list_of_strings     )     correct_answer: str | None = Field(         alias='correctAnswer',         default_factory=random_string     )   class DefaultQuestion(BaseModel):     id: int = Field(default_factory=random_number)     question: str = Field(default_factory=random_string)     possible_answers: list[str] = Field(         alias='possibleAnswers',         default_factory=random_list_of_strings     )     correct_answer: str = Field(         alias='correctAnswer',         default_factory=random_string     )   class DefaultQuestionsList(BaseModel):     __root__: list[DefaultQuestion]   class QuestionDict(TypedDict):     id: int     question: str     possibleAnswers: list[str]     correct_answer: str <\/code><\/pre>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442 <strong>alias<\/strong> \u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 Field. \u041e\u043d \u0441\u043b\u0443\u0436\u0438\u0442 \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043c\u044b \u043c\u043e\u0433\u043b\u0438 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441\u043e <strong>snake_case<\/strong> \u0432 python \u0438 \u0441 \u043b\u044e\u0431\u044b\u043c \u0434\u0440\u0443\u0433\u0438\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u043e\u043c \u0438\u0437\u0432\u043d\u0435. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432 python \u043d\u0430\u043c \u0431\u044b \u043d\u0435 \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c &#8212; possibleAnswers, \u0442.\u043a. \u044d\u0442\u043e \u043d\u0430\u0440\u0443\u0448\u0430\u0435\u0442 PEP8, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c <strong>alias<\/strong>. Pydantic \u0441\u0430\u043c \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u0442\u0441\u044f, \u043a\u0430\u043a \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c JSON \u043e\u0431\u044a\u0435\u043a\u0442 \u0438 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c \u0435\u0433\u043e \u043f\u043e \u043d\u0443\u0436\u043d\u044b\u043c \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430\u043c \u0432 \u043c\u043e\u0434\u0435\u043b\u0438. \u0422\u0430\u043a \u0436\u0435 \u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 Field \u0435\u0441\u0442\u044c \u043e\u0447\u0435\u043d\u044c \u043c\u043d\u043e\u0433\u043e \u043a\u0440\u0443\u0442\u044b\u0445 \u0444\u0438\u0447 \u043f\u043e \u0442\u0438\u043f\u0443: max_length, min_length, gt, ge, lt, le \u0438 \u043c\u043e\u0436\u043d\u043e \u043f\u0438\u0441\u0430\u0442\u044c \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043a\u0438. \u0415\u0441\u0442\u044c <a href=\"https:\/\/docs.pydantic.dev\/usage\/model_config\/\" rel=\"noopener noreferrer nofollow\">\u043a\u0443\u0447\u0430 \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0445 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a<\/a> \u0434\u043b\u044f \u0432\u0430\u0448\u0438\u0445 \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u0438 \u0435\u0441\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c <a href=\"https:\/\/docs.pydantic.dev\/usage\/types\/\" rel=\"noopener noreferrer nofollow\">\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0435 \u0442\u0438\u043f\u044b<\/a> \u0438\u043b\u0438 \u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0432\u043e\u0438. \u041a\u043e\u0440\u043e\u0447\u0435, \u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435\u0441\u044c.<\/p>\n<p>\u0414\u0430\u043d\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438: random_list_of_strings, random_number, random_string \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f, \u0447\u0442\u043e\u0431\u044b \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u0440\u0430\u043d\u0434\u043e\u043c\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435. \u041c\u044b \u043d\u0435<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\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-343652","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/343652","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=343652"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/343652\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=343652"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=343652"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=343652"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}