Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Build a simple web app using BottlePy
Lorenzo Garuti
Lorenzo Garuti

Posted on • Originally published atgarutilorenzo.github.io

     

Build a simple web app using BottlePy

Build a simple web application with SQLAlchemy and Redis support using BottlePy

Table of Contents

Requirements

To use this environment you needDocker anDocker compose installed.

The application stack

Backend:

Frontend:

Database:

Webserver:

Data used

To use this example application we need to import some example data. This example application use the "Stack Exchange Data Dump" available onarchive.org.

All the data used by this site is under thecc-by-sa 4.0 license.

Setup the environmant

First of all clonethis repository

git clone https://github.com/garutilorenzo/simple-bottlepy-application.gitcdsimple-bottlepy-application
Enter fullscreen modeExit fullscreen mode

Now we are ready to setup our environment. For dev purposes link the docker-compose-dev.yml do docker-compose.yml

ln-s docker-compose-dev.yml docker-compose.yml
Enter fullscreen modeExit fullscreen mode

For prod environments:

ln-s docker-compose-dev.yml docker-compose.yml
Enter fullscreen modeExit fullscreen mode

The difference between dev and prod envs are:

ProdDev
Nginx is used to expose our example applicationBuilt-in HTTP development server
Http port 80Http port 8080
Debug mode is disabledDebug mode is enabled
Reloader is disabledReloader is enabled

Now we can download some sample data.

Download sample data

To dwonload some example data run:

./download_samples.sh
Enter fullscreen modeExit fullscreen mode

By default the archive with the 'meta' attribute will be downloaded. If you want more data remove in download_samples.sh 'meta' form the archive name.

Small data:

forsampleinworkplace.meta.stackexchange.com.7z unix.meta.stackexchange.com.7z
Enter fullscreen modeExit fullscreen mode

Big data:

forsampleinworkplace.stackexchange.com.7z unix.stackexchange.com.7z
Enter fullscreen modeExit fullscreen mode

Note not all the stackexchange sites where imported on this example. After you choose the archives you will download adjust the network.py schema under src/schema/

classSites(enum.Enum):vi='vi.stackexchange.com'workplace='workplace.stackexchange.com'wordpress='wordpress.stackexchange.com'unix='unix.stackexchange.com'tex='tex.stackexchange.com'
Enter fullscreen modeExit fullscreen mode

Once the data is downloaded we can import the data:

docker-compose run--rm bottle bashweb@4edf053b7e4f:~/src$ python init_db.py# <- Initialize DBweb@4edf053b7e4f:~/src$ python import_data.py# <- Import sample data
Enter fullscreen modeExit fullscreen mode

Now for each data sample you have downloaded (Eg. tex, unix, vi) a python subprocess is started and will import in order:

  • all the tags
  • all the users
  • all the posts
  • all the post history events

Once the import is finished we can start our environment

Start the environment

With our DB populated we can now start our web application:

docker-compose up-dCreating network"bottle-exchange_default" with the default driverCreating postgres ...doneCreating redis    ...doneCreating bottle   ...done
Enter fullscreen modeExit fullscreen mode

The application will be available athttp://localhost:8080 for the dev andhttp://localhost for the prod

Application Overview

Index

Home page with a search form. On every change on the "Network" select, the "Tags" select is populated by an ajax call on /api/autocomplete/form/get_tags (POST) (for more details see src/bottle/static/asset/js/custom.js).

The POST call is authenticated with a random hard coded string (see src/bottle/app.py, api_get_tags)

Tags

List of all available tags with a pagination nav.
Clicking on the tag name the application will search all questions matching the tag you have selected, by clicking the site name the application will search all questions matching the tag and the site you ave selected.

Users

Table view of all available users with a pagination nav.

Clicking on the username, we enter on the detail's page of the user. In the details user page we see: UP Votes,Views,Down Votes. If the user has populated the "About me" field, we see a button that trigger a modal with the about me details.
If the user ha asked or answered some question we see a list of question in the "Post" section.

Posts

List of all posts with a pagination nav

API/REST endpoint

This application expose one api/rest route: /api/get/tags. You can query this route making a POST call, you have to make the call using a json payload:

curl--header"Content-Type: application/json"\--request POST\--data'{"auth_key":"dd4d5ff1c13!28356236c402d7ada.aed8b797ebd299b942291bc66,f804492be2009f14"}'\  http://localhost:8080/api/get/tags | jq{"data":[{"clean_name":"html5","created_time":"2021-12-29 11:33:06.517152+00:00","id":"1","name":"html5","network_sites":"Sites.wordpress","questions":"91","tag_id":"2","updated_time":"None"},    ...],"errors":[],"items": 5431,"last_page": 27}
Enter fullscreen modeExit fullscreen mode

The auth_key is hard coded in src/bottle/app.py

App configuration

The application's configuration are loaded by the load_config module (src/load_config.py).

This module will load a .yml file under:

/app/src/<BOTTLE_APP_NAME>/config/<BOTTLE_APP_ENVIRONMENT>
Enter fullscreen modeExit fullscreen mode

BOTTLE_APP_NAME andBOTTLE_APP_ENVIRONMENT are environment variables.

BOTTLE_APP_NAME is the name of the path where our bottle application lives, in this casebottle. BOTTLE_APP_ENVIRONMENT value is prod or env.

An example configuration is:

