Удобно за начинаещи ръководство за изграждане на вашата първа RESTful уеб услуга с помощта на Python и Flask за извършване на CRUD операции в база данни MongoDB.

Въведение

Уеб разработката вече не е просто създаване на уебсайт с една страница с помощта на HTML и малко CSS, тя напредна значително през годините и все по-често ще се налага да свързвате своя преден сайт към някаква форма на бекенд услуга, независимо дали това е за достъп до база данни или за обработка на данни.

Един от най-лесните начини да накарате вашия интерфейс да комуникира с бекенд услуга е като създадете RESTful уеб услуга. Ако не сте запознати с REST и се чудите за какво изобщо говоря, можете да прочетете предишната ми статия, която разглежда основите на REST и API като цяло.

В това ръководство ще разгледам създаването на REST API с помощта на Python и Flask. Flask е „микро“ рамка за създаване на уеб приложения, използващи Python [1], което означава с прости думи е, че не включва всички допълнителни модули, които може да са ви необходими за вашата система, като клиенти на бази данни, но това, което има работи добре, изключително добре; за много бързо стартиране и работа на вашето уеб приложение. Може да се използва за създаване на пълноценни уеб приложения, които обслужват динамични HTML страници или да се използва за създаване на RESTful уеб услуга, за това ще го използваме в това ръководство.

Ако Python не ви харесва и Java е повече вашата скорост, преди това публикувах ръководство за начинаещи за създаване на RESTful уеб услуга с помощта на Java и Spring Boot, вижте го.

Приготвяме се да започнем

Системата, която ще направя в това ръководство, е RESTful услуга, която може да се използва като бекенд за вашия собствен уебсайт или приложение със списък със задачи, което може да извършва операции за създаване, четене, актуализиране и изтриване (CRUD) във вашата база данни. За базата данни ще свържа услугата към локален екземпляр на MongoDB, за да се свържа с хостван екземпляр на Mongo, е просто да добавите URL адреса на вашата връзка. Въпреки че озаглавих това като ръководство за начинаещи, все още предполагам, че имате поне малко познания за Python и концепциите за програмиране като цяло.

Качих завършения проект в следното хранилище на GitHub, ако предпочитате да следвате заедно със завършен проект. Аз лично установявам, че се уча по-бързо, като въвеждам кода сам, но не се колебайте да клонирате репото и да използвате включения проект, няма преценка, идваща от мен.

Ще използвам PyCharm за създаването на този проект, намирам го за една от най-добрите IDE за пълни проекти на Python, но можете, разбира се, да използвате всяка IDE, която ви харесва.

Към момента на писане на тази статия проектът използва най-новите версии на всички използвани компоненти Python 3.9, Flask 2.0.3 и Pymongo 4.0.2.

Нека напишем малко код

Първото нещо, което ще създадем, е класът хранилище, който ще отговаря за извършването на CRUD операциите във вашата база данни Mongo. Класът на хранилището ще има специални методи за всяка от CRUD операциите и променливи на класа за клиентската връзка на Mongo.

След като създадем нашето хранилище, ще можем да създадем негово копие, което да използваме в методите на нашия REST API, затова първо ще го създадем.

По-горе включих пълния клас, за да можете да го видите заедно и за по-лесно копиране и поставяне. Сега ще го разделя на по-малки части и ще обясня всяка от тях възможно най-добре.

В този първи раздел на хранилището, започвайки от първи ред, ние импортираме модулите, които ще използваме в класа. Тези модули са MongoClient за свързване с нашата база данни и някои BSON и JSON инструменти за форматиране на нашите ToDo документи, преди да бъдат запазени в базата данни и след като бъдат извлечени отново от нея. Това форматиране ще има повече смисъл, докато четете.

На шести ред декларираме, че създаваме клас, наречен ToDoRepository. На редове от осем до единадесет ние създаваме нашия метод __init__, този метод се използва за инициализиране на екземпляр от нашия клас. В метода init създаваме три обекта, първият е обект, наречен „клиент“, този клиент се създава с помощта на името на хоста и порта на нашия екземпляр на MongoDB. В моя случай, тъй като използвам локално работещ екземпляр на MongoDB, нашето име на хост е „localhost“, а номерът на порта е 27017.

Ако имате инсталиран Docker на вашата машина, можете бързо да стартирате и стартирате локален екземпляр на MongoDB със следната команда. Той ще създаде и стартира mongo Docker контейнер и ще изложи порта на вашия локален хост, за да можете да се свържете с него.

Ако искате да се свържете с хоствано копие на Mongo, можете да замените localhost и номера на порта с вашия MongoDB URI, напр.. MongoClient(‘mongodb://externalmongoinstance:27017/’)

С обекта client сега ще създадем друг обект, наречен „db“, този обект е препратка към базата данни, която искаме да използваме „todo_database >'. Ако тази база данни все още не съществува, тя ще бъде създадена.

Сега, когато имаме препратка към нашата база данни, можем да създадем нова колекция в нея. Това се прави онлайн номер девет.

Въпреки че току-що казах, че базата данни и колекцията ще бъдат създадени, ако не съществуват, излъгах леко. Базите данни и колекциите се създават лениво в MongoDB, това означава, че те няма да бъдат създадени, докато не вмъкнете първия документ в тях.

В горния блок код имаме първите два метода от нашия клас хранилище. Тези два метода отговарят за четенето на документи от колекцията. Първият метод извлича един документ, използвайки неговия идентификатор, а вторият извлича всички ToDo документи, които се съхраняват в нашата колекция.

Първият метод, започващ на първи ред, декларира метода с име ‘get_id(self, todo_id)’. Този метод приема два параметъра, self и todo_id; self е препратка към екземпляра на класа, към който се извиква методът, това ни позволява достъп до променливи на екземпляр като тези, декларирани в нашия init метод. Вторият параметър е todo_id, това е идентификаторът на ToDo документа, който искаме да извлечем от нашата колекция.

На втори ред извикваме метода ‘find_one’ на обекта за колекция todos. Този метод приема заявка като параметър, за нашата заявка ние предаваме JSON обект с един ключ, ‘_id’. Ще забележите, че за стойността на идентификатора ние предаваме нов екземпляр на ObjectIdкойто се създава с идентификатора на документа, който искаме да намерим.

След като получим резултата от заявката, трябва да го конвертираме в JSON, това е така, защото Mongo използва BSON, който е подобен визуално на JSON, но е различен [2]. Преобразуването се случва на ред три, с този новоконвертиран JSON обект трябва да го форматираме малко повече, така че да е по-лесен за работа.

В горния Gist можете да видите два JSON обекта, първият е как се форматират нашите документи, когато ги получим от метода find. Както можете да видите в горния обект, _id има друг обект като стойност, която съдържа идентификатора на документа в него. За да улесним обработката, когато получим това в нашия интерфейс, искаме да го форматираме, за да изглежда като долния обект.

Веднъж форматиран на четвърти ред, ние връщаме получения JSON обект. Методът 'get_all' е много подобен на предишния, с изключение на това, че използва метода 'find' в колекцията todos, който връща списък с документи, всеки от които е форматиран в for цикъл.

В този блок код имаме нашите методи за запазване, актуализиране и изтриване на документи в колекцията. Методът save използва метода ‘insert_one’, за да запази новото To-Do. След като бъде запазен, отговорът се форматира и ние връщаме новосъздадения идентификатор на документи.

Методът update се използва за актуализиране на съществуващ документ в колекцията To-Do, той прави това с помощта на метода „update_one“. Този метод приема два аргумента, единият е филтър за намиране на документа(ите), които да бъдат актуализирани, а вторият е новите стойности, които да се зададат в документа(ите), намерени с филтъра. Методът за актуализиране може да се използва за актуализиране на множество обекти в зависимост от подавания филтър. Веднъж актуализирани, ние връщаме броя документи, които са били актуализирани.

Изтриването е доста ясно, използва метода „delete_one“, който предаваме на филтър, съдържащ идентификатора на задачата, която искаме да изтрием. Отново, тъй като този метод може да се използва за изтриване, ние връщаме броя на изтритите документи.

Тъй като използваме идентификационните номера като филтри в методите update_oneи delete_oneние ще актуализираме само един документ, тъй като идентификационният номер на документа трябва да е уникален. Филтърът може лесно да се актуализира, за да търси всякакви други двойки ключове и стойности в документите, ако искате да актуализирате няколко документа наведнъж.

Крайните точки на REST

Следващият и последен скрипт, който ще създадем, е основният клас на услугата, както и това, което използваме за изпълнение на програмата, но също така съдържа нашите REST крайни точки.

Подобно на класа ToDoRepository включих пълния скрипт по-горе, за да можете сами да го прочетете, а също и за по-лесно копиране и поставяне. Сега ще разделя скрипта на по-малки части и ще ги обясня.

В горния код ние импортираме модулите, които ще са ни необходими за нашия скрипт, ние също импортираме нашето ToDoRepository. Следващото нещо, което правим, е да създадем два обекта, appи repo. Обектът на приложението е екземпляр на класа Flask, това ще ни позволи да украсим нашите методи и да им позволим да бъдат разпознати като наши REST крайни точки. Обектът repo е екземпляр на нашия клас ToDoRepository, така че да можем да извикваме нашите CRUD методи за взаимодействие с нашата база данни.

Двата метода в този код са за извличане на нашите задачи от базата данни, един за получаване на една задача с помощта на нейния идентификатор и друг метод за извличане на всички задачи в базата данни. Правим това, като използваме методите get_allи get_id, които току-що създадохме в нашия клас ToDoRepository. Важно нещо, което трябва да отбележим тук, е как получаваме идентификатора в метода get_todo, получаваме го чрез URL адреса. Това се прави чрез добавяне на следното към пътя в декоратора над метода “/‹string:todo_id›”. Това казва на Flask, че очакваме низова стойност като параметър на пътя и тя ще се нарича todo_id,след това тази стойност може да се използва в рамките на метода за извикване на метода get_id на нашето ToDoRepository.

Веднъж извлечени от нашата база данни, ние конвертираме документите със задачи в JSON обект и ги връщаме с HTTP код за състояние 200, което означава, че заявката е била успешна. Flask е достатъчно умен, за да се справи с повечето неуспешни отговори вместо нас.

Горните два метода за получаване могат лесно да бъдат обединени в един единствен метод, тъй като по-голямата част от логиката е същата, с изключение на метода за получаване, използван за намиране на нашите документи. Това може лесно да се реши с условие да се провери дали методът е получил todo_id или не.

Създаване и актуализиране

Горният Gist съдържа както нашите POST, така и PUT методи, те се използват съответно за създаване на нови задачи и актуализиране на съществуващи задачи в нашата колекция.

В метода POST, наречен 'save_todo', ние четем тялото на HTTP заявката, използвана за извикване на метода, правим това с помощта на 'request.get_json( )' това ще върне тялото на заявката в JSON обект. Този JSON обект е новата To-Do, която искаме да запазим. За да го запазим, ние използваме метода запазване на нашия ToDoRepository,тъй като нашият метод за запазване връща идентификатора на новозапазения документ, можем да го използваме, за да намерим новата задача в нашата колекция и да го върне в тялото на нашия HTTP отговор с код на състоянието 201, което означава, че нещо е създадено.

Методът PUTсе използва за актуализиране на съществуващи ToDos, можем да използваме метода updateна нашето ToDoRepositoryза да направим това. Методът PUT чете тялото на заявката, подобно на метода POST, след като имаме ToDo, което е изпратено в заявката, запазваме идентификатора в собствената му променлива за използване по-късно в метода. Тъй като методът за актуализиране на ToDoRepository,връща броя документи, които са били актуализирани, можем да проверим този номер, за да видим дали актуализацията е била успешна. Ако беше успешно, можем да извлечем новоактуализирания документ от колекцията, като използваме идентификатора, записан по-рано, и да го върнем в отговора с код на състоянието 200. Ако актуализацията не беше успешна, например, ако се опитахме да актуализираме не -existent ToDo можем да върнем кода на състоянието 404, което означава, че ресурсът не е намерен.

Изтрий

Методът за изтриване е доста лесен за разбиране, той приема ID като параметър на пътя в URL адреса и го използва, за да извика метода delete на ToDoRepository. Отговорът на delete method е броят документи, изтрити от базата данни, така че можем да го използваме, за да проверим дали изтриването е било успешно или не. Ако броят на изтритите документи е по-голям или равен на един, това е било успешно и можем да върнем HTTP код за състояние 200, ако не, ще върнем 404.

Последната част от горния Gist е да извикате метода run на обекта Flask, създаден в горната част на този скрипт. Това ще стартира нашата услуга REST и ако всичко е наред, ще бъде готова за получаване на заявки.

Тестваме нашия нов REST API

След като вече сме написали кода си, ще трябва да го тестваме по някакъв начин, можем да направим това, като стартираме приложението и му изпратим някои заявки с помощта на REST клиент като Postman.

За да изпълним нашето приложение, можем или да го изпълним с вашата IDE като PyCharm, или ако предпочитате, можете да го изпълните с помощта на терминал, като отидете до папката, в която е вашият скрипт app.pyи изпълните следната команда .

Преди да стартирате вашето приложение, уверете се, че екземплярът ви на MongoDB работи локално, така че ToDoRepositoryда може да се свърже с него.

Добре, нека започнем да тестваме нашите крайни точки. Първо ще тестваме метода POST, тъй като методът GET няма да върне нищо, докато не добавим някои задачи към базата данни. Можем да ПУБЛИКУВАМЕ следния JSON обект на този URL адрес: “http://localhost:5000/

Обектът има два ключа, единият е булев за проследяване на състоянието на изпълнение на To-Do, а вторият е низ за описание на задачата. Използвайки Postman, изпратих горния JSON до крайната точка на POST.

В изображението можете да видите обекта за отговор, съдържащ новосъздадената To-Do с добавена към него стойност _id. Може също да забележите, че отговорът ни се върна с 201 СЪЗДАДЕНО в средната дясна част на изображението.

Сега, когато имаме нещо съхранено в нашата база данни, можем да тестваме нашите GET методи. Можем да направим това, като изпратим GET заявка до същия URL като по-горе.

В изображението ще видите, че получихме обратно нашата задача, създадена от нашата POST заявка по-горе, и също така получихме код за състояние 200 OK. Тъй като не сме имали никакви параметри на пътя, добавени към URL по-горе нарекохме метода get_todos, който ще върне всички документи в колекцията. След това трябва да тестваме нашия метод get_todo, който ще върне конкретна To-Do база на нейния ID. За да направим това, можем да добавим идентификатора, получен от един от предишните ни отговори и да го добавим към URL адреса и да изпратим GET заявката.

В изображението по-горе ще забележите, че получихме един обект обратно и че той не е част от списък като предишната GET заявка.

След това можем да тестваме нашия PUT метод, това се прави подобно на нашата POST заявка, с изключение на това, че тялото на заявката трябва също да включва ID.

В изображението по-горе ще видите, че изпратихме задачата, създадена от нашата POST заявка, с изключение на това, че променихме описанието от „Яжте закуски“ на „Яжте повече закуски“. Това ще актуализира To-Do в нашата база данни с дадения идентификатор към стойностите, изпратени в тази заявка, и както може да се види в отговора, е направено точно това. За да проверим това, можем отново да използваме метода GET за кръстосана проверка на резултатите.

Последният метод, който трябва да тестваме, е нашият метод DELETE. Това е много ясно, можем да използваме URL адреса и параметъра на пътя от нашата GET заявка по-горе, за да изпратим нашата DELETE заявка.

В отговора можем да видим, че сме получили код за състояние 200, което означава, че изтриването ни е било успешно, отново можем да потвърдим това, като използваме нашите методи GET.

И това е! Създадохме и тествахме REST API с помощта на Flask и Python! От тук бих предложил да опитате да направите горните методи малко по-сложни или да добавите повече методи за намиране на задачи с определени стойности в тях, вместо само по техния ID. Ще научите повече за това как работи Flask, колкото повече си играете с него и опитвате нови неща, какво ще кажете да опитате да добавите скриптовете към Docker изображение и да го стартирате в контейнер.

Обобщавайки

Надявам се, че проектът ви задейства и работи според очакванията и сте успели да тествате REST API, ако не сте го направили и откриете проблеми с ръководството, оставете отговор на историята и аз ще се опитам да ви помогна, доколкото мога . Ако откриете някакви проблеми с ръководството или нещо, което бих могъл да направя по-добре, не се колебайте да ме образовате и да оставите отговор! Както всички мои статии, пиша тази като образователен опит както за себе си, така и за другите, така че приветствам всякакви конструктивни отзиви.
Ако ръководството ви е харесало, помислете дали да не се абонирате за актуализации на бъдещите ми статии. Планирам да направя ръководство за контейнеризиране както на този проект, така и на моя проект Java and Spring Boot и да ги изпълнявам в Docker, така че ако това звучи интересно, помислете дали да ме следвате, за да получавате актуализации, също така, ако все още не сте член на Medium, можете присъединете се чрез моята връзка за членство.

Препратки

[1]^https://flask.palletsprojects.com/en/2.0.x/предговор/

[2]^https://www.mongodb.com/json-and-bson