Bottle Framework ile Rest API Oluşturma

Yayınlanan: 2022-03-11

REST API'leri, web arka uçları ve ön uçlar arasında ve farklı web hizmetleri arasında bir arayüz oluşturmanın yaygın bir yolu haline geldi. Bu tür bir arabirimin basitliği ve farklı ağlar ve çerçeveler arasında HTTP ve HTTPS protokollerinin her yerde desteği, birlikte çalışabilirlik sorunları düşünüldüğünde onu kolay bir seçim haline getirir.

Bottle, minimalist bir Python web çerçevesidir. Hafif, hızlı ve kullanımı kolaydır ve RESTful hizmetleri oluşturmaya çok uygundur. Andriy Kornatskyy tarafından yapılan temel bir karşılaştırma, onu yanıt süresi ve aktarım hızı (saniyedeki istek sayısı) açısından ilk üç çerçeve arasına koydu. DigitalOcean'dan temin edilebilen sanal sunucular üzerinde yaptığım kendi testlerimde, uWSGI sunucu yığını ve Bottle kombinasyonunun istek başına 140μs kadar düşük bir ek yük elde edebileceğini buldum.

Bu makalede, Bottle kullanarak bir RESTful API hizmetinin nasıl oluşturulacağına dair bir kılavuz sunacağım.

Şişe: Hızlı ve Hafif Bir Python Web Çerçevesi

Kurulum ve Yapılandırma

Şişe çerçevesi, etkileyici performansını kısmen hafifliği sayesinde elde eder. Aslında tüm kitaplık tek dosyalı bir modül olarak dağıtılır. Bu, diğer çerçeveler kadar elinizi tutmadığı, ancak aynı zamanda daha esnek olduğu ve birçok farklı teknoloji yığınına uyacak şekilde uyarlanabileceği anlamına gelir. Bu nedenle Bottle, performansın ve özelleştirilebilirliğin birinci sınıf olduğu ve daha ağır hizmet çerçevelerinin zaman kazandıran avantajlarının daha az dikkate alındığı projeler için en uygunudur.

Bottle'ın esnekliği, kendi yığınınızı yansıtmayabileceğinden, platformu kurmanın derinlemesine bir açıklamasını biraz boşuna yapar. Bununla birlikte, seçeneklere ve bunların nasıl kurulacağı hakkında daha fazla nereden bilgi edinileceğine dair hızlı bir genel bakış burada uygundur:

Kurulum

Bottle'ı kurmak, diğer Python paketlerini kurmak kadar kolaydır. Seçenekleriniz:

  • Sistemin paket yöneticisini kullanarak sisteminize kurun. Debian Jessie (mevcut kararlı), 0.12 sürümünü python-bottle olarak paketler.
  • pip install bottle ile Python Paket Dizini'ni kullanarak sisteminize kurun.
  • Sanal bir ortama kurun (önerilir).

Bottle'ı sanal bir ortama kurmak için virtualenv ve pip araçlarına ihtiyacınız olacak. Bunları yüklemek için, muhtemelen sisteminizde zaten bulunmasına rağmen, lütfen virtualenv ve pip belgelerine bakın.

Bash'te Python 3 ile bir ortam oluşturun:

 $ virtualenv -p `which python3` env

-p `which python3` parametresinin bastırılması, sistemde bulunan varsayılan Python yorumlayıcısının yüklenmesine yol açacaktır – genellikle Python 2.7. Python 2.7 desteklenir, ancak bu öğretici Python 3.4'ü varsayar.

Şimdi ortamı etkinleştirin ve Bottle'ı kurun:

 $ . env/bin/activate $ pip install bottle

Bu kadar. Şişe takılı ve kullanıma hazır. virtualenv veya pip ile aşina değilseniz, belgeleri birinci sınıftır. Bir göz at! Onlar buna değer.

sunucu

Bottle, Python'un standart Web Sunucusu Ağ Geçidi Arayüzü (WSGI) ile uyumludur, yani herhangi bir WSGI uyumlu sunucu ile kullanılabilir. Buna uWSGI, Tornado, Gunicorn, Apache, Amazon Beanstalk, Google App Engine ve diğerleri dahildir.

Bunu kurmanın doğru yolu, her ortama göre biraz değişir. Bottle, WSGI arabirimine uyan bir nesneyi ortaya çıkarır ve sunucunun bu nesneyle etkileşime girecek şekilde yapılandırılması gerekir.

Sunucunuzu nasıl kuracağınız hakkında daha fazla bilgi edinmek için sunucunun belgelerine ve burada Bottle'ın belgelerine bakın.

Veri tabanı

Bottle, veritabanından bağımsızdır ve verilerin nereden geldiğini umursamaz. Uygulamanızda bir veritabanı kullanmak istiyorsanız, Python Paket Dizini'nde SQLAlchemy, PyMongo, MongoEngine, CouchDB ve DynamoDB için Boto gibi birkaç ilginç seçenek vardır. Seçtiğiniz veritabanıyla çalışmasını sağlamak için yalnızca uygun adaptöre ihtiyacınız var.

Şişe Çerçevesi Temelleri

Şimdi Bottle'ta temel bir uygulamanın nasıl yapıldığını görelim. Kod örnekleri için Python >= 3.4 kabul edeceğim. Ancak burada yazacaklarımın çoğu Python 2.7 üzerinde de çalışacak.

Bottle'taki temel bir uygulama şöyle görünür:

 import bottle app = application = bottle.default_app() if __name__ == '__main__': bottle.run(host = '127.0.0.1', port = 8000)

Temel dediğimde, bu program size “Merhaba Dünya” bile yapmıyor. (En son ne zaman “Merhaba Dünya?” yanıtını veren bir REST arayüzüne eriştiniz) 127.0.0.1:8000 yapılan tüm HTTP istekleri, 404 Bulunamadı yanıt durumunu alır.

Şişedeki Uygulamalar

Bottle, oluşturulmuş birkaç uygulama örneğine sahip olabilir, ancak kolaylık olması açısından ilk örnek sizin için oluşturulur; varsayılan uygulama budur. Bottle, bu örnekleri modülün içindeki bir yığında tutar. Bottle ile bir şey yaptığınızda (uygulamayı çalıştırmak veya bir rota eklemek gibi) ve hangi uygulamadan bahsettiğinizi belirtmezseniz, bu varsayılan uygulamaya atıfta bulunur. Aslında, app = application = bottle.default_app() satırının bu temel uygulamada var olmasına bile gerek yoktur, ancak Gunicorn, uWSGI veya bazı genel WSGI sunucularıyla varsayılan uygulamayı kolayca çağırabilmemiz için oradadır.

Birden fazla uygulama olasılığı ilk başta kafa karıştırıcı görünebilir, ancak Bottle'a esneklik katıyorlar. Uygulamanızın farklı modülleri için, diğer Bottle sınıflarını somutlaştırarak ve gerektiğinde farklı konfigürasyonlarla ayarlayarak özel Bottle uygulamaları oluşturabilirsiniz. Bu farklı uygulamalara Bottle'ın URL yönlendiricisi aracılığıyla farklı URL'ler aracılığıyla erişilebilir. Bu eğitimde bunu derinlemesine incelemeyeceğiz, ancak burada ve burada Bottle'ın belgelerini incelemeniz önerilir.

Sunucu Çağırma

Komut dosyasının son satırı, belirtilen sunucuyu kullanarak Bottle'ı çalıştırır. Burada olduğu gibi sunucu belirtilmemişse, varsayılan sunucu Python'un yerleşik WSGI referans sunucusudur ve yalnızca geliştirme amaçlarına uygundur. Bunun gibi farklı bir sunucu kullanılabilir:

 bottle.run(server='gunicorn', host = '127.0.0.1', port = 8000)

Bu, bu betiği çalıştırarak uygulamayı başlatmanıza izin veren sözdizimsel şekerdir. Örneğin, bu dosyanın adı main.py ise, uygulamayı başlatmak için python main.py çalıştırmanız yeterlidir. Bottle, bu şekilde kullanılabilecek oldukça geniş bir sunucu adaptörleri listesi taşır.

Bazı WSGI sunucularında Bottle adaptörleri bulunmaz. Bunlar, sunucunun kendi çalıştırma komutlarıyla başlatılabilir. Örneğin, uWSGI'da tek yapmanız gereken uwsgi şu şekilde çağırmak olacaktır:

 $ uwsgi --http :8000 --wsgi-file main.py

Dosya Yapısı Üzerine Bir Not

Şişe, uygulamanızın dosya yapısını tamamen size bırakır. Dosya yapısı ilkelerimin projeden projeye değiştiğini, ancak bir MVC felsefesine dayanma eğiliminde olduğunu gördüm.

REST API'nizi Oluşturma

Elbette, hiç kimse, istenen her URI için yalnızca 404 döndüren bir sunucuya ihtiyaç duymaz. Size bir REST API oluşturacağımıza söz verdim, hadi yapalım.

Bir dizi adı işleyen bir arabirim oluşturmak istediğinizi varsayalım. Gerçek bir uygulamada muhtemelen bunun için bir veritabanı kullanırsınız, ancak bu örnek için sadece bellek içi set veri yapısını kullanacağız.

API'mizin iskeleti şöyle görünebilir. Bu kodu projenin herhangi bir yerine yerleştirebilirsiniz, ancak benim tavsiyem api/names.py gibi ayrı bir API dosyası olacaktır.

 from bottle import request, response from bottle import post, get, put, delete _names = set() # the set of names @post('/names') def creation_handler(): '''Handles name creation''' pass @get('/names') def listing_handler(): '''Handles name listing''' pass @put('/names/<name>') def update_handler(name): '''Handles name updates''' pass @delete('/names/<name>') def delete_handler(name): '''Handles name deletions''' pass

yönlendirme

Görüldüğü gibi Bottle içinde yönlendirme dekoratörler kullanılarak yapılmaktadır. İçe aktarılan dekoratörler, get dört eylem için kayıt işleyicilerini post , alır, put ve delete . Bu çalışmaların aşağıdaki gibi nasıl bölünebileceğini anlamak:

  • Yukarıdaki dekoratörlerin tümü, default_app yönlendirme dekoratörlerinin bir kısayoludur. Örneğin, @get() dekoratörü, işleyiciye bottle.default_app().get() uygular.
  • default_app üzerindeki yönlendirme yöntemlerinin tümü route() için kısayollardır. Yani default_app().get('/') , default_app().route(method='GET', '/') ) ile eşdeğerdir.

Yani @get('/') , @route(method='GET', '/') ile aynıdır, bu da @bottle.default_app().route(method='GET', '/') ile aynıdır. , ve bunlar birbirinin yerine kullanılabilir.

@route dekoratörüyle ilgili yararlı bir şey, örneğin, hem nesne güncellemeleri hem de silme işlemleriyle ilgilenmek için aynı işleyiciyi kullanmak isterseniz, şu şekilde işlediği yöntemlerin bir listesini iletebilmenizdir:

 @route('/names/<name>', method=['PUT', 'DELETE']) def update_delete_handler(name): '''Handles name updates and deletions''' pass

Pekala o zaman, bu işleyicilerden bazılarını uygulayalım.

Bottle Framework ile mükemmel REST API'nizi oluşturun.

RESTful API'ler, modern web geliştirmenin temelidir. Şişe arka ucuyla API istemcilerinize güçlü bir karışım sunun.
Cıvıldamak

POST: Kaynak Oluşturma

POST işleyicimiz şöyle görünebilir:

 import re, json namepattern = re.compile(r'^[a-zA-Z\d]{1,64}$') @post('/names') def creation_handler(): '''Handles name creation''' try: # parse input data try: data = request.json() except: raise ValueError if data is None: raise ValueError # extract and validate name try: if namepattern.match(data['name']) is None: raise ValueError name = data['name'] except (TypeError, KeyError): raise ValueError # check for existence if name in _names: raise KeyError except ValueError: # if bad request data, return 400 Bad Request response.status = 400 return except KeyError: # if name already exists, return 409 Conflict response.status = 409 return # add name _names.add(name) # return 200 Success response.headers['Content-Type'] = 'application/json' return json.dumps({'name': name})

Bu oldukça fazla. Bu adımları kısım kısım inceleyelim.

Vücut Ayrıştırma

Bu API, kullanıcının gövdede "name" adlı bir öznitelikle bir JSON dizesini POST yapmasını gerektirir.

Daha önce bottle içe aktarılan request nesnesi her zaman mevcut isteğe işaret eder ve isteğin tüm verilerini tutar. body niteliği, bir akış nesnesini okuyabilen (bir dosyayı okumak gibi) herhangi bir işlev tarafından erişilebilen istek gövdesinin bir bayt akışını içerir.

request.json() yöntemi, "application/json" içerik türü için isteğin başlıklarını kontrol eder ve doğruysa gövdeyi ayrıştırır. Bottle hatalı biçimlendirilmiş bir gövde algılarsa (örneğin: boş veya yanlış içerik türüyle), bu yöntem None değerini döndürür ve bu nedenle bir ValueError . JSON ayrıştırıcısı tarafından hatalı biçimlendirilmiş JSON içeriği algılanırsa; yine bir ValueError olarak yakalayıp yeniden oluşturduğumuz bir istisna oluşturur.

Nesne Ayrıştırma ve Doğrulama

Hata yoksa, isteğin gövdesini data değişkeni tarafından başvurulan bir Python nesnesine dönüştürdük. "Ad" anahtarı olan bir sözlük aldıysak, ona data['name'] aracılığıyla erişebileceğiz. Bu anahtar olmadan bir sözlük aldıysak, ona erişmeye çalışmak bizi bir KeyError istisnasına götürecektir. Sözlük dışında bir şey alırsak, TypeError istisnası alırız. Bu hatalardan herhangi biri meydana gelirse, onu bir kez daha ValueError olarak yeniden yükseltiriz, bu da hatalı bir girdi olduğunu gösterir.

İsim anahtarının doğru formatta olup olmadığını kontrol etmek için, onu burada oluşturduğumuz namepattern maskesi gibi bir normal ifade maskesine karşı test etmeliyiz. Anahtar name bir dize değilse, namepattern.match() bir TypeError yükseltir ve eşleşmezse None döndürür.

Bu örnekteki maske ile bir ad, 1 ila 64 karakter arasında boşluk içermeyen bir ASCII alfasayısal olmalıdır. Bu basit bir doğrulamadır ve örneğin çöp verileri olan bir nesneyi test etmez. FormEncode gibi araçlar kullanılarak daha karmaşık ve eksiksiz doğrulama elde edilebilir.

Varoluş Testi

İsteği yerine getirmeden önceki son test, verilen adın kümede zaten var olup olmadığıdır. Daha yapılandırılmış bir uygulamada, bu test muhtemelen özel bir modül tarafından yapılmalı ve özel bir istisna aracılığıyla API'mize bildirilmelidir, ancak bir seti doğrudan manipüle ettiğimiz için bunu burada yapmak zorundayız.

Bir KeyError yükselterek adın varlığını bildiririz.

Hata Yanıtları

İstek nesnesinin tüm istek verilerini tutması gibi, yanıt nesnesi de yanıt verileri için aynı şeyi yapar. Yanıt durumunu ayarlamanın iki yolu vardır:

 response.status = 400

ve:

 response.status = '400 Bad Request'

Örneğimiz için daha basit formu seçtik, ancak ikinci form hatanın metin açıklamasını belirtmek için kullanılabilir. Dahili olarak Bottle ikinci diziyi bölecek ve sayısal kodu uygun şekilde ayarlayacaktır.

Başarı Yanıtı

Tüm adımlar başarılı olursa, adı set _names ekleyerek, Content-Type yanıt başlığını ayarlayarak ve yanıtı döndürerek isteği yerine getiriyoruz. İşlev tarafından döndürülen herhangi bir dize, 200 Success yanıtının yanıt gövdesi olarak ele alınacaktır, bu nedenle json.dumps ile bir tane oluşturmamız yeterlidir.

GET: Kaynak Listeleme

Ad oluşturma işleminden geçerek, ad listeleme işleyicisini uygulayacağız:

 @get('/names') def listing_handler(): '''Handles name listing''' response.headers['Content-Type'] = 'application/json' response.headers['Cache-Control'] = 'no-cache' return json.dumps({'names': list(_names)})

İsimleri listelemek çok daha kolaydı, değil mi? İsim oluşturma ile karşılaştırıldığında burada yapılacak pek bir şey yok. Basitçe bazı yanıt başlıklarını ayarlayın ve tüm adların bir JSON temsilini döndürün ve işimiz bitti.

PUT: Kaynak Güncellemesi

Şimdi güncelleme yöntemini nasıl uygulayacağımızı görelim. Create yönteminden çok farklı değil, ancak bu örneği URI parametrelerini tanıtmak için kullanıyoruz.

 @put('/names/<oldname>') def update_handler(name): '''Handles name updates''' try: # parse input data try: data = json.load(utf8reader(request.body)) except: raise ValueError # extract and validate new name try: if namepattern.match(data['name']) is None: raise ValueError newname = data['name'] except (TypeError, KeyError): raise ValueError # check if updated name exists if oldname not in _names: raise KeyError(404) # check if new name exists if name in _names: raise KeyError(409) except ValueError: response.status = 400 return except KeyError as e: response.status = e.args[0] return # add new name and remove old name _names.remove(oldname) _names.add(newname) # return 200 Success response.headers['Content-Type'] = 'application/json' return json.dumps({'name': newname})

Güncelleme eylemi için gövde şeması, oluşturma eylemiyle aynıdır, ancak artık URI'de, route @put('/names/<oldname>') oldname tanımlandığı gibi yeni bir eski isim parametresine sahibiz.

URI Parametreleri

Gördüğünüz gibi, Bottle'ın URI parametreleri için gösterimi çok basittir. URI'leri istediğiniz kadar parametre ile oluşturabilirsiniz. Bottle bunları otomatik olarak URI'den alır ve istek işleyicisine iletir:

 @get('/<param1>/<param2>') def handler(param1, param2): pass

Basamaklı rota dekoratörlerini kullanarak, isteğe bağlı parametrelerle URI'ler oluşturabilirsiniz:

 @get('/<param1>') @get('/<param1>/<param2>') def handler(param1, param2 = None) pass

Ayrıca Bottle, URI'lerde aşağıdaki yönlendirme filtrelerine izin verir:

  • int

Yalnızca int biçimine dönüştürülebilen parametrelerle eşleşir ve dönüştürülen değeri işleyiciye iletir:

 @get('/<param:int>') def handler(param): pass
  • float

int ile aynı, ancak kayan nokta değerleriyle:

 @get('/<param:float>') def handler(param): pass
  • re (normal ifadeler)

Yalnızca verilen normal ifadeyle eşleşen parametrelerle eşleşir:

 @get('/<param:re:^[az]+$>') def handler(param): pass
  • path

URI yolunun alt bölümlerini esnek bir şekilde eşleştirir:

 @get('/<param:path>/id>') def handler(param): pass

Maçlar:

  • /x/id , x param olarak iletmek.
  • /x/y/id , x/y param olarak iletir.

SİL: Kaynak Silme

GET yöntemi gibi, DELETE yöntemi de bize çok az haber getirir. Bir durum ayarlamadan None döndürmenin, boş bir gövde ve 200 durum koduyla bir yanıt döndürdüğünü unutmayın.

 @delete('/names/<name>') def delete_handler(name): '''Handles name updates''' try: # Check if name exists if name not in _names: raise KeyError except KeyError: response.status = 404 return # Remove name _names.remove(name) return

Son Adım: API'yi Etkinleştirme

Ad api/names.py olarak kaydettiğimizi varsayarsak, artık bu yolları main.py ana uygulama dosyasında etkinleştirebiliriz.

 import bottle from api import names app = application = bottle.default_app() if __name__ == '__main__': bottle.run(host = '127.0.0.1', port = 8000)

Yalnızca names modülünü içe aktardığımıza dikkat edin. Tüm yöntemleri, varsayılan uygulamaya eklenmiş URI'leri ile süslediğimiz için, başka bir kurulum yapmanıza gerek yoktur. Yöntemlerimiz zaten yerinde, erişilmeye hazır.

Hiçbir şey ön yüzü iyi yapılmış bir REST API kadar mutlu edemez. Tıkır tıkır çalışıyor!

API'yi kullanmak ve manuel olarak test etmek için Curl veya Postman gibi araçları kullanabilirsiniz. (Curl kullanıyorsanız, yanıtın daha az karmaşık görünmesi için bir JSON biçimlendirici kullanabilirsiniz.)

Bonus: Çapraz Menşe Kaynak Paylaşımı (CORS)

REST API oluşturmanın yaygın bir nedeni, AJAX aracılığıyla bir JavaScript ön ucu ile iletişim kurmaktır. Bazı uygulamalar için, bu isteklerin yalnızca API'nizin ana etki alanından değil, herhangi bir etki alanından gelmesine izin verilmelidir. Varsayılan olarak, çoğu tarayıcı bu davranışa izin vermez, bu nedenle, buna izin vermek için Bottle'ta çıkış noktaları arası kaynak paylaşımını (CORS) nasıl kuracağınızı göstermeme izin verin:

 from bottle import hook, route, response _allow_origin = '*' _allow_methods = 'PUT, GET, POST, DELETE, OPTIONS' _allow_headers = 'Authorization, Origin, Accept, Content-Type, X-Requested-With' @hook('after_request') def enable_cors(): '''Add headers to enable CORS''' response.headers['Access-Control-Allow-Origin'] = _allow_origin response.headers['Access-Control-Allow-Methods'] = _allow_methods response.headers['Access-Control-Allow-Headers'] = _allow_headers @route('/', method = 'OPTIONS') @route('/<path:path>', method = 'OPTIONS') def options_handler(path = None): return

hook dekoratörü, her istekten önce veya sonra bir işlevi çağırmamızı sağlar. Bizim durumumuzda, CORS'u etkinleştirmek için, yanıtlarımızın her biri için Access-Control-Allow-Origin , -Allow-Methods ve -Allow-Headers başlıklarını ayarlamalıyız. Bunlar, talep edene, belirtilen talepleri yerine getireceğimizi belirtir.

Ayrıca, istemci, diğer yöntemlerle gerçekten istekte bulunup bulunamayacağını görmek için sunucuya bir OPTIONS HTTP isteğinde bulunabilir. Bu örnek tümünü yakalama örneğiyle, tüm OPTIONS isteklerine 200 durum kodu ve boş gövde ile yanıt veriyoruz.

Bunu etkinleştirmek için kaydedin ve ana modülden içe aktarın.

Sarmak

Hepsi bu kadar!

Bu eğitimde, Bottle web çerçevesine sahip bir Python uygulaması için bir REST API oluşturmanın temel adımlarını ele almaya çalıştım.

Öğretici ve API referans belgelerini ziyaret ederek bu küçük ama güçlü çerçeve hakkındaki bilginizi derinleştirebilirsiniz.

İlgili: Node.js/TypeScript REST API Oluşturma, Bölüm 1: Express.js