---enable_debug:Trueenable_reloader:Truehttp_port:8080pgsql_username:"bottle"pgsql_password:"b0tTl3_Be#"pgsql_db:"bottle_exchange"pgsql_host:"pgsql"pgsql_port:5432create_db_schema:Truedefault_result_limit:50
Enter fullscreen modeExit fullscreen mode

DB configuration

The database configuration is defined under the src/schema module.

The base.py file contains the engine configuration:

importload_config# <- See App configurationfromsqlalchemyimportcreate_enginefromsqlalchemy.ext.declarativeimportdeclarative_basefromsqlalchemy.ormimportsessionmakermain_config=load_config.load_config()conn_string='postgresql+psycopg2://{pgsql_username}:{pgsql_password}@{pgsql_host}:{pgsql_port}/{pgsql_db}'.format(**main_config)engine=create_engine(conn_string,pool_size=80,pool_recycle=60)Session=sessionmaker(bind=engine)Base=declarative_base()
Enter fullscreen modeExit fullscreen mode

All the tables are defined in a separate file always under schema:

schemaTablesDescription
network.pyNoneSites class is not a SQLAlchemy object, but is an Enum used by all the other tables
posts.pyposts,post_historyTable definition for post and post_history tables. This module contains also three enum definitions: PostType, PostHistoryType, CloseReason
tags.pytagsTags table
users.pyusersUsers table

SQLAlchemy plugin

In this example application is used and insalled a SQLAlchemy plugin (src/bottle/bottle_sa.py). This plugin is used to handle the SQLAlchemy session:

fromschema.baseimportBase,engine# <- Base and engine are defined in the schema module, see "DB configuration"frombottle_saimportSQLAlchemyPluginimportload_configmain_config=load_config.load_config()# Main Bottle app/applicationapp=application=Bottle()# DB PluginsaPlugin=SQLAlchemyPlugin(engine=engine,metadata=Base.metadata,create=main_config['create_db_schema'],config=main_config,)application.install(saPlugin)
Enter fullscreen modeExit fullscreen mode

This plugin pass an extra parameter on each function defined in src/bottle/app.py. By default this parameter is 'db', but it can be changed by passing the extra parameter 'keyword' on the SQLAlchemyPlugin init.

So an example function will be:

@app.route('/docs')@view('docs')defindex(db):# <- db is our SQLAlchemy sessionreturndict(page_name='docs')
Enter fullscreen modeExit fullscreen mode

Redis Cache

In this example application we use redis to cache some pages. The caching "approach" is very useful if you have:

  • a site with few updates
  • a slow page/route
  • you have to decrease the load of your DB

The RedisCache is defined in src/bottle/bottle_cache.py and this is an example usage:

frombottle_cacheimportRedisCache# Cachecache=RedisCache()@app.route('/tags')@app.route('/tags/<page_nr:int>')@cache.cached()@view('tags')defget_tags(db,page_nr=1):do_something()returnsomething
Enter fullscreen modeExit fullscreen mode

You can init RedisCache class with an extra parameter config:

config={'redis_host':'<redis_hostname'>,'redis_port':6379,'redis_db':0,'cache_expiry':86400}cache=RedisCache(config=config)
Enter fullscreen modeExit fullscreen mode

By default the configurations are:

ParamDefaultDescription
redis_hostredisRedis FQDN or ip address
redis_port6379Redis listen port
redis_db0Redis database
cache_expiry3600Global cache expiry time in seconds

The @cached decorator can accept some arguments:

ParamDefaultDescription
expiryNoneRoute cache expiry time. If not defined is the same value as the global expiry time
key_prefixbottle_cache_%sRedis key prefix
content_typetext/html; charset=UTF-8Default content type

Caching json requests

A json caching example would be:

@app.route('/api/get/tags',method='POST')@cache.cached(content_type='application/json')defapi_get_tags(db):do_something()returnsomething
Enter fullscreen modeExit fullscreen mode

Invalidate cache

To invalidate the cache passinvalidate_cache key as query parameter or in the body request if you make a POST call

Skip/bypass cache

To skip or bypass the cache passskip_cache key as query parameter or in the body request if you make a POST call

Data export

To backup PgSQL data run dump_db.sh

./dump_db.sh
Enter fullscreen modeExit fullscreen mode

the dump will be placed in the root directory of this repository, the file will be named dump.sql.gz (Gzipped format)

Data import

To import an existing DB uncomment the following line in the docker-compose.yml:

volumes:-type:volumesource:postgrestarget:/var/lib/postgresql/data-./sql:/docker-entrypoint-initdb.d# <- uncomment this line
Enter fullscreen modeExit fullscreen mode

and plache your dump in gzip or plain text format under sql/ (create the directory first)

Stop the environment

To stop the environment run

docker-compose downStopping bottle   ...doneStopping postgres ...doneStopping redis    ...doneRemoving bottle   ...doneRemoving postgres ...doneRemoving redis    ...doneRemoving network bottle-exchange_default
Enter fullscreen modeExit fullscreen mode

To clean up all the data (pgsql data) pass the extra argument "-v" to docker-compose down. With this parameter the pgsql volume will be deleted.

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

☁️ Multi Cloud solution architect • 🖥️ Linux system engineer • 🚀 DevOps • 👨‍💻Software developer
  • Location
    Italy
  • Education
    high school diploma
  • Work
    Linux system engineer
  • Joined

Trending onDEV CommunityHot

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp