Bottle Framework ile Rest API Oluşturma
Yayınlanan: 2022-03-11REST 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.
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şleyiciyebottle.default_app().get()
uygular. -
default_app
üzerindeki yönlendirme yöntemlerinin tümüroute()
için kısayollardır. Yanidefault_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.
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.
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.