Only this pageAll pages
Powered by GitBook
1 of 69

v1.6

Loading...

Prologue

Loading...

Loading...

Loading...

Loading...

Loading...

What's New

Loading...

Loading...

Loading...

Loading...

Upgrade Guide

Loading...

Loading...

Loading...

The Basics

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

The Craft Command

Loading...

Loading...

Authentication System

Architectural Concepts

Loading...

Loading...

Loading...

Advanced

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Useful Features

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Security

Loading...

Loading...

Loading...

Orator ORM

Loading...

Managers and Drivers

Loading...

Loading...

Loading...

Official Packages

Creating Your First Blog

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Known Installation Issues

Introduction

There are no known Masonite specific issues known and if there are then there should be an issue open for them on GitHub. With that being said, some users may experience some difficulties with installing Masonite simply because their computer environment is not the norm, they have never setup Python and may have configured it incorrectly or they have not used Python in a while and have an old version.

Before you get started reading through this FAQ make sure you have:

  • Python 3.4+

  • Pip3

Ensure you are installing masonite-cli with pip3 and not pip.

I ran pip install masonite-cli but it didn't gave me a permission error

You are likely running this command on a UNIX based machine like Mac or Linux. In that case you should either run it again with a sudo command or a user command flag:

$ sudo pip install masonite-cli

or

$ pip install --user masonite-cli

I'm getting this weird ModuleNotFound idna issue when running the craft new command

You may get a strange error like:

pkg_resources.DistributionNotFound: The 'idna<2.7,>=2.5' distribution was not found and is required by requests

The simple fix may just be to run:

pip install idna==2.6

If that does not fix the issue then continue reading.

If the above fix did not work then this likely means you installed masonite-cli using the Python 2.7 pip command. Out of the box, all Mac and Linux based machines have Python 2.7. If you run:

$ python --version

you should get a return value of:

Python 2.7.14

But if you run:

$ python3 --version

you should get a return value of:

Python 3.6.5

Now pip commands are similar:

$ pip --version
pip 10.0.1 /location/of/installation (python 2.7)

$ pip3 --version
pip 10.0.1 /location/of/installation (python 3.6)

Notice here we are using 2 different Python installations.

So if you are getting this error you should uninstall masonite-cli from pip and reinstall it using pip3:

$ pip uninstall masonite-cli
$ pip3 install masonite-cli

You may have to run sudo to remove it and you may need to close your terminal to get it work

I installed masonite-cli successfully but the craft command is not showing up

If you installed everything successfully and running:

$ craft

Shows an error that it can't be found then try closing your terminal and opening it again. This should refresh any commands that were recently installed

If you still have errors and on a UNIX based machine try running:

$ sudo pip3 install masonite-cli

I'm getting a module urlib has no attribute urlopen

You likely ran:

$ craft new project_name

and hit this weird snag that throws this ambiguous error. You might think this is because of a Python version issue but craft is designed to work on Python 2.7 and 3.4+ (although 2.7 and not thoroughly tested) and you're years of Python experience would make you right but this is special. If you are getting this error then that means you are likely on a UNIX machine, Mac right?

The problem is that your machine does not have sufficient permissions to access these external calls from the command line because your machine does not have permission to do so. You will have to give you machine the command to do so by running:

$ /Applications/Python\ 3.6/Install\ Certificates.command

or whatever your Python 3 version is in the middle. Now try running:

$ craft new project_name

and it should work great!

Introduction

Masonite is the rapid application Python development framework that strives for: beautiful and elegant syntax, actual batteries included with a lot of out of the box functionality, and extremely extendable. Masonite works hard to be fast and easy from install to deployment so developers can go from concept to creation in as quick and efficiently as possible. Try it once and you’ll fall in love.

Some Notable Features Shipped With Masonite

  • Easily send emails with the Mail Provider and the SMTP and Mailgun drivers.

  • Send websocket requests from your server with the Broadcast Provider and Pusher and Ably drivers.

  • IOC container and auto resolving dependency injection.

  • Service Providers to easily add functionality to the framework.

  • Extremely simple static files configured and ready to go.

  • Active Record style ORM called Orator.

  • An extremely useful command line tool called craft commands.

  • Extremely extendable.

These, among many other features, are all shipped out of the box and ready to go. Use what you need when you need it.

Requirements

In order to use Masonite, you’ll need:

  • Python 3.4+

  • Pip

Linux

If you are running on a Linux flavor, you’ll need the Python dev package and OpenSSL. You can download this package by running:

$ sudo apt-get install python-dev libssl-dev

Or you may need to specify your python3.x-dev version:

$ sudo apt-get install python3.6-dev libssl-dev

Installation

Masonite works at being simple to install and get going. We use a simple command line that will become your best friend. You’ll never want to develop again without it. We call it the craft command line tool.

We can download our craft command line tool by just running:

$ pip install masonite-cli

You may have to use sudo if you are on a UNIX machine

All pip commands are assuming they are connected to a Python 3.4+ installation. If you are having a hard time installation then try running all pip commands in this documentation with pip3 commands.

Great! We are now ready to create our first project. We should have the new craft command. We can check this by running:

$ craft

This should show a list of command options. If it doesn't then try closing your terminal and reopening it or running it with sudo if you are on a UNIX machine. We are currently only interested in the craft new command. To create a new project just run:

$ craft new project_name
$ cd project_name

This will get the latest Masonite project template and unzip it for you. We just need to go into our new project directory and install the dependencies in our requirements.txt file.

You can optionally create a virtual environment if you don't want to install all of masonite's dependencies on your systems Python. If you use virtual environments then create your virtual environment by running:

$ python -m venv venv
$ source venv/bin/activate

or if you are on Windows:

$ python -m venv venv
$ ./venv/Scripts/activate

Now lets install our dependencies. We can do this simply by using a craft command:

$ craft install

This installs all the required dependencies of Masonite, creates a .env file for us, generates a new secret key, and puts that secret key in our .env file. After it’s done we can just run the server by using another craft command:

$ craft serve

Contributing Guide

Introduction

When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners or contributors of this repository before making a change.

Please note we have a code of conduct, please follow it in all your interactions with the project.

Getting Started

The framework has three main parts.

This MasoniteFramework/masonite repository is the main repository that will install when creating new projects using the craft new command. Not much development will be done in this repository and won't be changed unless new releases of Masonite require changes in the default installation project.

The MasoniteFramework/core repository where the main masonite pip package lives. This is where the from masonite ... module lives.

The MasoniteFramework/craft repository where the craft command lives

Getting the Masonite repository up and running to be edited

This repo is simple and will be able to be installed following the installation instruction in the README.

  • Fork the MasoniteFramework/masonite repo.

  • Clone that repo into your computer:

    • git clone http://github.com/your-username/masonite.git

  • Checkout the current release branch (example: develop)

  • You should now be on a develop local branch.

  • Run git pull origin develop to get the current release version.

  • From there simply create your feature branches (change-default-orm) and Make your desired changes.

  • Push to your origin repository:

    • git push origin change-default-orm

  • Open a pull request and follow the PR process below

Editing the Masonite core repository

The trick to this is that we need it to be pip installed and then quickly editable until we like it, and then pushed back to the repo for a PR. Do this only if you want to make changes to the core Masonite package

To do this just:

  • Fork the MasoniteFramework/core repo,

  • Clone that repo into your computer:

    • git clone http://github.com/your-username/core.git

  • Activate your masonite virtual environment (optional)

    • Go to where you installed masonite and activate the environment

  • While inside the virtual environment, cd into the directory you installed core.

  • Run pip install . from inside the masonite-core directory. This will install masonite as a pip package.

  • Any changes you make to this package just push it to your feature branch on your fork and follow the PR process below.

Editing the craft repository (craft commands)

Craft commands make up a large part of the workflow for Masonite. Follow these instructions to get the masonite-cli package on your computer and editable.

  • Fork the MasoniteFramework/craft repo,

  • Clone that repo into your computer:

    • git clone http://github.com/your-username/craft.git

  • Activate your masonite virtual environment (optional)

    • Go to where you installed masonite and activate the environment

  • While inside the virtual environment, cd into the directory you installed cli

  • Run pip install --editable . from inside the masonite-cli directory. This will install craft (which contains the craft commands) as a pip package but also keep a reference to the folder so you can make changes freely to craft commands while not having to worry about continuously reinstalling it.

  • Any changes you make to this package just push it to your feature branch on your fork and follow the PR process below.

Comments

Comments are a vital part of any repository and should be used where needed. It is important not to overcomment something. If you find you need to constantly add comments, you're code may be too complex. Code should be self documenting (with clearly defined variable and method names)

Types of comments to use

There are 3 main type of comments you should use when developing for Masonite:

Module Docstrings

All modules should have a docstring at the top of every module file and should look something like:

""" This is a module to add support for Billing users """
from masonite.request import Request
...

Method and Function Docstrings

All methods and functions should also contain a docstring with a brief description of what the module does

For example:

def some_function(self):
    """
    This is a function that does x action. 
    Then give an exmaple of when to use it 
    """
    ... code ...

Code Comments

If you're code MUST be complex enough that future developers will not understand it, add a # comment above it

For normal code this will look something like:

# This code performs a complex task that may not be understood later on
# You can add a second line like this
complex_code = 'value'

perform_some_complex_task()

Flagpole Comments

Flag pole comments are a fantastic way to give developers an inside to what is really happening and for now should only be reserved for configuration files. A flag pole comment gets its name from how the comment looks

'''
|--------------------------------------------------------------------------
| A Heading of The Setting Being Set
|--------------------------------------------------------------------------
|
| A quick description
|
'''

SETTING = 'some value'

It's important to note that there should have exactly 75 - above and below the header and have a trailing | at the bottom of the comment.

Pull Request Process

  1. You should open an issue before making any pull requests. Not all features will be added to the framework and some may be better off as a third party package. It wouldn't be good if you worked on a feature for several days and the pull request gets rejected for reasons that could have been discussed in an issue.

  2. Ensure any changes are well commented and any configuration files that are added have a flagpole comment on the variables it's setting.

  3. Update the README.md and MasoniteFramework/docs repo with details of changes to the interface, this includes new environment variables, new file locations, container parameters etc.

  4. You must add unit testing for any changes made. Of the three repositories listed above, only the craft and core repos require unit testing.

  5. The PR must pass the Travis CI build. The Pull Request can be merged in once you have a successful review from two other collaborators, or the feature maintainer for your specific feature improvement or the repo owner.

Code of Conduct

Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

Our Standards

Examples of behavior that contributes to creating a positive environment include:

  • Using welcoming and inclusive language

  • Being respectful of differing viewpoints and experiences

  • Gracefully accepting constructive criticism

  • Focusing on what is best for the community

  • Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

  • The use of sexualized language or imagery and unwelcome sexual attention or advances

  • Trolling, insulting/derogatory comments, and personal or political attacks

  • Public or private harassment

  • Publishing others' private information, such as a physical or electronic

    address, without explicit permission

  • Other conduct which could reasonably be considered inappropriate in a

    professional setting

Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at idmann509@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

Attribution

Congratulations! You’ve setup your first Masonite project! Keep going to learn more about how to use Masonite to build your applications. You can learn more about craft by reading documentation.

Increase the version numbers in any example files and the README.md to the new version that this Pull Request would represent. The versioning scheme we use is for both core and craft or for the main Masonite repo.

This Code of Conduct is adapted from the , version 1.4, available at

The Craft Command
You can read about how the framework flows, works and architectural concepts here
SemVer
RomVer
Contributor Covenant
http://contributor-covenant.org/version/1/4

Masonite 1.4

Introduction

Broadcast Support

Caching

Masonite now has a built in caching class that you can use to either cache forever or cache for a specific amount of time.

Template Caching

Templates may have a lot of logic that are only updated every few minutes or even every few months. With template caching you can now cache your templates anywhere from every few seconds to every few years. This is an extremely powerful caching technique that will allow your servers to run less intensively and easily increase the performance of your application.

If a page gets hit 100 times every second then you can cache for 5, 10 or 15 seconds at a time to lessen the load on your server.

This feature only activates if you have the CacheProvider loaded in your PROVIDERS list. If you try to use these features without that provider then you will be hit with a RequiredContainerBindingNotFound exception letting you know you are missing a required binding from a service provider. This provider comes out of the box in Masonite 1.4.

PEP 8 Standards

We have also updated the code to closely conform to PEP 8 standards.

Added a New Folder and Configuration File

Because of the caching features, we have added a bootstrap/cache folder where all caching will be put but you can change this in the new config/cache.py file.

Added Contracts

Masonite 1.4 brings the idea of contracts which are very similar to interfaces in other languages. Contracts ensure that a driver or manager inherits has the same functionality across all classes of the same type.

Added CSRF Protection

Cross-Site Request Forgery is a crucial security milestone to hit and Masonite 1.4 brings that ability. With a new Service Provider and middleware, we can now add a simple {{ csrf_field|safe }} to our forms and ensure we are protected from CSRF attacks.

Changed Managers

Managers were very redundant before this release so we made it much easier to create managers with 2 simple class attributes instead of the redundant method. Managers are used to manage features and drivers to Masonite.

Middleware is now resolved by the container

Now the constructor of all middleware is resolved by the container. This means you may use the IOC dependency injection techniques like controller methods and drivers.

Fixed unused imports

There were two unused imports in the models that Masonite created. These have been removed completely.

Masonite 1.4 brings several new features to Masonite. These features include caching, template caching, websocket support with Masonite calls Broadcasting and much more testing to make Masonite as stable as possible. If you would like to contribute to Masonite, please read the and the documentation.

If you are upgrading from Masonite 1.3 then please read the documentation.

We recognize that in order for frameworks to keep up with modern web application, they require real time broadcasting. Masonite 1.4 brings basic broadcasting of events to masonite and comes with two drivers out of the box: pusher and ably. If you'd like to create more drivers then you can do so easily by reading the documentation. If you do create a driver, please consider making it available on PyPi so others can install it into their projects or open an issue on GitHub and make to add it to the built in drivers.

Contributing Guide
How To Contribute
Masonite 1.3 to 1.4
About Drivers

Release Cycle

Introduction

The Masonite framework itself follows the RomVer versioning schema which is PARADIGM.MAJOR.MINOR although all Masonite packages follow the SemVer versioning schema which is MAJOR.MINOR.FEATURE/BUGFIX.

This means that a framework version of 1.3.20 will have breaking changes with version 1.4.0.

Reasoning for RomVer over SemVer

The Masonite main repository (the MiraFramework/masonite repository) contains only the basic file structure of the application. All of the core framework functionality is inside the MasoniteFramework/core repository which can be updated as much as every day or once per month and therefore will follow normal SemVer. Because MiraFramework/masonite does not require major updates, we can follow RomVer nicely and keep the versioning number artificially lower. Any major updates to this repsository will likely just be file structure changes which should rarely happen unless there are major architectural changes.

Releases and Release Cycles

Masonite is currently on a 1 month major release cycle. This means that once every month will be a new 1.x release. This 1 month release cycle will continue until Masonite has reached a release that is stable enough to move far into the future with that releases architecture.

Once this stable release has been achieved, Masonite will move to a 6 month major release cycle with minor releases as often as every few days to as much as every few months.

Releases are planned to be upgradable from the previous release in 10 minutes or less. If they break that requirement then they should be considered for the next Paradigm release (the x.0.0 release)

Scheduled Releases

Creating Releases

Masonite is made up of three different repositories. There is

  • The main repository where development is done on the repo that installs on developer's systems.

  • The core repository which is where the main Masonite pip package is located.

  • The craft repository where the craft command tool is located.

Major 1 month releases will be released on or after the release date when all repositories are able to be released at the same time, as well as passing all tests.

Whenever the MasoniteFramework/craft and MasoniteFramework/core repositories are released on Github, Travis CI will run tests and automatically deploy to PyPi. These major version numbers should correspond to the version of Masonite they support. For example, if the MasoniteFramework/masonite releases to version 1.4, MasoniteFramework/core should bump up to 1.4.x regardless of changes.

Main Repository and New Projects

This is so developers and maintainers will be able to test the new pre release with their applications. Once all QA tests have passed, it will be marked as a normal release and therefore all new applications created from there on out will be the new release.

Developers will still have the option of doing something like: craft new project_name --version 1.5 and installing that version of Masonite.

Once all three repositories are ready for release, they will all be released on GitHub under the respective new version numbers.

The main repository which is MasoniteFramework/masonite does not have a corresponding PyPi package and is only for installing new Masonite projects. See the craft new command under documentation. The craft new command will download a zip of the latest release of Masonite, unzip it and rename the folder. Once the next release for this repository is ready, it will be released but marked as a Pre-release and therefore will not be installable by the default craft new command.

The Craft Command

Masonite 1.4 to 1.5

Introduction

Masonite 1.5 doesn't bring many file changes to Masonite so this upgrade is fairly straight forward and should take less than 10 minutes.

Requirements.txt

All requirements are now gone with the exception of the WSGI server (waitress) and the Masonite dependency. You should remove all dependencies and only put:

waitress==1.1.0
masonite>=1.5,<=1.5.99

Site Packages Configuration

If you have added your site packages directory to our packages configuration file, you can now remove this because Craft commands can now detect your site packages directory in your virtual environment.

Api Removal

Remove the masonite.providers.ApiProvider.ApiProvider from the PROVIDERS list as this has been removed completely in 1.5

You'll also have to add a new RESOURCES = [] line to your routes/api.py file for the new Masonite Entry package if you choose to use it.

Craft Commands

This release works with the new craft command release. Upgrade to version masonite-cli / 1.1+. <1.1 will only work with Masonite 1.4 and below.

Simply run:

$ pip install --upgrade masonite-cli

You may have to run sudo if you are using a UNIX machine.

Sessions

Masonite 1.5 now has sessions that can be used to hold temporary data. It comes with the cookie and memory drivers. Memory stores all data in a class which is lost when the server restarts and the cookie driver sets cookies in the browser.

There is a new config/session.py file you can copy and paste:

''' Session Settings '''

'''
|--------------------------------------------------------------------------
| Session Driver
|--------------------------------------------------------------------------
|
| Sessions are able to be linked to an individual user and carry data from
| request to request. The memory driver will store all the session data
| inside memory which will delete when the server stops running.
|
| Supported: 'memory', 'cookie'
| 
'''

DRIVER = 'memory'

As well as add the SessionProvider inside your PROVIDERS list just below the AppProvider:

PROVIDERS = [
    # Framework Providers
    'masonite.providers.AppProvider.AppProvider',

    # New Provider
    'masonite.providers.SessionProvider.SessionProvider',
    'masonite.providers.RouteProvider.RouteProvider',
    ....
]

Finished

That's it! You have officially upgrades to Masonite 1.5

Introduction and Installaton

This is an unreleased version. Scheduled Release June 15th 2018

Masonite is the rapid application Python development framework that strives for: beautiful and elegant syntax, actual batteries included with a lot of out of the box functionality, and extremely extendable. Masonite works hard to be fast and easy from install to deployment so developers can go from concept to creation in as quick and efficiently as possible. Try it once and you’ll fall in love.

Some Notable Features Shipped With Masonite

  • Easily send emails with the Mail Provider and the SMTP and Mailgun drivers.

  • Send websocket requests from your server with the Broadcast Provider and Pusher and Ably drivers.

  • IOC container and auto resolving dependency injection.

  • Service Providers to easily add functionality to the framework.

  • Extremely simple static files configured and ready to go.

  • Active Record style ORM called Orator.

  • An extremely useful command line tool called craft commands.

  • Extremely extendable.

These, among many other features, are all shipped out of the box and ready to go. Use what you need when you need it.

Requirements

In order to use Masonite, you’ll need:

  • Python 3.4+

  • Pip3

Linux

If you are running on a Linux flavor, you’ll need the Python dev package. You can download this package by running:

Or you may need to specify your python3.x-dev version:

Installation

Masonite works at being simple to install and get going. We use a simple command line that will become your best friend. You’ll never want to develop again without it. We call it the craft command line tool.

We can download our craft command line tool by just running:

You may have to use sudo if you are on a UNIX machine

All examples of pip in this documentation are based on pip3. If you see a pip command it is implied you are using a Python 3 pip command and not a Python 2 pip command

Great! We are now ready to create our first project. We should have the new craft command. We can check this by running:

This should show a list of command options. If it doesn't then try closing your terminal and reopening it or running it with sudo if you are on a UNIX machine. We are currently only interested in the craft new command. To create a new project just run:

This will get the latest Masonite project template and unzip it for you. We just need to go into our new project directory and install the dependencies in our requirements.txt file.

You can optionally create a virtual environment if you don't want to install all of masonite's dependencies on your systems Python. If you use virtual environments then create your virtual environment by running:

or if you are on Windows:

The pythoncommand here is utilizing Python 3. Your machine may run Python 2 (typically 2.7) by default. You may set an alias on your machine for Python 3 or simply run python3anytime you see the pythoncommand.

For example, you would run python3 -m venv venv instead of python -m venv venv

Now lets install our dependencies. We can do this simply by using a craft command:

This command is just a wrapper around the pipcommand with a few added Masonite specific install instructions. This installs all the required dependencies of Masonite, creates a .env file for us, generates a new secret key, and puts that secret key in our .env file.

Python 3.7

Two of the libraries that Masonite uses are currently not up to date with Python 3.7 installation. These libraries have old versions the .pyc files inside their distributions and need to be installed outside of the normal install workflow. Installing for Python 3.7 will be:

Running The Server

After it’s done we can just run the server by using another craft command:

You can also run the server in auto-reload mode which will rerun the server when file changes are detected:

Congratulations! You’ve setup your first Masonite project! Keep going to learn more about how to use Masonite to build your applications.

If you are using the Api() route inside routes/api.py for API endpoints then remove this as well. You will need to implement API endpoints using the new Official package instead.

You can learn more about craft by reading documentation or continue on to learning about how to create web application by first reading the documentation

Masonite Entry
$ sudo apt-get install python-dev
$ sudo apt-get install python3.6-dev
$ pip3 install masonite-cli
$ craft
$ craft new project_name
$ cd project_name
$ python -m venv venv
$ source venv/bin/activate
$ python -m venv venv
$ ./venv/Scripts/activate
$ craft install
$ pip install pycparser
$ pip install git+https://github.com/yaml/pyyaml.git
$ craft install
$ craft serve
$ craft serve -r
The Craft Command
Routing

Controllers

Introduction

Controllers are a vital part of Masonite and is mainly what differs it from other Python frameworks that implement the MVC structure differently. Controllers are simply classes with methods. These methods take a self parameter which is the normal self that Python class methods require. Controller methods can be looked at as function based views if you are coming from Django as they are simply methods inside a class and work in similar ways.

Controllers have an added benefit over straight function based views as the developer has access to to a full class they can manipulate however they want. In other words, controller methods may utilize class attributes or private methods to break up logic. They provide a lot of flexibility.

Creating a Controller

Its very easy to create a controller with Masonite with the help of our craft command tool. We can simply create a new file inside app/http/controllers, name the class the same name as the file and then create a class with methods. We can also use the craft controller command to do all of that for us which is:

terminal
$ craft controller Dashboard

When we run this command we now have a new class in app/http/controllers/DashboardController.py called DashboardController. By convention, Masonite expects that all controllers have their own file since it’s an extremely easy way to keep track of all your classes since the class name is the same name as the file but you can obviously name this class wherever you like.

Notice that we passed in Dashboard but created a DashboardController. Masonite will always assume you want to append Controller to the end.

Exact Controllers

Remember that Masonite will automatically append Controller to the end of all controllers. If you want to create the exact name of the controller then you can pass a -e or --exact flag.

terminal
$ craft controller Dashboard -e

or

terminal
$ craft controller Dashboard --exact

This will create a Dashboard controller located in app/http/controllers/Dashboard.py

Resource Controllers

Resource controllers are controllers that have basic CRUD / resource style methods to them such as create, update, show, store etc. We can create a resource controller by running:

$ craft controller Dashboard -r

or

$ craft controller Dashboard --resource

this will create a controller that looks like:

app/http/controllers/DashboardController.py
''' A Module Description '''

class DashboardController: 
 ''' Class Docstring Description '''
 
    def show(self): 
        pass
        
    def index(self): 
        pass
        
    def create(self): 
        pass
        
    def store(self): 
        pass
        
    def edit(self): 
        pass
        
    def update(self): 
        pass
    
    def destroy(self): 
        pass

Defining a Controller Method

Controller methods are very similar to function based views in a Django application except this is just a normal class method. Our controller methods at a minimum should look like:

app/http/controllers/DashboardController.py
def show(self):
    pass

All controller methods must have the self parameter. The self parameter is the normal python self object which is just an instance of the current class as usual. Nothing special here.

All controller methods are resolved by the container so you may also retrieve additional objects from the container by specifying them as a parameter:

app/http/controllers/DashboardController.py
def show(self, Request):
    print(Request) # Grabbed the Request object from the container

It’s important to note that unlike other frameworks, we do not have to specify our route parameters as parameters in our controller method. We can retrieve the parameters using the Request.param('key') class method.

This might look magical to you so be sure the read about the IOC container in the documentation.

Read about how to create and use views by reading the documentation

Service Container
Views

Masonite 1.3 to 1.4

Introduction

Masonite 1.4 brings several new features and a few new files. This is a very simple upgrade and most of the changes were done in the pip package of Masonite. The upgrade from 1.3 to 1.4 should take less than 10 minutes

Requirements.txt File

This requirement file has the masonite>=1.3,<=1.3.99 requirement. This should be changed to masonite>=1.4,<=1.4.99. You should also run pip install --upgrade -r requirements.txt to upgrade the Masonite pip package.

New Cache Folder

There is now a new cache folder under bootstrap/cache which will be used to store any cached files you use with the caching feature. Simply create a new bootstrap/cache folder and optionally put a .gitignore file in it so your source control will pick it up.

New Cache and Broadcast Configuration

3 New Service Providers

Masonite comes with a lot of out of the box functionality and nearly all of it is optional but Masonite 1.4 ships with three new providers. Most Service Providers are not ran on every request and therefore does not add significant overhead to each request. To add these 3 new Service Providers simple add these to the bottom of the list of framework providers:

PROVIDERS = [
    # Framework Providers
    ...
    'masonite.providers.HelpersProvider.HelpersProvider',
    'masonite.providers.QueueProvider.QueueProvider',

    # 3 New Providers in Masonite 1.4
    'masonite.providers.BroadcastProvider.BroadcastProvider',
    'masonite.providers.CacheProvider.CacheProvider',
    'masonite.providers.CsrfProvider.CsrfProvider',

    # Third Party Providers

    # Application Providers
    'app.providers.UserModelProvider.UserModelProvider',
    'app.providers.MiddlewareProvider.MiddlewareProvider',
]

Note however that if you add the CsrfProvider then you will also need the CSRF middleware which is new in Masonite 1.4. Read the section below to add the middleware

CSRF and CSRF Middleware

Masonite 1.4 adds CSRF protection. So anywhere there is any POST form request, you will need to add the {{ csrf_field|safe }} to it. For example:

<form action="/dashboard" method="POST">
    {{ csrf_field|safe }}
    <input type="text" name="first_name">
</form>

Lastly, put that middleware into the HTTP_MIDDLEWARE list inside config/middleware.py like so:

HTTP_MIDDLEWARE = [
    'app.http.middleware.LoadUserMiddleware.LoadUserMiddleware',
    'app.http.middleware.CsrfMiddleware.CsrfMiddleware',
]

Changes to Database Configuration

from config import database

database.db.table(...)

should now be:

from config import database

database.DB.table(...)

with this change

Masonite 1.4 brings a new config/cache.py and config/broadcast.py files. These files can be found on the page and can be copied and pasted into your project. Take a look at the new file and the file. Just copy and paste those configuration files into your project.

This type of protection prevents cross site forgery. In order to activate this feature, we also need to add the . Copy and paste the middleware into your project under the app/http/middleware/CsrfMiddleware.py file.

There has been a slight change in the constants used in the file. Mainly just for consistency and coding standards. Your file may have some slight changes but this change is optional. If you do make this change, be sure to change any places in your code where you have used the Orator Query Builder. For example any place you may have:

GitHub
config/cache.py
config/broadcast.py
CSRF middleware
config/database.py

Helper Functions

Introduction

Masonite works on getting rid of all those mundane tasks that developers either dread writing or dread writing over and over again. Because of this, Masonite has several helper functions that allows you to quickly write the code you want to write without worrying about imports or retrieving things from the Service Container. Many things inside the Service Container are simply retrieved using several functions that Masonite sets as builtin functions.

These functions do not require any imports and are simply just available which is similiar to the print() function. These functions are all set inside the HelpersProvider Service Provider.

It may make more sense if we take a peak at this Service Provider:

masonite.providers.HelpersProvider
class HelpersProvider(ServiceProvider):

    wsgi = False

    def register(self):
        pass

    def boot(self, View, ViewClass, Request):
        ''' Add helper functions to Masonite '''
        builtins.view = View
        builtins.request = Request.helper
        builtins.auth = Request.user
        builtins.container = self.app.helper
        builtins.env = os.getenv
        builtins.resolve = self.app.resolve

        ViewClass.share({'request': Request.helper, 'auth': Request.user})

Notice how we simply just add builtin functions via this provider.

Request

The Request class has a simple request() helper function.

def show(self):
    request().input('id')

is exactly the same as:

def show(self, Request):
    Request.input('id')

Notice we didn't import anything at the top of the file, nor did we inject anything from the Service Container.

View

The view() function is just a shortcut to the View class.

def show(self):
    return view('template_name')

is exactly the same as:

def show(self, View):
    return View('template_name')

Auth

The auth() function is a shortcut around getting the current user. We can retrieve the user like so:

def show(self):
    auth().id

is exactly the same as:

def show(self, Request):
    Request.user().id

This will return None if there is no user so in a real world application this may look something like:

def show(self):
    if auth():
        auth().id

This is because you can't call the .id attribute on None

Container

We can get the container by using the container() function

def show(self):
    container().make('User')

is exactly the same as:

def show(self, Request):
    Request.app().make('User')

Env

We may need to get some environment variables inside our controller or other parts of our application. For this we can use the env() function.

def show(self):
    env('S3_SECRET')

is exactly the same as:

import os

def show(self):
    os.environ.get('S3_SECRET')

Resolve

We can resolve anything from the container by using this resolve() function.

def some_function(Request):
    print(Request)

def show(self):
    resolve(some_function)

is exactly the same as:

def some_function(Request):
    print(Request)

def show(self, Request):
    Request.app().resolve(some_function)

That's it! These are simply just functions that are added to Python's builtin functions.

Views

Introduction

Views contain all the HTML that you’re application will use to render to the user. Unlike Django, views in Masonite are your HTML templates. All views are located inside resources/templatesdirectory.

All views are rendered with Jinja2 so we can use all the Jinja2 code you are used to. An example view looks like:

resources/templates/helloworld.html
<html>
  <body>
    <h1> Hello {{ 'world' }}</h1>
  </body>
</html>

Creating Views

Since all views are located in resources/templates, we can use simply create all of our views manually here or use our craft command tool. To make a view just run:

terminal
$ craft view hello

This will create a template under resources/templates/hello.html.

Calling Views

Helper Function

One of the helper functions is the view() function which is accessible like any other built in Python function.

We can call views in our controllers like so:

app/http/controllers/YourController.py
def show(self):
    return view('dashboard')

This will return the view located at resources/templates/dashboard.html. We can also specify a deeper folder structure like so:

app/http/controllers/YourController.py
def show(self):
    return view('profiles/dashboard')

This will look for the view at resources/templates/profiles/dashboard.html

From The Container

The View class is loaded into the container so we can retrieve it in our controller methods like so:

app/http/controllers/YourController.py
def show(self, View):
    return View('dashboard')

This is exactly the same as using the helper function above. So if you choose to code more explicitly, the option is there for you.

Global Views

Some views may not reside in the resources/templates directory and may even reside in third party packages such as a dashboard package. We can locate these views by passing a / in front of our view.

For example as a use case we might pip install a package:

terminal
$ pip install package-dashboard

and then be directed or required to return one of their views:

app/http/controllers/YourController.py
def show(self):
    return view('/package/views/dashboard')

This will look inside the dashboard.views package for a dashboard.html file and return that. You can obviously pass in data as usual.

Global View Caveats

Template Location

It's important to note that if you are building a third party package that integrates with Masonite that you place any .html files inside a Python package instead of directly inside a module. For example, you should place .html files inside a file structure that looks like:

package/
  views/
    __init__.py
    index.html
    dashboard.html
setup.py
MANIFEST.in
...

and not inside the package directory. This is a Jinja limitation that says that all templates should be located in packages.

Accessing a global view such as:

app/http/controllers/YourController.py
def show(self):
    return view('/package/dashboard')

will perform a relative import for your Masonite project. For example it will catch:

app/
config/
databases/
...
package/
  dashboard.html

So if you are making a package for Masonite then keep this in mind in where you should put your templates

Extending Views

When you extend a view, you are isolated to the directory you are in when you want to extend templates and you're view namespace loses it's globalization and all extending should be done relative to the current template. For example, if you have a package with a directory structure like:

package/
  controllers/
    PackageController.py
  templates/
    __init__.py
    index.html # needs to inherit base.html
    base.html
setup.py
MANIFEST.in
...

And a controller like:

package/controllers/PackageController.py
def show(self):
    return view('/package/templates/index')

You will have to extend your template like so:

package/templates/index.html
<!-- This is right -->
{% extends 'base.html' %}

{% block content %}
    ... index.html code goes here ...
{% endblock %}

and not extend your template with another global view:

package/templates/index.html
<!-- This is wrong -->
{% extends '/package/templates/base.html' %}

{% block content %}
    ... index.html code goes here ...
{% endblock %}

Passing Data to Views

A lot of the time we’ll need to pass in data to our views. This data is passed in with a dictionary that contains a key which is the variable with the corresponding value. We can pass data to the function like so:

app/http/controllers/YourController.py
def show(self, Request):
    return view('dashboard', {'id': Request.param('id')})

This will send a variable named id to the view which can then be rendered like:

resources/templates/dashboard.html
<html>
  <body>
    <h1> {{ id }} </h1>
  </body>
</html>

There are several ways we can call views in our controllers. The first recommended way is using the view() function. Masonite ships with a HelpersProvider Service Provider. This provider will add several new built in functions to your project. These helper functions can be used as shorthand for several commonly used classes such as the View and Request class. See the documentation for more information.

If this looks weird to you or you are not sure how the container integrates with Masonite, make sure you read the documentation

Remember that by passing in parameters like Request to the controller method, we can retrieve objects from the IOC container. Read more about the IOC container in the documentation.

Helper Functions
Service Container
Service Container

Routing

Introduction

Masonite Routing is an extremely simple but powerful routing system that at a minimum takes a url and a controller. Masonite will take this route and match it against the requested route and execute the controller on a match.

All routes are created inside routes/web.py and are contained in a ROUTES constant. All routes consist of either a Get() route or a Post() route. At the bare minimum, a route will look like:

routes/web.py
Get().route('/url/here', 'WelcomeController@show')

Most of your routes will consist of a structure like this. All URI’s should have a preceding /. Routes that should only be executed on Post requests (like a form submission) will look very similar:

routes/web.py
Post().route('/url/here', 'WelcomeController@store')

Notice the controller here is a string. This is a great way to specify controllers as you do not have to import anything into your web.py file. All imports will be done in the backend. More on controllers later.

If you wish to not use string controllers and wish to instead import your controller then you can do so by specifying the controller as well as well as only passing a reference to the method. This will look like:

routes/web.py
...
from app.http.controllers.DashboardController import DashboardController


ROUTES = [
    Get().route('/url/here', DashboardController.show)
]

It’s important here to recognize that we didn't initialize the controller or the method, we did not actually call the method. This is so Masonite can pass parameters into the constructor and method when it executes the route, typically through auto resolving dependency injection.

Route Options

There are a few methods you can use to enhance your routes. Masonite typically uses a setters approach to building instead of a parameter approach so to add functionality, we can simply attach more methods.

HTTP Verbs

There are several HTTP verbs you can use for routes:

routes/web.py
from masonite.routes import Get, Post, Put, Patch, Delete

Get().route(...)
Post().route(...)
Put().route(...)
Patch().route(...)
Delete().route(...)

HTTP Helpers

If the syntax is a bit cumbersome, you just want to make it shorter or you like using shorthand helper functions, then you can also use these:

routes/web.py
from masonite.helpers.routes import get, post, put, patch, delete

ROUTES = [
    get('/url/here', 'Controller@method'),
    post('/url/here', 'Controller@method'),
    put('/url/here', 'Controller@method'),
    patch('/url/here', 'Controller@method'),
    delete('/url/here', 'Controller@method'),
]

These return instances of their respective classes so you can append on to them:

routes/web.py
get('/url/here', 'Controller@method').middleware(...),

Most developers choose to use these instead of the classes.

Route Groups

Sometimes routes can be very similiar such as having many dashboard or profile routes:

ROUTES = [
    Get().route('/home', ...),
    Get().route('/dashboard', ...),
    Get().route('/dashboard/user', ...),
    Get().route('/dashboard/user/@id', ...),
    Get().route('/dashboard/friends', ...),
    ...
]

These routes can be grouped using the group helper:

from masonite.helpers.routes import group

ROUTES = [
    Get().route('/home', 'DashboardController@show'),
    group('/dashboard', [
        Get().route('/user', ...)
        Get().route('/user/@id', ...)
        Get().route('/user/friends', ...)
    ])
]

Notice that this is the same as above and can help organize and group routes. This feature will also be expanded on in future releases of Masonite.

Named Routes

We can name our routes so we can utilize these names later when or if we choose to redirect to them. We can specify a route name like so:

routes/web.py
Get().route('/dashboard', 'DashboardController@show').name('dashboard')

It is good convention to name your routes since route URI's can change but the name should always stay the same.

Route Middleware

Middleware is a great way to execute classes, tasks or actions either before or after requests. We can specify middleware specific to a route after we have registered it in our config/middleware.py file but we can go more in detail in the middleware documentation. To add route middleware we can use the middleware method like so:

routes/web.py
Get().route('/dashboard', 'DashboardController@show').middleware('auth', 'anothermiddleware')

This middleware will execute either before or after the route is executed depending on the middleware.

Deeper Module Controllers

All controllers are located in app/http/controllers but sometimes you may wish to put your controllers in different modules deeper inside the controllers directory. For example, you may wish to put all your product controllers in app/http/controllers/products or all of your dashboard controllers in app/http/controllers/users. In order to access these controllers in your routes we can simply specify the controller using our usual dot notation:

routes/web.py
Get().route('/dashboard', 'users.DashboardController@show')

Global Controllers

Controllers are defaulted to the app/http/controllers directory but you may wish to completely change the directory for a certain route. We can use a forward slash in the beginning of the controller namespace:

Get().route('/dashboard', '/thirdparty.package.users.DashboardController@show')

This can enable us to use controllers in third party packages.

Route Parameters

Very often you’ll need to specify parameters in your route in order to retrieve information from your URI. These parameters could be an id for the use in retrieving a certain model. Specifying route parameters in Masonite is very easy and simply looks like:

routes/web.py
Get().route('/dashboard/@id', 'Controller@show')

That’s it. This will create a dictionary inside the Request object which can be found inside our controllers.

In order to retrieve our parameters from the request we can use the param method on the Request object like so:

app/http/controller/YourController.py
def show(self, Request):
    Request.param('id')

Route Parameter Options

Sometimes you will want to make sure that the route parameter is of a certain type. For example you may want to match a URI like /dashboard/1 but not /dashboard/joseph. In order to do this we simply need to pass a type to our parameter. If we do not specify a type then our parameter will default to matching all alphanumeric and underscore characters.

routes/web.py
Get().route('/dashboard/@id:int', 'Controller@show')

This will match all integers but not strings. So for example it will match /dashboard/10283 and not /dashboard/joseph

If we want to match all strings but not integers we can pass:

routes/web.py
Get().route('/dashboard/@id:string', 'Controller@show')

This will match /dashboard/joseph and not /dashboard/128372. Currently only the integer and string types are supported.

Subdomain Routing

You may wish to only render routes if they are on a specific subdomain. For example you may want example.com/dashboard to route to a different controller than joseph.example.com/dashboard. To do this we can use the .domain() method on our routes like so:

routes/web.py
Get().domain('joseph').route('/dashboard', 'Controller@show')

This route will match to joseph.example.com/dashboard but not to example.com/dashboard or test.example.com/dashboard.

It may be much more common to match to any subdomain. For this we can pass in an asterisk instead.

routes/web.py
Get().domain('*').route('/dashboard', 'Controller@show')

This will match all subdomains such as test.example.com/dashboard, joseph.example.com/dashboard but not example.com/dashboard.

If a match is found, it will also add a subdomain parameter to the Request class. We can retrieve the current subdomain like so:

app/http/controllers/YourController.py
def show(self, Request):
    print(Request.param('subdomain'))

Request Lifecycle

Introduction

Lifecycle Overview

With that being said, not all Service Providers need to be ran on every request and there are good times to load objects into the container. For example, loading routes into the container does not need to be ran on every request. Mainly because they won't change before the server is restarted again.

Now the entry point when the server is first ran (with something like craft serve) is the wsgi.py file in the root of the directory. In this directory, all Service Providers are registered. This means that objects are loaded into the container first that typically need to be used by any or all Service Providers later. All Service Providers are registered regardless on whether they require the server to be running (more on this later).

Now it's important to note that the server is not yet running we are still in the wsgi.py file but we have only hit this file, created the container, and registered our Service Providers. Right after we register all of our Service Providers, we run the boot methods of all all Service Providers that have the wsgi=False attribute. This signifies to Masonite that these boot methods need to be ran before the WSGI server starts. These boot methods may contain things like Manager classes creating drivers which require all drivers to be registered first but doesn't require the WSGI server to be running.

Also, more importantly, the WSGI key is binded into the container at this time. The default behavior is to wrap the WSGI application in a Whitenoise container to assist in the straight forwardness of static files.

This behavior can be changed by swapping that Service Provider with a different one.

Once all the register methods are ran and all the boot methods of Service Providers where wsgi is false, and we have a WSGI key in the container, we can startup the server by using the value of the WSGI key.

We then make an instance of the WSGI key from the container and set it to an application variable in order to better show what is happening. Then this is where the WSGI server is started.

The Server

Now that we have the server running, we have a new entry point for our requests. This entry point is the app function inside bootstrap/start.py.

Now all wsgi servers set a variable called environ. In order for our Service Providers to handle this, we bind it into the container to the Environ key.

Next we run all of our Service Providers where wsgi is true now (because the WSGI server is running).

WSGI Service Providers

The Request Life Cycle is now going to hit all of these providers. Although you can obviously add any Service Providers you at any point in the request, Masonite comes with 5 providers that should remain in the order they are in. These providers have been commented as # Framework Providers. Because the request needs to hit each of these in succession, they should be in order although you may put any amount of any kind of Service Providers in between them.

We enter into a loop with these 5 Service Providers and they are the:

App Provider

This Service Provider registered objects like the routes, request class, response, status codes etc into the container and then loads the environment into these classes on every request so that they can change with the environment. Remember that we registered these classes when the server first started booting up so they remain the same class object as essentially act as singletons Although they aren't being reinstantiated with the already existing object, they are instantiated once and die when the server is killed.

Session Provider

If one of the middleware has instructed the request object to redirect, the view that is ready to execute, will not execute.

For example, if the user is planned on going to the dashboard view, but middleware has told the request to redirect to the login page instead then the dashboard view, and therefore the controller, will not execute at all. It will be skipped over. Masonite checks if the request is redirecting before executing a view.

Also, the request object can be injected into middleware by passing the Request parameter into the constructor like so:

This provider loads the ability to use sessions, adds a session helper to all views and even attaches a session attribute to the request class.

Route Provider

This provider takes the routes that are loaded in and makes the response object, static codes, runs middleware and other route displaying specific logic. This is the largest Service Provider with the most logic. This provider also searchest hrough the routes, finds which one to hit and exectues the controller and controller method.

Redirection Provider

This provider checks for any redirections and sets the responses accordingly. For example we may be redirecting to a named route. In that case we need to run back through the routes and get the correct URL.

Start Response

This Service Provider collects a few classes that have been manipulated by the above Service Providers and constructs a few headers such as the content type, status code, setting cookies and location if redirecting.

Leaving the Providers

Once these 5 providers have been hit (and any you add), we have enough information to show the output. We leave the Service Provider loop and set the response and output which are specific to WSGI. The output is then sent to the browser with any cookies to set, any new headers, the response, status code, and everything else you need to display html (or json) to the browser.

Creating Commands

Introduction

It's extremely simple to add commands to Masonite via the craft command tool and Service Providers. If you have been using Masonite for any amount of time you will learn that commands are a huge part of developing web applications with Masonite. We have made it extremely easy to create these commands and add them to craft to build really fast personal commands that you might use often.

Masonite uses the Cleo package for creating and consuming commands so for more extensive documentation on how to utilize commands themselves, how to get arguments and options, and how to print colorful text to the command line.

Getting Started

You can create commands by using craft itself:

This will create a app/commands/HelloCommand.py file with boiler plate code that looks like this:

Let's create a simple hello name application which prints "hello your-name" to the console.

Where it says command:name inside the docstring we can put hello and inside the argument we can put name like so:

Inside the handle method we can get the argument passed by specifying self.argument('name'). Simply put:

That's it! Now we just have to add it to our craft command.

Adding Our Command To Craft

We can add commands to craft by creating a Service Provider and registering our command into the container. Craft will automatically run all the register methods on all containers and retrieve all the commands.

Let's create a Service Provider:

This will create a provider in app/providers/HelloProvider.py that looks like:

Let's import our command and register it into the container. Also because we are only registering things into the container, we can set wsgi = False so it is not ran on every request and only before the server starts:

Make sure you instantiate the command. Also the command name needs to end in "Command". So binding HelloCommand will work but binding Hello will not. Craft will only pick up commands that end in Command. This is also case sensitive so make sure Command is capitalized.

Adding The Service Provider

Like normal, we need to add our Service Provider to the PROVIDERS list inside our config/application.py file:

That's it! Now if we run:

We will see our new hello command:

and if we run:

We will see an output of:

How To Contribute

Introduction

There are plenty of ways to contribute to open source. Many of which don't even rely on writing code. A great open source project should have excellent documentation and have as little bugs as possible. Below I will explain how to contribute to this project in different ways.

This is not an exhaustive list and not the only ways to contribute but they are the most common. If you know of other ways to contribute then please let us know.

Contributing to Development

Become a Feature Maintainer

Feature maintainers must already have significant contributions to development of the repository they are trying to be a Feature Maintainer for. Although they do not have to be contributors to the actual feature they plan to maintain.

Comment the Code

If you don't want to touch the code and instead want to just look at it and figure it out, contribute some comments! Comments are an excellent way for future developers to read and understand the framework. Masonite strives on being extremely commented. Although most of the code itself does not need to be commented, some of the classes, modules, methods and functions do (although a lot of them already are).

Comments don't affect the working code so if you want to get used to contributing to open source or you just don't quite understand what a class method is doing or you are afraid of contributing and breaking the project (there are tests) then contributing comments is right for you!

Write Tests

Contribute to Tutorials

Plus there will be fantastic tutorials out there for beginners to find and watch and you could also build a following off the back of Masonite.

Fix the Documentation

Report Bugs

Squash Bugs

Build a Community

If you have a large following on any social media or no following at all, you can contribute by trying to build up a following around Masonite. Any open source project requires an amazing community around the framework. You can either build up a community personally and be the leader of that community or you can simply send them to Masonite's GitHub repository where we can build up a community around there.

Build Community Software Around Masonite

Answer Questions from the Community

Questions will come in eventually either through the GitHub issues or through websites like StackOverflow. You could make it a priority to be the first to answer these peoples questions or if you don't know the answer you can redirect one of the core maintainers or contributors to the question so we can answer it further.

Review Code on Pull Requests

Discuss Issues

Every now and then will be a requires discussion label on an issue or pull request. If you see this label then be sure to add your thoughts on an issue. All issues are open for discussion and Masonite strives off of developer input so feel free to enter a discussion.

Read more about how to use and create middleware in the documentation.

It's important to know the life cycle of the request so you can understand what is going on under the hood in order to write better software. Whenever you have a better understanding of how your development tools work, you will feel more confident as you'll understand what exactly is going on. This documentation will try to explain in as much detail as needed the flow of the request from initiation to execution. To read about how to use the Request class, read the documentation.

Masonite is bootstrapped via , these providers load features, hooks, drivers and other objects into the which can then be pulled out by you, the developer, and used in your views, middleware and drivers.

This will inject the Request class into the constructor when that middleware is executed. Read more about how middleware works in the documentation.

Read more about Cleo by visiting the .

Of course the project requires contributions to the main development aspects but it's not the only way. But if you would like to contribute to development then a great way to get started is to simply read through this documentation. Get acquainted with how the framework works, how and work and read the documentation starting with the , then the and finally the .

It would also be good to read about the to get familiar with how Masonite does releases (RomVer over SemVer).

Feature Maintainers are people who are in charge of specific features (such as or ). These developers will be in charge of reviewing PR's and merging them into the development branch and also have direct contact with the repository owner to discuss.

The require testing (The main repository does not). If you want to search through all the tests in the tests directories of those repositories and write additional tests and use cases then that will be great! There are already over 100 tests but you can always write more. With more testing comes more stability. Especially as people start to contribute to the project. Check the tests that are already there and write any use cases that are missing. These tests can be things such as special characters in a url or other oddities that may not have been thought of when using TDD for that feature.

Once familiar with the project (by either contributing or by building application using the framework) it would be excellent if you could write or record tutorials and put them on or . In order for the framework to be successful, it needs to have a plethora of documentation even outside of this documentation. It needs to have notoriety and if people are seeing the framework pop up in their favorite locations they will be more inclined to use the framework and contribute to it as well.

This documentation is fantastic but there are spots where it could be improved. Maybe we haven't explained something fully or something just doesn't make sense to you. Masonite uses to host it's documentation and with that you are able to comment directly on the documentation which will start a discussion between you and the documentation collaborators. So if you want to cycle through the documentation page by page and get acquainted with the framework but at the same time contribute to the documentation, this is perfect for you.

If you just don't want to contribute code to the main project you may instead simply report bugs or improvements. You can go ahead and build any of your applications as usual and report any bugs you encounter to the issues page.

Look at the issues page on for any issues, bugs or enhancements that you are willing to fix. If you don't know how to work on them, just comment on the issue and Joseph Mancuso or other core contributors will be more than happy explaining step by step on how you can go about fixing or developing that issue.

Another idea is to use Masonite to build applications such as a screencast website like or an official Masonite website or even a social network around Masonite. Every great framework needs it's "ecosystem" so you may be apart of that buy building these applications with the Masonite branding and logos. Although copying the branding requires an OK from Joseph Mancuso, as long as the website was built with Masonite and looks clean it shouldn't be a problem at all.

Most pull requests will sit inside GitHub for a few days while it gets quality tested. The main develop branch pull requests could sit there for as long as 6 months and will only be merged in on releases. With that being said, you can look at the file changes of these pull requests and ensure they meet the community guidelines, the API is similar to other aspects of the project and that they are being respectful and following pull requests rules in accordance with the documentation.

Middleware
class SomeMiddleware:

    def __init__(self, Request):
        self.request = Request

    ...
terminal
$ craft command HelloCommand
app/commands/HelloCommand.py
""" A HelloCommand Command """
from cleo import Command


class HelloCommand(Command):
    """
    Description of command

    command:name
        {argument : description}
    """

    def handle(self):
        pass
app/commands/HelloCommand.py
""" A HelloCommand Command """
from cleo import Command


class HelloCommand(Command):
    """
    Say hello to you

    hello
        {name : Your name}
    """

    def handle(self):
        pass
app/commands/HelloCommand.py
""" A HelloCommand Command """
from cleo import Command


class HelloCommand(Command):
    """
    Say hello to you

    hello
        {name : Your name}
    """

    def handle(self):
        print('Hello {0}'.format(self.argument('name')))
terminal
$ craft provider HelloProvider
app/providers/HelloProvider.py
''' A HelloProvider Service Provider '''
from masonite.provider import ServiceProvider


class HelloProvider(ServiceProvider):

    def register(self):
        pass

    def boot(self):
        pass
app/providers/HelloProvider.py
''' A HelloProvider Service Provider '''
from masonite.provider import ServiceProvider
from app.commands.HelloCommand import HelloCommand

class HelloProvider(ServiceProvider):

    wsgi = False

    def register(self):
        self.app.bind('HelloCommand', HelloCommand())

    def boot(self):
        pass
config/application.py
PROVIDERS = [
...
    # Application Providers
    'app.providers.UserModelProvider.UserModelProvider',
    'app.providers.MiddlewareProvider.MiddlewareProvider',

    # New Hello Provider
    'app.providers.HelloProvider.HelloProvider',
]
terminal
$ craft
terminal
  help              Displays help for a command
  hello             Say hello to you
  install           Installs all of Masonite's dependencies
terminal
$ craft hello Joseph
terminal
Hello Joseph

Static Files

Introduction

Masonite tries to make static files extremely easy and comes with whitenoise out of the box. White noise wraps the WSGI application and listens for certain URI requests that can be resistered in your configuration files.

Configuration

All configurations that are specific to static files can be found in config/storage.py. In this file you'll find a constant file called STATICFILES which is simply a dictionary of directories as keys and aliases as the value.

The directories to include as keys is simply the location of your static file locations. For example, if your css files are in storage/assets/css then put that folder location as the key. For the value, put the alias you want to use in your templates. For this example, we will use css/ as the alias.

For this setup, our STATICFILES constant should look like:

STATICFILES = {
    'storage/assets/css': 'css/',
}

Now in our templates we can use:

<img src="/css/style.css">

Which will get the storage/assets/css/style.css file.

Thats it! Static files are extremely simple. You are now a master at static files!

Validation

Introduction

Very often you will find a need to validate forms after you have submitted them. Masonite comes with a very simple and reusable way to validate input data with the Masonite Validator() class. In this documentation, we'll talk about how you can create your own validator to use within your project. Masonite uses the validator.py library for this feature.

Getting Started

The best way to create a reusable validator class within your Masonite project is to create a class and inherit from the Validator class inside the masonite.validator module.

We can make a class called RegistrationValidator(), inherit from masonite.validator.Validator() and put it inside app/validators/RegistrationValidator.py like so:

from masonite.validator import Validator

class RegistrationValidator(Validator):

    def register_form(self):
        pass

Awesome! By inheriting from Validator, this will add several key methods to our validator class that we'll need to verify our request input which we'll talk about below.

Starting Our Validations

Since we inherited from Validator, we have access to a validate() method which we can call with self.validate(). Inside this method call, we can use some useful attributes in order to check our request data.

An example of a simple validation will look something like:

from masonite.validator import Validator
from validate import Required, Pattern, Truthy

class RegistrationValidator(Validator):

    def register_form(self):
        self.validate(
            'username': [Required, Pattern('[a-zA-Z0-9]')]
            'is_staff': [Required, Truth()],
            'password': [Required]
        )

Custom Error Messages

By default, Masonite will supply you with a dictionary of errors depending on the validator class. More information on what the default error messages are found below.

Although this is very convenient, you may wish to specify your own error messages. To do so, we can call the self.messages() method after we validate. This will look like:

from masonite.validator import Validator
from validate import Required, Pattern, Truthy

class RegistrationValidator(Validator):

    def register_form(self):
        self.validate({
            'username': [Required, Pattern('[a-zA-Z0-9]')]
            'is_staff': [Required, Truth()],
            'password': [Required]
        })

        self.messages({
            'username': 'The username is required!',
            'password': 'Make this password top secret!'
        })

This will change the default error messages to the defaults provided. If you do not specify a custom error message for a validation field then the default one will display as usual.

Requests

In order to check the input data we receive from a request, such as a form submission, we can use this validator like so:

from app.validators.RegistrationValidator import RegistrationValidator

def show(self, Request):
    validate = RegistrationValidator(Request)
    validate.register_form()
    validate.check() # returns True or False
    validate.errors() # returns a dictionary of errors if any

Notice that we pass in the request inside the constructor of our RequestValidator class. Our class is using the constructor inherited from Masonite's Validator class.

Non Requests

Sometimes we may wish to use our validator class without request data. In order to do this we can just pass a dictionary of values into our .check() method like so:

from app.validators.RegistrationValidator import RegistrationValidator

def show(self):
    validate = RegistrationValidator()
    validate.register_form()
    validate.check({'username': 'John'}) # returns True or False
    validate.errors() # returns a dictionary of errors if any

Notice how we passed a dictionary into our .check() method here and didn't pass the request object in the constructor.

Validator Options

There are a plethora of options that you can use to validate your forms. In addition to validating your request input, we also get a dictionary of errors. In order to get the errors if a validation fails, we can get use the method:

validate.errors() # {'username': 'must be present'}

This method will return a dictionary of errors that will be different depending on the validation class used but this method will return None if there are no errors. Below each option will be what the value of .errors() will be as well as how you would use them inside Masonite.

Required

By default, all keys registered for validation are optional. Any key that doesn't exist in the validation will skip any of the missing input data. For example, if a validation is not set for password then it will simply not check any validation on that specific request input. In this case, we can leave our password validation out entirely.

Unlike other validator classes, this class does not need to be instantiated (contain parenthesis at the end). So Required is the correct usage and not Required().

Usage

from validate import Required

self.validate({
    'username': [Required]
})

Error

{"username": ["must be present"]}

Truthy

The Truthy() validator class will check whatever is truthy to Python. This includes True, non-0 integers, non-empty lists, and strings

Usage

from validate import Truthy

self.validate({
    'username': [Truthy()]
})

Error

{"username": ["must be True-equivalent value"]}

Equals

This validator will check that a value is equal to the value given.

Usage

from validate import Equals

self.validate({
    'username': [Equals('Joseph')]
})

Error

{"username": ["must be equal to 'Joseph'"]}

Note that all request input data will be a string. so make sure you use Equals('1') and not Equals(1). Just be sure to maintain the data type in your validation.

Range

This validator checks that the dictionary value falls inclusively between the start and end values passed to it.

from validate import Range

self.validate({
    'age': [Range(1, 100)]
})

Error

{"age": ["must fall between 1 and 100"]}

Pattern

The Pattern validator checks that the dictionary value matches the regex pattern that was passed to it.

Usage

from validate import Pattern

self.validate({
    'age': [Pattern('\d+')]
})

Error

{"age": ["must match regex pattern \d+"]}

### In
****

This validator checks that the dictionary value is a member of a collection passed to it.


#### Usage

```python
from validate import In

self.validate({
    'username': [In(['Joseph', 'Michael', 'John'])]
})

Since Orator returns a collection, we can specify an Orator Collection as well:

from validate import In
from config import database
users = db.table('users').select('name').get()

self.validate({
    'username': [In([users])]
})

Error

{"age": ["must be one of <collection here>"]}

Not

This validator negates a validator that is passed to it and checks the dictionary value against that negated validator.

Usage

from validate import Not
from config import database
users = db.table('users').select('name').get()

self.validate({
    'age': [Not(In(users))]
})

Error

{"age": ["must be one of <collection here>"]}

InstanceOf

This validator checks that the dictionary value is an instance of the base class passed to it, or an instance of one of its subclasses.

Usage

from validate import InstanceOf

self.validate({
    'age': [InstanceOf(basestring)]
})

Error

{"age": ["must be an instance of basestring or its subclasses"]}

SubclassOf

This validator checks that the dictionary value inherits from the base class passed to it. To be clear, this means that the dictionary value is expected to be a class, not an instance of a class.

Usage

from validate import SubclassOf

self.validate({
    'age': [SubclassOf(str)]
})

Error

{"age": ["must be a subclass of str"]}

Length

This validator checks that value the must have at least minimum elements and optionally at most maximum elements.

Usage

from validate import Length

self.validate({
    'age': [Length(0, maximum=5)]
})

Error

{"age": ["must be at most 5 elements in length"]}

Extending Classes

Introduction

It's common to want to use a Service Provider to add new methods to a class. For example, you may want to add a is_authenticated method to the Request class. Your package and Service Provider may be for a better authentication system.

You may easily extend classes that inherit from the Extendable class. Many of the built in classes inherit from it.

Usage

You have a few options for adding methods to any of the core classes. You can extend a class with functions, classes and class methods. Typical usage may look like:

def is_authenticated(self):
    return self

def show(self, Request):

    Request.extend(is_authenticated)

    print(Request.is_authenticated()) # returns the Request class

Usage is very simple and has several options for extending a class. Notice that we don't call the function but we pass the reference to it.

Extending a function

This will simply add the function as a bound method to the Request class

def is_authenticated(self):
    return self

def show(self, Request):

    Request.extend(is_authenticated)

    print(Request.is_authenticated()) # returns the Request class

Extending a class method

We can also extend a class method which will take the method given and add it as a bound method.

class Authentication:

    def is_authenticated(self):
        return self

def show(self, Request):

    Request.extend(Authentication.is_authenticated)

    print(Request.is_authenticated()) # returns the Request class

Extending a class

We can even extend a whole class which will get all the classes methods and create bound methods to the Request class.

class Authentication:

    def is_authenticated(self):
        return self

    def login(self):
        return self

def show(self, Request):

    Request.extend(Authentication)

    print(Request.is_authenticated()) # returns the Request class
    print(Request.login()) # returns the Request class

Masonite 1.6

Introduction

Masonite 1.6 brings mostly minor changes to the surface layer of Masonite. This release brings a lot of improvements to the engine of Masonite and sets up the framework for the release of 2.0.

HttpOnly Cookies

Previously, all cookies were set with an HttpOnly flag when setting cookies. This change came after reading several articles about how cookies can be read from Javascript libraries, which is fine, unless those Javascript libraries have been compromised which could lead to a malicious hacker sending your domain name and session cookies to a third party. There is no the ability to turn the HttpOnly flag off when setting cookies by creating cookies like:

request().cookie('key', 'value', http_only=False)

Moved Commands

Because craft is it's own tool essentially and it needs to work across Masonite versions, all commands have been moved into the Masonite repository itself. Now each version of Masonite maintains it's own commands. The new craft version is 2.0

pip install masonite-cli==2.0

Drivers Can Now Change To Other Drivers

Before, you had to use the Manager class associated with a driver to switch a driver. For example:

def show(self, Upload, UploadManager):
    Upload.store(...) # default driver
    UploadManager.driver('s3').store(...) # switched drivers

Now you can switch drivers from the driver itself:

def show(self, Upload):
    Upload.store(...) # default driver
    Upload.driver('s3').store(...) # switched drivers

New Absolute Controllers Syntax

This version has been fine tuned for adding packages to Masonite. This version will come along with a new Masonite Billing package. The development of Masonite Billing has discovered some rough spots in package integrations. One of these rough spots were adding controllers that were not in the project. For example, Masonite Billing allows adding a controller that handles incoming Stripe webhooks. Although this was possible before this release, Masonite 1.6 has added a new syntax:

ROUTES = [
    ...
    # Old Syntax:
    Get().route('/url/here', 'Controller@show').module('billing.controllers')
    
    # New Syntax:
    Get().route('/url/here', '/billing.controllers.Controller@show')
    ...
]

Notice the new forward slash in the beginning where the string controller goes.

Changed How Controllers Are Created

Previously, controllers were created as they were specified. For example:

$ craft controller DashboardController

created a DashboardController. Now the "Controller" part of the controller is appended by default for you. Now we can just specify:

$ craft controller Dashboard

to create our DashboardController. You may was to actually just create a controller called Dashboard. We can do this by specifying a flag:

$ craft controller Dashboard -e

short for "exact"

Added Resource Controllers

It's also really good practice to create 1 controller per "action type." For example we might have a BlogController and a PostController. It's easy to not be sure what action should be in what controllers or what to name your actions. Now you can create a "Resource Controller" which will give you a list of actions such as show, store, create, update etc etc. If what you want to do does not fit with an action you have then you may want to consider making another controller (such as an AuthorController)

You can now create these Resource Controllers like:

craft controller Dashboard -r

Added Global Views

Just like the global controllers, some packages may require you to add a view that is located in their package (like the new exception debug view in 1.6) so you may now add views in different namespaces:

def show(self):
    return view('/masonite/views/index')

This will get a template that is located in the masonite package itself.

Added Route Groups

You can now group routes based on a specific string prefix. This will now look like:

routes/web.py
from masonite.helpers.routes import get, group
ROUTES = [
    get('/home', ...)
    group('/dashboard', [
        get('/user', ...)
        get('/user/1')
    ])
]

which will compile down into /dashboard/user and /dashboard/user/1

Changed Container Resolving

The container was one of the first features coded in the 1.x release line. For Masonite 1.6 we have revisited how the container resolves objects. Before this release you had to put all annotation classes in the back of the parameter list:

from masonite.request import Request

def show(self, Upload, request: Request):
    pass

If we put the annotation in the beginning it would have thrown an error because of how the container resolved.

Now we can put them in any order and the container will grab each one and resolve it.

from masonite.request import Request

def show(self, Upload, request: Request, Broadcast):
    pass

This will now work when previously it did not.

Resolving Instances

The container will now resolve instances of classes as well. It's a common paradigm to "code to an interface and not an implementation." Because of this paradigm, Masonite comes with contracts that act as interfaces but in addition to this, we can also resolve instances of classes.

For example, all Upload drivers inherit the UploadContract contract so we can simply resolve the UploadContract which will return an Upload Driver:

from masonite.contracts.UploadContract import UploadContract

def show(self, upload: UploadContract):
    upload.store(...)

Notice here that we annotated an UploadContract but got back the actual upload driver.

Container Collection

You can now search the container and "collect" objects from it by key using the new collect method:

app.collect('Sentry*Hook')

which will find all keys in the container such as SentryExceptionHook and SentryWebHook and make a new dictionary out of them.

Removed Some Dependencies

A complaint a few developers pointed out was that Masonite has too many dependencies. Masonite added Pusher, Ably and Boto3 packages by default which added a bit of overhead, especially if developers have no intentions on real time event broadcasting (which most applications probably won't). These dependencies have now been removed and will throw an exception if they are used without the required dependencies.

Framework Hooks

Masonite 1.6 + will slowly be rolling out various framework hooks. These hooks will allow developers and third party packages to integrate into various events that are fired throughout the framework. Currently there is the abilitity to tie into the exception hook which will call any objects loaded into the container whenever an exception is hit. This is great if you want to add things like Sentry into your project. Other hooks will be implemented such as View, Mail and Upload Hooks.

Requests
Service Providers
Service Container
Middleware
Cleo Documentation
Controllers
Routing
Architectural Concepts
Request Lifecycle
Service Providers
Service Container
Release Cycle
Caching
Creating Packages
Masonite pip packages
Medium
YouTube
Gitbook.com
GitHub.com
GitHub.com
LaraCasts.com
Contributing Guide

Service Providers

Introduction

You may create your own service provider and add it to your providers list to extend Masonite, or even remove some providers if you don't need their functionality. If you do create your own Service Provider, consider making it available on PyPi so others can install it into their framework.

Creating a Provider

We can create a Service Provider but simply using a craft command:

$ craft provider DashboardProvider

This will create a new Service Provider under our app/providers/DashboardProvider.py. This new Service Provider will have two simple methods, a register method and a boot method. We'll explain both in detail.

Service Provider Execution

There are a few architectural examples we will walk through to get you familiar with how Service Providers work under the hood. Let's look at a simple provider and walk through it.

from masonite.provider import ServiceProvider
from app.User import User

class UserModelProvider(ServiceProvider):
    ''' Binds the User model into the Service Container '''

    wsgi = False 

    def register(self):
        self.app.bind('User', User)

    def boot(self):
        print(self.app.make('User'))

We can see that we have a simple provider that registers the User model into the container. There are three key features we have to go into detail here.

WSGI

First, the wsgi = False just tells Masonite that this specific provider does not need the WSGI server to be running. When the WSGI server first starts, it will execute all service providers that have wsgi set to False. Whenever a provider only binds things into the container and we don't need things like requests or routes, then consider setting wsgi to False. the ServiceProvider class we inherited from sets wsgi to True by default. Whenever wsgi is True then the service provider will fire on every request.

Register

In our register method, it's important that we only bind things into the container. When the server is booted, Masonite will execute all register methods on all service providers. This is so the boot method will have access to the entire container.

Boot

The boot method will have access to everything that is registered in the container and is actually resolved by the container. Because of this, we can actually rewrite our provider above as this:

from masonite.provider import ServiceProvider
from app.User import User

class UserModelProvider(ServiceProvider):
    ''' Binds the User model into the Service Container '''

    wsgi = False 

    def register(self):
        self.app.bind('User', User)

    def boot(self, User):
        print(User)

This will be exactly the same as above. Notice that the boot method is resolved by the container.

Great! It's really that simple. Just this knowledge will take you a long way. Take a look at the other service providers to get some inspiration on how you should create yours. Again, if you do create a Service Provider, consider making it available on PyPi so others can install it into their framework.

Creating Packages

Introduction

Creating packages is very simple for Masonite. You can get a package created and on PyPi is less than 5 minutes. With Masonite packages you'll easily be able to integrate and scaffold all Masonite projects with ease. Masonite comes with several helper functions in order to create packages which can add configuration files, routes, controllers, views, commands and more.

Getting Started

As a developer, you will be responsible for both making packages and consuming packages. In this documentation we'll talk about both. We'll start by talking about how to make a package and then talk about how to use that package or other third party packages.

Masonite, being a Python framework, can obviously utilize all Python packages that aren’t designed for a specific framework. For example, Masonite can obviously use a library like requests but can’t use Django Rest Framework.

Similarly to how Django Rest Framework was built for Django, you can also build packages specific to Masonite. Although you can just as simply build packages for both, as long as you add some sort of Service Provider to your package that can integrate your library into Masonite.

About Packages

There are several key functions that Masonite uses in order to create applications. These include primarily: routes, controllers, views, and craft commands. Creating a package is simple. Conveniently Masonite comes with several helper functions in order to create all of these.

You can easily create a command like craft mypackage:install and can scaffold out and install controllers, routes, etc into a Masonite project.

You do not have to use this functionality and instead have the developer copy and paste things that they need to from your documentation but having a great setup process is a great way to promote developer happiness which is what Masonite is all about.

Creating a Package

Like other parts of Masonite, in order to make a package, we can use a craft command. The craft package command will scaffold out a simple PyPi package and is fully able to be uploaded directly to PyPi.

This should be done in a separate folder outside of your project.

Let's create our package:

$ craft package testpackage

This will create a file structure like:

testpackage/
    __init__.py
    integration.py
MANIFEST.in
setup.py

Creating a Config Package

Lets create a simple package that will add or append a config file from our package and into the project.

First lets create a config file inside testpackage/snippets/configs/services.py. We should now have a project structure like:

testpackage/
    __init__.py
    integration.py
    snippets/
        configs/
            services.py
MANIFEST.in
setup.py

Great! inside the services.py lets put a configuration setting. This configuration file will be directly added into a Masonite project so you can put doctrings or flagpole comments directly in here:

TESTPACKAGE_PAYMENTS = {
    'stripe': {
        'some key': 'some value',
        'another key': 'another value'
    }
}

Perfect! Now we'll just need to tell PyPi to include this file when we upload it to PyPi. We can do this in our MANIFEST.in file.

include testpackage/snippets/configs/*

Creating an Install Command

Head over to that documentation page and create an InstallCommand and an InstallProvider. This step should take less than a few minutes. Once those are created we can continue to the adding package helpers below.

Adding Migration Directories

Masonite packages allow you to add new migrations to a project. For example, this could be used to add a new package_subscriptions table if you are building a package that works for subscribing users to Stripe.

Inside the Service Provider you plan to use for your package we can register our directory:

from masonite.provider import ServiceProvider

package_directory = os.path.dirname(os.path.realpath(__file__))

class ApiProvider(ServiceProvider):

    def register(self):
        self.app.bind(
            'TestPackageMigrationDirectory',
            os.path.join(package_directory, '../migrations')
        )

Masonite will find any keys in the container that end with MigrationDirectory and will add it to the list of migrations being ran whenever craft migrate and craft migrate:* commands are ran.

The package_directory variable contains the absolute path to the current file so the migration directory being added should also be an absolute path to the migration directory as demonstrated here. Notice the ../migrations syntax. This is going back one directory and into a migration directory there.

Package Helpers

Almost done. Now we just need to put our masonite.package helper functions in our install command. The location we put in our create_or_append_config() function should be an absolute path location to our package. To help with this, Masonite has put a variable called package_directory inside the integration.py file. Our handle method inside our install command should look something like:

import os
from cleo import Command
from masonite.packages import create_or_append_config


package_directory = os.path.dirname(os.path.realpath(__file__))

class InstallCommand(Command):
    """
    Installs needed configuration files into a Masonite project

    testpackage:install
    """

    def handle(self):
        create_or_append_config(
            os.path.join(
                package_directory,
                '../testpackage/snippets/configs/services.py'
            )
        )

This will append the configuration file that has the same name as our package configuration file. In this case the configuration file we are creating or appending to is config/services.py because our packages configuration file is services.py. If we want to append to another configuration file we can simply change the name of our package configuration file.

Working With Our Package

We can either test our package locally or upload our package to PyPi.

To test our package locally, if you use virtual environments, just go to your Masonite project and activate your virtual environment. Navigate to the folder where you created your package and run:

$ pip install .

If you want to be able to make changes without having to constantly reinstall your package then run

$ pip install --editable .

This will install your new package into your virtual environment. Go back to your project root so we can run our craft testpackage:install command. If we run that we should have a new configuration file under config/services.py.

Uploading to PyPi

To upload to PyPi we just have to pick a great name for our package in the setup.py file. Now that you have a super awesome name, we'll just need to run:

$ python setup.py sdist upload

which should upload our package with our credentials in our .pypirc file. Make sure you click the link above and see how to make once.

If python doesn’t default to Python 3 or if PyPi throws errors than you may need to run:

$ python3 setup.py sdist upload

Consuming a package.

Now that your package is on PyPi we can just run:

$ pip install super_awesome_package

Then add your Service Provider to the PROVIDERS list:

PROVIDERS = [
    ...
    # New Provider
    'testpackage.providers.InstallProvider.InstallProvider',
]

and then run:

$ craft testpackage:install

Remember our Service Provider added the command automatically to craft.

Again, not all packages will need to be installed or even need commands. Only packages that need to scaffold the project or something similar need one. This should be a judgment call on the package author instead of a requirement.

You will know if a package needs to be installed by reading the packages install documentation that is written by the package authors.

Helper Functions

These helper functions are used inside the install commands or anywhere else in your package where you need to scaffold a Masonite project.

The location specified as parameters here are absolute path locations. You can achieve this by using the package_directory variable in your integration.py file.

To achieve an absolute path location, this will look like:

location = os.path.join(
            package_directory, 'snippets/configs/services.py'
        )

All helper functions are located in the masonite.packages module. To use these functions you’ll need to import the function to be used like:

from masonite.packages import create_or_append_config

Creating Configuration Files

create_or_append_config(location) will create a configuration file based on a configuration file from your package.

Creating Web Routes

append_web_routes(location) will append web routes to the routes/web.py file. Your web routes should have a += to the ROUTES constant and should look something like:

ROUTES += [
    # your package routes here
]

Creating Api Routes

append_api_routes(location) will append api routes to a masonite project under routes/api.py. Your api routes should have a += to the ROUTES constant and should look something like:

ROUTES += [
    # your package routes here
]

Creating Controllers

create_controller(location) will take a controller from your package and append it under the app.http.controllers namespace by default.

You can also optionally add a to parameter to specify which directory you want to install the controller into. The directory will automatically be created.

create_controller(
    os.path.join(
        package_directory, 'snippets/controller/ControllerHere.py'
    ),
    to = 'app/http/controllers/Vendor/Package'
)

This will create the controller in app.http.controller.Vendor.Package.ControllerHere.py

Creating a Mail Driver

Introduction

Because of Masonite's Service Container, It is extremely easy to make drivers that can be used by simply adding your service provider.

Getting Started

Masonite comes shipped with a Service Provider called MailProvider which loads a few classes into the container as well as boots the default mail driver using the MailManager. This manager class will fetch drivers from the container and instantiate them. We can look at the MailProvider class which will gives us a better explanation as to what's going on:

class MailProvider(ServiceProvider):

    wsgi = False

    def register(self):
        self.app.bind('MailConfig', mail)
        self.app.bind('MailSmtpDriver', MailSmtpDriver)
        self.app.bind('MailMailgunDriver', MailMailgunDriver)

    def boot(self):
        self.app.bind('Mail', MailManager(self.app))

We can see here that because we are only binding things into the container and we don't need the WSGI server to be running, we set wsgi = False. Service Providers that set wsgi to False will only run when the server starts and not on every request.

We can see here that we are binding a few drivers into the container and then binding the MailManager on boot. Remember that our boot method has access to everything that has been registered into the container. The register methods are executed on all providers before the boot methods are executed.

Mail Manager

The MailManager here is important to understand. When the MailManager is instantiated, it accepts the container as a parameter. When the MailManager is instantiated, it fires a create_driver method which will grab the driver from the configuration file and retrieve a MailXDriver from the container. The create_driver method is a very simple method:

def create_driver(self, driver=None):
    if not driver:
        driver = self.container.make('MailConfig').DRIVER.capitalize()
    else:
        driver = driver.capitalize()

    try:
        self.manage_driver = self.container.make('Mail{0}Driver'.format(driver))
    except KeyError:
        raise DriverNotFound('Could not find the Mail{0}Driver from the service container. Are you missing a service provider?'.format(driver))

Notice that when the driver is created, it tries to get a Mail{0}Driver from the container. Therefore, all we need to do is register a MailXDriver into the container ('X' being the name of the driver) and Masonite will know to grab that driver.

Creating a Driver

So now we know that we need a MailXDriver so let's walk through how we could create a maildrill email driver.

We can simply create a class which can become our driver. We do not need to inherit anything, although Masonite comes with a BaseMailDriver to get you started faster and all drivers should inherit from it for consistency reasons. You can make your driver from a normal class object but it will be harder and won't be considered in Pull Requests.

Let's create a class anywhere we like and inherit from BaseMailDriver:

from masonite.driver.BaseMailDriver import BaseMailDriver

class MailMaildrillDriver(BaseMailDriver):
    pass

Great! We are almost done. We just have to implement one method on this class and that's the send method. All other methods like to and template are inherited from the BaseMailDriver class. You can find out how to send an email using Maildrill and implement it in this send method.

We can look at other drivers for inspiration but let's look at the MailMailgunDriver class now:

import requests
from masonite.drivers.BaseMailDriver import BaseMailDriver

class MailMailgunDriver(BaseMailDriver):

    def send(self, message=None):
        if not message:
            message = self.message_body

        domain = self.config.DRIVERS['mailgun']['domain']
        secret = self.config.DRIVERS['mailgun']['secret']
        return requests.post(
            "https://api.mailgun.net/v3/{0}/messages".format(domain),
            auth=("api", secret),
            data={"from": "{0} <mailgun@{1}>".format(self.config.FROM['name'], domain),
                  "to": [self.to_address, "{0}".format(self.config.FROM['address'])],
                "subject": self.message_subject,
                "html": message})

If you are wondering where the self.message_body and self.config are coming from, check the BaseMailDriver. All driver constructors are resolved by the service container so you can grab anything you need from the container to make your driver work. Notice here that we don't need a constructor because we inherited it from the BaseMailDriver

Registering Your Mail Driver

Our AppProvider class might look something like this:

from your.driver.module import MailMandrillDriver
class AppProvider(ServiceProvider):

    wsgi = True

    def register(self):
        self.app.bind('WebRoutes', web.ROUTES)
        self.app.bind('ApiRoutes', api.ROUTES)
        self.app.bind('Response', None)
        self.app.bind('Storage', storage)

        # Register new mail driver
        self.app.bind('MailMandrillDriver', MailMandrillDriver)

    def boot(self, Environ):
        self.app.bind('Request', Request(Environ))
        self.app.bind('Route', Route(Environ))

Great! Our new driver is registered into the container. It is now able to be created with Masonite's MailManager class. We can retrieve your new driver by doing:

def show(self, Mail)
    Mail.driver('mandrill') # fetches MailMandrillDriver from container

Configuration

If we want the MailManager to use our new driver by default, change the DRIVER in our config/mail.py file. In addition, you may have the users of your driver require a special dictionary entry to the DRIVERS dictionary:

DRIVERS = {
    'smtp': {
        'host': os.getenv('MAIL_HOST', 'smtp.mailtrap.io'),
        'port': os.getenv('MAIL_PORT', '465'),
        'username': os.getenv('MAIL_USERNAME', 'username'),
        'password': os.getenv('MAIL_PASSWORD', 'password'),
    },
    'mailgun': {
        'secret': os.getenv('MAILGUN_SECRET', 'key-XX'),
        'domain': os.getenv('MAILGUN_DOMAIN', 'sandboxXX.mailgun.org')
    },
    'maildrill': {
        'secret': 'xx'
        'other_key': 'xx'
    }
}

This way, users can easily swap drivers by simply changing the driver in the config file.

That's it! We just extended our Masonite project and created a new driver. Consider making it available on PyPi so others can install it!

Masonite 1.5

Introduction

Masonite 1.5 is focused on a few bug fixes and changes to several core classes in order to expand on the support of third party package integration.

Masonite Entry

HTTP Methods

Moving Dependencies

Nearly all dependencies have been moved to the core Masonite package. The only thing inside the project that is installed using craft new is the WSGI server (which is waitress by default) and Masonite itself. This will improve the ability to change and update dependencies.

Changing Form Request Methods

Added Shorthand Route Helpers

Removed API from core

In Masonite 1.4 and below was the Api() route which added some very basic API endpoints. All references to API's have been removed from core and added to the new Masonite Entry official package.

If you would like to use API's you will have to use the Masonite Entry package instead.

Headers

Cookies

Caching Driver Update

An All New Craft Command

Adding Commands

Adding Migration Directories

Added Sessions

You can now add data to sessions using the new Sessions feature which comes with a memory and cookie driver for storing data.

Craft Auto Adds Site Packages

In previous versions, Masonite has not been able to fetch the site packages directory if you were in a virtual environment because virtual environment directories are dynamically named depending on who created it. We have found a way to detect the virtual environment and the site packages directory so now there is no need to add the site packages directory manually to the packages configuration file

Masonite 1.5 to 1.6

Introduction

Not much has changed in the actual project structure of Masonite 1.6 so we just need to make some minor changes to our existing 1.5 project

Requirements.txt

We just have to change our Masonite dependency version in this file:

...
masonite>=1.6,<=1.6.99

Bootstrap/start.py

Masonite 1.6 now wraps the majority of the application in a try and catch block so we can add exception handling such as the new debug view and other exception features down the road such as Sentry and other error logging.

In the middle of the file (line 45 if you have not made changes to this file) we can simply wrap the application:

try:
    for provider in container.make('Application').PROVIDERS:
        located_provider = locate(provider)().load_app(container)
        if located_provider.wsgi is True:
            container.resolve(located_provider.boot)
except Exception as e:
    container.make('ExceptionHandler').load_exception(e)

This will also display an exception view whenever an exception is it.

Finished

That's all the changes we have to make for our project.

Masonite 1.3

Introduction

Masonite 1.3 comes with a plethora of improvements over previous versioning. This version brings new features such as Queue and Mail drivers as well as various bug fixes.

Fixed infinite redirection

Previously when a you tried to redirect using the Request.redirect() method, Masonite would sometimes send the browser to an infinite redirection. This was because masonite was not resetting the redirection attributes of the Request class.

Fixed browser content length

Previously the content length in the request header was not being set correctly which led to the gunicorn server showing a warning that the content length did not match the content of the output.

Changed how request input data is retrieved

Previously the Request class simply got the input data on both POST and GET requests by converting the wsgi.input WSGI parameter into a string and parsing. All POST input data is now retrieved using FieldStorage which adds support for also getting files from multipart/formdata requests.

Added Upload drivers

You may now simply upload images to both disk and Amazon S3 storage right out of the box. With the new UploadProvider service provider you can simply do something like:

<html>
  <body>
      <form action="/upload" method="POST" enctype="multipart/formdata">
        <input type="file" name="file">
      </form>
  </body>
</html>
def show(self, Upload):
    Upload.store(Request.input('file'))

As well as support for Amazon S3 by setting the DRIVER to s3.

Added several helper functions

These helper functions are added functions to the builtin Python functions which can be used by simply calling them as usual:

def show(self):
    return view('welcome')

Added a way to have global template variables

Very often you will want to have a single variable accessible in all of your views, such as the Request object or other class. We can use the new View class for this and put it in it's own service provider:

def boot(self, View, Request):
    View.share({'request': Request})

Middleware is now resolved by the container

You can now specify anything that is in the container in your middleware constructor and it will be resolved automatically from the container

Added new domain method to the Get and Post classes

Specify the subdomain you want to target with your route. It's common to want to have separate routes for your public site and multi-tenant sites. This will now look something like:

Get().domain('test').route('/dashboard', 'DashboardController@show')

Added new module method to Get and Post routes

By default, masonite will look for routes in the app/http/controllers namespace but you can change this for individual routes:

Get().module('thirdpary.routes').route('/dashboard', 'DashboardController@show')

This will look for the controller in the thirdparty.routes module.

Added Queues and Jobs

Caching

Introduction

Caching is an important aspect to any project and typically is used to speed up data that never changes and required a lot of resources to get. Powerful caching support is important in any application and Masonite comes with great caching support out of the box.

Getting Started

We need the CacheProvider in order to activate caching with Masonite. We do so simple by going to our config/application.py file and adding the Service Provider masonite.providers.CacheProvider.CacheProvider to the PROVIDERS list.

All configuration settings are inside the config/cache.py file. Masonite only comes with a simple disk driver which stores all of your cache on the file system.

By default, Masonite will store the cache under the bootstrap/cache directory but can be changed simply inside the DRIVERS dictionary in the config/cache.py file. For example to change from bootstrap/cache to the storage/cache/templates directory, this will look like:

Using the Cache

To start using the cache, we can use the Cache alias that is loaded into the container from the CacheProvider Service Provider. We can retrieve this from the container inside any method that is resolved by the container such as drivers, middleware and controllers. For example we can retrieve it from our controller method like so:

Storing

We can easily store items into the cache by doing:

This will create a bootstrap/cache/key.txt file which contains a simple value.

Also note that the directory will be automatically created if it does not exist.

Caching For Time

We may only want to cache something for a few seconds or a few days so we can do something like:

This will store the cache for 5 seconds. If you try to retrieve this value after 5 seconds, the Cache class will return None so be sure to check.

Storing in a Specific Location

If you don't want to store in the default locations you can specify the location parameter.

This can also be used in the normal store method:

Getting

It wouldn't be very good if we could only store values and not retrieve them. So we can also do this simple by doing:

Again this will return None if a cache is expired. If there is no time limit on the cache, this will simply always return the cache value.

Checking Validity

You can also explicitly check if a cache is still valid by doing:

This will return a boolean if a key is valid. This means it is not expired.

Checking Cache Exists

We'll have to sometimes check if a cache even exists so we can do that by running:

Which will return a boolean if the cache exists or not.

Updating

We may also want to update a cache. For a real world example, this is used for API's for example when updating the cache for rate limiting. This will not reset the expiration, only update the value.

Deleting

You can delete a cache by key using:

Mail

Introduction

Masonite comes with email support out of the box. Most projects you make will need to send emails upon actions like account creation or notifications. Because email is used so often with software applications, masonite provides mail support with several drivers.

Getting Started

All mail configuration is inside config/mail.py and contains several well documented options. There are several built in drivers you can use but you can make your own if you'd like.

By default, Masonite uses the smtp driver. Inside your .env file, just put your smtp credentials. If you are using Mailgun then switch your driver to mailgun and put your Mailgun credentials in your .env file.

Configuring Drivers

There are two drivers out of the box that masonite uses and there is a tiny bit of configuration for both.

SMTP Driver

The SMTP driver takes several configuration files we can all put in our .env file.

Because this is SMTP, we can utilize all SMTP services such as mailtrap and gmail.

Thats it! As long as the authentication works, we can send emails.

Remember that it is save to put sensitive data in your .env file because it is not committed to source control and it is inside the .gitignore file by default.

Mailgun Driver

Mailgun does not use SMTP and instead uses API calls to their service to send emails. Mailgun only requires 2 configuration settings:

If you change to using Mailgun then you will need to change the driver. By default the driver looks like:

This means you can specify the mail driver in the .env file:

or we can specify the driver directly inside config/mail.py

Masonite will retrieve the configuration settings for the mailgun driver from the DRIVERS configuration setting which Masonite has by default, you do not have to change this.

Sending an Email

The Mail class is loaded into the container via the the MailProvider Service Provider. We can fetch this Mail class via our controller methods:

We can send an email like so:

You can also obviously specify a specific user:

Switching Drivers

All mail drivers are managed by the MailManager class and bootstrapped with the MailProvider Service Provider.

We can specify which driver we want to use. Although Masonite will use the DRIVER variable in our mail config file by default, we can change the driver on the fly.

You can see in our MailProvider Service Provider that we can use the MailManager class to set the driver. We can use this same class to change the driver:

Queues

Sending an email may take several seconds so it might be a good idea to create a Job. Jobs are simply Python classes that inherit from the Queueable class and can be pushed to queues or ran asynchronously. This will look something like:

Instead of taking seconds to send an email, this will seem immediate and be sent using whatever queue driver is set. The async driver is set by default which requires no additional configuration and simply sends jobs into a new thread to be ran in the background.

Methods

We can also specify the subject:

You can specify which address you want the email to appear from:

Templates

If you don't want to pass a string as the message, you can pass a view template.

This will render the view into a message body and send the email as html. Notice that we didn't pass anything into the send message

Service Providers are the key building blocks to Masonite. The only thing they do is register things into the Service Container, or retrieve things from the Service Container. You can read more about the Service Container in the documentation. If you look inside the config/application.py file, you will find a PROVIDERS list which contains all the Service Providers involved in building the framework.

It's great (and convenient) to add craft commands to a project so developers can use your package more efficiently. You can head over the to learn how to create a command. It only involves a normal command class and a Service Provider.

Make sure this command is added to your Service Provider and the developer using your package adds it to the PROVIDERS list as per the documentation.

If you have never set up a package before then you'll need to . This file will hold our PyPi credentials.

Since the MailManager class creates the driver on boot, we can simply register the driver into the container via any service providers register method. We could create a new Service Provider and register it there. You can read more about created Service Providers under the documentation. For now, we will just register it from within our AppProvider.

Masonite 1.5 is releasing also with a new official package for adding RESTful API's to your project. This package used API Resources which are classes that can add a plethora of capabilities to your API endpoints. You can read more about it at the documentation.

Masonite 1.5 also adds additional support HTTP methods like PUT, PATCH and DELETE. More information about these new routes can be found under the documentation

HTML forms only support GET and POST so there is no the ability to add any other HTTP methods like PUT and PATCH which will change the actual request method on submission. You can read more about this in the documentation.

You can now use functions for routes instead of the classes. These new functions are just wrappers around the classes itself. Read more about this under the documentation.

You can now get and set any header information using the new Request.header method. This also allows third party packages to manipulate header information. Read more about this in the documentation.

You can now delete cookies using the delete_cookie method as well as set expiration dates for them. See the documentation for more information.

The Cache driver now has an update method which can update a cache value by key. This is useful if you want to change a key value or increment it. Storing a cache file also now auto creates that directory. Read more about this in the documentation.

Craft commands have been built from the ground up with the cleo package. It's an excellent package that is built around the extendability of commands by using primarily classes (instead of decorator functions). Read more under documentation

It is now possible to add craft commands to craft. You can read more about how under documentation

You can now add more migration directories by adding it to the container with a key ending in MigrationDirectory. This will add the directory to the list of directory that run when migrate commands are ran. You can read more about this in the documentation.

Read for any futher changes you may want or have to make to your code base.

Notice how we never imported anything from the module or Service Container. See the documentation for a more exhaustive list

Which will target test.example.com/dashboard and not example.com/dashboard. Read more about subdomains in the documentation.

Masonite now ships with a QueueManager class which can be used to build queue drivers. Masonite ships with an async driver which sends jobs to a background thread. These queues can process Jobs which ca be created with the new craft job command. See the documentation for more information.

Remember that Masonite uses a Service Container and automatic dependency injection. You can read more about it under the documentation.

You can follow the documentation here at . If you do make your own, consider making it available on PyPi so others can install it. We may even put it in Masonite by default.

Read more about creating Jobs and sending emails asynchronously in the documentation.

Service Container
Creating Commands
Creating Commands
check how to make a .pypirc file
Service Providers
Masonite Entry
Routing
Requests
Routing
Requests
Requests
Caching
The Craft Command
The Craft Command
Creating Packages
What's New in Masonite 1.6
Helper Functions
Routing
Queues and Jobs
DRIVERS = {
    'disk': {
        'location': 'storage/cache/templates'
    }
}
def show(self, Cache):
    Cache # returns the cache class
def show(self, Cache):
    Cache.store('key', 'value')
def show(self, Cache):
    Cache.store_for('key', 'value', 5, 'seconds')
def show(self, Cache):
    Cache.store_for('key', 'value', 5, 'seconds', location='app/cache')
def show(self, Cache):
    Cache.store('key', 'value', location='app/cache')
def show(self, Cache):
    Cache.get('key')
def show(self, Cache):
    Cache.is_valid('key')
def show(self, Cache):
    Cache.cache_exists('key')
def show(self, Cache):
    Cache.update('key', 'value')
def show(self, Cache):
    Cache.delete('key')
.env
MAIL_DRIVER=smtp
MAIL_FROM_ADDRESS=admin@email.com
MAIL_FROM_NAME=Masonite
MAIL_HOST=smtp.gmail.com
MAIL_PORT=465
MAIL_USERNAME=admin@email.com
MAIL_PASSWORD=password
.env
MAILGUN_SECRET=key-xx
MAILGUN_DOMAIN=sandboxXX.mailgun.org
config/mail.py
DRIVER = os.getenv('MAIL_DRIVER', 'smtp')
.env
MAIL_DRIVER=mailgun
config/mail.py
DRIVER = 'mailgun'
config/mail.py
DRIVERS = {
    ...
    'mailgun': {
        'secret': os.getenv('MAILGUN_SECRET', 'key-XX'),
        'domain': os.getenv('MAILGUN_DOMAIN', 'sandboxXX.mailgun.org')
    }
}
def show(self, Mail):
    print(Mail) # returns the default mail driver
def show(self, Mail):
    Mail.to('hello@email.com').send('Welcome!')
from app.User import User
...
def show(self, Mail):
    Mail.to(User.find(1).email).send('Welcome!')
def show(self, MailManager):
    MailManager.driver('mailgun') # now uses the Mailgun driver
from app.jobs.SendWelcomeEmail import SendWelcomeEmail

def show(self, Queue):
    Queue.push(SendWelcomeEmail)
Mail.subject('Welcome!').to('hello@email.com').send('Welcome!')
Mail.send_from('Admin@email.com').to('hello@email.com').send('Welcome!')
Mail.to('idmann509@gmail.com').template('mail/welcome').send()

Service Container

Introduction

The Service Container is an extremely powerful feature of Masonite and should be used to the fullest extent possible. It's important to understand the concepts of the Service Container. It's a simple concept but is a bit magical if you don't understand what's going on under the hood.

Getting Started

The Service Container is just a dictionary where classes are loaded into it by key-value pairs, and then can be retrieved by either the key or value through resolving objects. That's it.

Think of "resolving objects" as Masonite saying "what does your object need? Ok, I have them in this dictionary, let me get them for you."

The container holds all of the frameworks classes and features so adding features to Masonite only entails adding classes into the container to be used by the developer later on. This typically means "registering" these classes into the container (more about this later on).

This allows Masonite to be extremely modular.

There are a few objects that are resolved by the container by default. These include your controller methods (which are the most common and you have probably used them so far) driver and middleware constructors and any other classes that are specified in the documentation.

There are four methods that are important in interacting with the container: bind, make and resolve

Bind

In order to bind classes into the container, we will just need to use a simple bind method on our app container. In a service provider, that will look like:

from masonite.provider import ServiceProvider
from app.User import User
from masonite.request import Request

class UserModelProvider(ServiceProvider):

    def register(self):
        self.app.bind('User', User)

    def boot(self):
        pass

This will load the key value pair in the providers dictionary in the container. The dictionary after this call will look like:

>>> app.providers
{'User': <class app.User.User>}

The service container is available in the Request object and can be retrieved by:

def show(self, Request):
    Request.app() # will return the service container

Make

In order to retrieve a class from the service container, we can simply use the make method.

>>> from app.User import User
>>> app.bind('User', User)
>>> app.make('User')
<class app.User.User>

That's it! This is useful as an IOC container which you can load a single class into the container and use that class everywhere throughout your project.

Collecting

Collecting By Key

You may want to collect specific kinds of objects from the container based on the key. For example we may want all objects that start with "Exception" and end with "Hook" or want all keys that end with "ExceptionHook" if we are building an exception handler. We can do this easily:

app.collect('*ExceptionHook')

This will return a dictionary of all objects that are binded to the container that start with anything and end with "ExceptionHook" such as "SentryExceptionHook" or "AwesomeExceptionHook".

We can also do the opposite and collect everything that starts with a specific key:

app.collect('Sentry*')

This will collect all keys that start with "Sentry" such as "SentryWebhook" or "SentryExceptionHandler."

Lastly, we may want to collect things that start with "Sentry" and end with "Hook"

app.collect('Sentry*Hook')

This will get keys like "SentryExceptionHook" and "SentryHandlerHook"

Collecting By Object

You may also collect objects from the container depending on a specific class. This is useful if you need to get all objects in the container that you loaded in yourself and uses a proprietary base object or even just to get all drivers from the container.

from masonite.driver.BaseDriver import BaseDriver
app.collect(BaseDriver)

This will search the container for all objects that are subclasses of the BaseDriver and return them as a dictionary.

Resolve

This is the most useful part of the container. It is possible to retrieve objects from the container by simply passing them into the parameters. Certain aspects of Masonite are resolved such as controller methods, middleware and drivers.

For example, we can hint that we want to get the Request class and put it into our controller. All controller methods are resolved by the container.

def show(self, Request):
    Request.user()

In this example, before the show method is called, Masonite will look at the parameters and look inside the container for a key with the same name. In this example we are looking for Request so Masonite will look for a key inside the provider dictionary called Request and inject that value from the container into our method for us. Request is already loaded into the container for you out of the box.

Another way to resolve classes is by using Python 3 annotations:

from masonite.request import Request

def show(self, request_class: Request):
    request_class.user()

Masonite will know that you are trying to get the Request class and will actually retrieve that class from the container. Masonite will search the container for a Request class regardless of what the key is in the container, retrieve it, and inject it into the controller method. Effectively creating an IOC container with dependency injection. Think of this as a get by value instead of a get by key like the earlier example.

You can pass in keys and annotations in any order:

from masonite.request import Request

def show(self, Upload, request_class: Request, Broadcast):
    Upload.store(...)
    request_class.user()
    Broadcast.channel(...)

Pretty powerful stuff, eh?

Resolving Instances

Another powerful feature of the container is it can actually return instances of classes you annotate. For example, all Upload drivers inherit from the UploadContract which simply acts as an interface for all Upload drivers. Many programming paradigms say that developers should code to an interface instead of an implementation so Masonite allows instances of classes to be returned for this specific use case.

Take this example:

from masonite.contracts.UploadContract import UploadContract

def show(self, upload: UploadContract)
    upload.driver('s3').store(...)

Notice that we passed in a contract instead of the upload class. Masonite went into the container and fetched a class with the instance of the contract.

Resolving your own code

The service container can also be used outside of the flow of Masonite. Masonite takes in a function or class method, and resolves it's dependencies by finding them in the service container and injecting them for you.

Because of this, you can resolve any of your own classes or functions.

from masonite.request import Request

def randomFunction(User):
    print(User)

def show(self, Request):
    Request.app().resolve(randomFunction) # Will print the User object

Remember not to call it and only reference the function. The Service Container needs to inject dependencies into the object so it requires a reference and not a callable.

This will fetch all of the parameters of randomFunction and retrieve them from the service container. There probably won't be many times you'll have to resolve your own code but the option is there.

Broadcasting

Introduction

Masonite understands the developer need for building modern web applications so Masonite 1.4+ ships with WebSocket support. With a new Service Provider, configuration file and support for the pusher and ably drivers out of the box, we can now have full web socket support quickly and easily.

Configuration

All broadcasting configuration is located in the config/broadcast.py file. There are only two options: DRIVER and DRIVERS. The DRIVER should hold the value of the driver you want to use such as pusher:

DRIVER = 'pusher'

and DRIVERS should hold the configuration data:

DRIVERS = {
    'pusher': {
        'app_id': os.getenv('PUSHER_APP_ID', '29382xx..'),
        'client': os.getenv('PUSHER_CLIENT', 'shS8dxx..'),
        'secret': os.getenv('PUSHER_SECRET', 'HDGdjss..'),
    },
    'ably': {
        'secret': os.getenv('ABLY_SECRET', 'api:key')
    }
}

Each driver may require it's own individual setting values so be sure to check the documentation for the driver you are using. For the ably and pusher drivers, these are the only values you will need.

Make sure that the key in the DRIVER setting has a corresponding key in the DRIVERS setting.

Pusher

If you are using Pusher you will need the Pusher library:

terminal
$ pip install pusher

Ably

If you are using Ably you will need the ably driver:

terminal
$ pip install ably

Usage

Since we have a ServiceProvider Service Provider which takes care of the container bindings for us, we can now it simply by passing Broadcast into our parameter list in our controller methods like so:

def show(self, Broadcast):
    print(Broadcast) # prints the driver class

We can change the driver on the fly as well:

def show(self, Broadcast):
    print(Broadcast.driver('ably')) # prints the ably driver class

All drivers have the same methods so don't worry about different drivers having different methods.

Channels

We can send data through our WebSocket by running:

def show(self, Broadcast):
    Broadcast.channel('channel_name', 'message')

That's it! we have just sent a message to anyone subscribed to the channel_name channel.

We can also send a dictionary:

def show(self, Broadcast):
    Broadcast.channel('channel_name', {'message': 'hello world'})

We can also send a message to multiple channels by passing a list:

def show(self, Broadcast):
    Broadcast.channel(['channel1', 'channel2'], {'message': 'hello world'})

This will broadcast the message out to both channels. We can pass as many channels into the list as we like.

Masonite also has an optional third parameter which is the event name:

def show(self, Broadcast):
    Broadcast.channel('channel_name', 'message', 'subscribed')

Which will pass the event on to whoever is receiving the WebSocket.

View Composers and Sharing

Introduction

Very often you will find yourself adding the same variables to a view again and again. This might look something like

def show(self):
    return view('dashboard', {'request': request()})

def another_method(self):
    return view('dashboard/user', {'request': request()})

This can quickly become annoying and it can be much easier if you can just have a variable available in all your templates. For this, we can "share" a variable with all our templates with the View class.

The View class is loaded into our container under the ViewClass alias. It's important to note that the ViewClass alias from the container points to the class itself and the View from the container points to the View.render method. By looking at the ViewProvider this will make more sense:

class ViewProvider(ServiceProvider):

    wsgi = False

    def register(self):
        view = View()
        self.app.bind('ViewClass', view)
        self.app.bind('View', view.render)

As you can see, we bind the view class itself to ViewClass and the render method to the View alias.

View Sharing

We can share variables with all templates by simply specifying them in the .share() method like so:

ViewClass.share({'request': request()})

The best place to put this is in a new Service Provider. Let's create one now called ViewComposer.

$ craft provider ViewComposer

This will create a new Service Provider under app/providers/ViewComposer.py and should look like this:

class ViewComposer(ServiceProvider):

    def register(self):
        pass

    def boot(self):
        pass

We also don't need it to run on every request so we can set wsgi to False. Doing this will only run this provider when the server first boots up. This will minimize the overhead needed on every request:

class ViewComposer(ServiceProvider):

    wsgi = False

    def register(self):
        pass

    def boot(self):
        pass

Great!

Since we need the request, we can throw it in the boot method which has access to everything registered into the service container, including the Request class.

class ViewComposer(ServiceProvider):

    wsgi = False

    def register(self):
        pass

    def boot(self, ViewClass, Request):
        ViewClass.share({'request': Request})

Lastly we need to load this into our PROVIDERS list inside our config/application.py file.

PROVIDERS = [
    # Framework Providers
    ...
    'masonite.providers.ViewProvider.ViewProvider',
    'masonite.providers.HelpersProvider.HelpersProvider',

    # Third Party Providers

    # Application Providers
    'app.providers.UserModelProvider.UserModelProvider',
    'app.providers.MiddlewareProvider.MiddlewareProvider',
    'app.providers.ViewComposer.ViewComposer', # <- New Service Provider
]

And we're done! When you next start your server, the request variable will be available on all templates.

View Composing

In addition to sharing these variables with all templates, we can also specify only certain templates. All steps will be exactly the same but instead of the .share() method, we can use the .composer() method:

def boot(self, ViewClass, Request):
    ViewClass.composer('dashboard', {'request': Request})

Now anytime the dashboard template is accessed (the one at resources/templates/dashboard.html) the request variable will be available.

We can also specify several templates which will do the same as above but this time with the resources/templates/dashboard.html template AND the resources/templates/dashboard/user.html template:

def boot(self, ViewClass, Request):
    ViewClass.composer(['dashboard', 'dashboard/user'], {'request': Request})

We can compose a dictionary for all templates:

def boot(self, ViewClass, Request):
    ViewClass.composer('*', {'request': Request})

Note that this has exactly the same behavior as ViewClass.share()

Lastly, we can use wildcard composers with template names:

ViewClass.composer(['dashboard*'], {'request': Request})

This will match templates such as resources/templates/dashboard.html.This will also match templates in an entire directory structures such as resources/templates/dashboard/user.html A nice shorthand from a list of templates and much more scalable.

Check For Existing Views

You may need to check if a certain view exists. This could be used to display alternate views or used when developing a package to verify that certain views exist. We can use the exists() method on the ViewClass class:

ViewClass.exists('errors/404')

This will check is the template at resources/templates/errors/404.html exists. We can also check if global templates exist by prepending a forward slash:

ViewClass.exists('/masonite/errors/404')

An entire use case as code may look something like:

app/http/controllers/YourController.py
def show(self, ViewClass):
    if ViewClass.exists('errors/404'):
        return view('errors/404')
    
    return view('/some/package/404')

Uploading

Introduction

Very often you will need to upload user images such as a profile image. Masonite let's you handle this very elegantly and allows you to upload to both the disk, and Amazon S3 out of the box. The UploadProvider Service Provider is what adds this functionality. Out of the box Masonite supports the disk driver which uploads directly to your file system and the s3 driver which uploads directly to your Amazon S3 bucket.

You may build more drivers if you wish to expand Masonite's capabilities. If you do create your driver, consider making it available on PyPi so others may install it into their project.

Read the "Creating an Email Driver" for more information on how to create drivers. Also look at the drivers directory inside the MasoniteFramework/core repository.

Configuration

All uploading configuration settings are inside config/storage.py. The settings that pertain to file uploading are just the DRIVER and the DRIVERS settings.

DRIVER and DRIVERS Settings

This setting looks like:

DRIVER = os.getenv('STORAGE_DRIVER', 'disk')

This defaults to the disk driver. The disk driver will upload directly onto the file system. This driver simply needs one setting which is the location setting which we can put in the DRIVERS dictionary:

DRIVERS = {
    'disk': {
        'location': 'storage/uploads'
    }
}

This will upload all images to the storage/uploads directory. If you change this directory, make sure the directory exists as Masonite will not create one for you before uploading. Know that the dictionary inside the DRIVERS dictionary should pertain to the DRIVER you set. For example, to set the DRIVER to s3 it will look like this:

DRIVER = 's3'

DRIVERS = {
    'disk': {
        'location': 'storage/uploads'
    },
    's3': {
        'client': os.getenv('S3_CLIENT', 'AxJz...'),
        'secret': os.getenv('S3_SECRET', 'HkZj...'),
        'bucket': os.getenv('S3_BUCKET', 's3bucket'),
    }
}

Some deployment platforms are Ephemeral. This means that either hourly or daily, they will completely clean their file systems which will lead to the deleting of anything you put on the file system after you deployed it. In other words, any user uploads will be wiped. To get around this, you'll need to upload your images to Amazon S3 or other asset hosting services which is why Masonite comes with Amazon S3 capability out of the box.

Uploading

Uploading with masonite is extremely simple. We can use the Upload class which is loaded into the container via the UploadProvider Service Provider. Whenever a file is uploaded, we can retrieve it using the normal Request.input() method. This will look something like:

<html>
    <body>
    <form action="/upload" method="POST" enctype="multipart/form-data">
        <input type="file" name="file_upload">
    </form>
    </body>
</html>

And inside our controller we can do:

def upload(self, Upload):
    Upload.driver('disk').store(Request.input('file_upload'))

That's it! We specified the driver we want to use and just uploaded an image to our file system.

This action will return the file system location. We could use that to input into our database if we want:

>>> Upload.driver('disk').store(Request.input('file_upload'))
storage/uploads/new_upload.png

We may also need to get the filename of the upload. If the request input is a file upload, we have some additional attributes we can use:

>>> Request.input('file_upload')
new_upload.png

Lastly, we may need to prepend the file name with something like a uuid or something or even just a normal string. We can do so by using the storePrepend() method:

>>> Upload.driver('disk').store(Request.input('file_upload'), 'prepend_name_')
prepend_name_newupload.png

Accepting Certain File Types

When uploading, it may be important to only accept certain file types. Out of the box Masonite allows uploading of all file types including .doc and .dmg and .exe. This is a security hazard so you whenever you upload, you should specify which file types you want to accept:

def show(self, Upload):
    Upload.accept(
        'jpeg',
        'jpg',
        'png'
    ).store(request().input('file'))

Notice that we specified jpeg and jpg. You should specify both if you want to accept either of them as the file extensions are unpredictable.

This doesn't only work for images. You can specify any file types:

def show(self, Upload):
    Upload.accept(
        'doc',
        'docx',
        'pdf'
    ).store(request().input('file'))

Uploading to S3

Before you get started with uploading to Amazon S3, you will need the boto3 library:

terminal
$ pip install boto3

Uploading to S3 is exactly the same. Simply add your username, secret key and bucket to the S3 setting:

DRIVER = 's3'

DRIVERS = {
    'disk': {
        'location': 'storage/uploads'
    },
    's3': {
        'client': os.getenv('S3_CLIENT', 'AxJz...'),
        'secret': os.getenv('S3_SECRET', 'HkZj...'),
        'bucket': os.getenv('S3_BUCKET', 's3bucket'),
    }
}

Make sure that your user has the permission for uploading to your S3 bucket.

Then in our controller:

def upload(self, Upload):
    Upload.driver('s3').store(Request.input('file_upload'))

How the S3 driver currently works is it uploads to your file system using the disk driver, and then uploads that file to your Amazon S3 bucket. So do not get rid of the disk setting in the DRIVERS dictionary.

Sessions

Introduction

You'll find yourself needing to add temporary data to an individual user. Sessions allow you to do this by adding a key value pair and attaching it to the user's IP address. You can reset the session data anytime you want; which is usually done on logout or login actions.

The Session features are added to the framework through the SessionProvider Service Provider. This provider needs to be between the AppProvider and the RouteProvider. For Masonite 1.5+, this provider is already available for you.

It's important to note that the Session will default to the memory driver. This means that all session data is stored in an instantiated object when the server starts and is destroyed when the server stops. This is not good when using a WSGI server like Gunicorn which might use multiple workers because each worker will create it's own memory state and requests may jump from worker to worker unpredictably. If you are only using 1 worker then this won't be an issue as long as the worker doesn't die and reset for the duration of the server's life. In this case you should use another driver that doesn't have the memory issue like the cookie driver which will store all session information as cookies instead of in memory.

Getting Started

There are a two ideas behind sessions. There is session data and flash data. Session data is any data that is persistent for the duration of the session and flash data is data that is only persisted on the next request. Flash data is useful for showing things like success or warning messages after a redirection.

Session data is automatically encrypted and decrypted using your secret key when using the cookie driver.

Using Sessions

Sessions are loaded into the container with the Session key. So you may access the Session class in any part of code that is resolved by the container. These include controllers, drivers, middleware etc:

def show(self, Session):
    print(Session) # Session class

Setting Data

Data can easily be persisted to the current user by using the set method. If you are using the memory driver, it will connect the current user's IP address to the data. If you are using the cookie then it will simply set a cookie with a s_ prefix to notify that it is a session and allows better handling of session cookies compared to other cookies.

def show(self, Session):
    Session.set('key', 'value')

This will update a dictionary that is linked to the current user.

Getting Data

Data can be pulled from the session:

def show(self, Session):
    Session.get('key') # Returns 'value'

Checking Data

Very often, you will want to see if a value exists in the session:

def show(self, Session):
    Session.has('key') # Returns True

Getting all Data

You can get all data for the current user:

def show(self, Session):
    Session.all() # Returns {'key': 'value'}

Flashing Data

Data can be inserted only for the next request. This is useful when using redirection and displaying a success message.

def show(self, Session):
    Session.flash('success', 'Your action is successful')

When using the cookie driver, the cookie will automatically be deleted after 2 seconds of being set.

Changing Drivers

You can of course change the drivers on the fly by using the SessionManager key from the container:

def show(self, SessionManager):
    SessionManager.driver('cookie').set('key', 'value')

It's important to note that changing the drivers will not change the session() function inside your templates (more about templates below).

Request Class

The SessionProvider attaches a session attribute to the Request class. This attribute is the Session class itself.

def show(self):
    request().session.flash('success', 'Your action is successful')

Templates

The SessionProvider comes with a helper method that is automatically injected into all templates. You can use the session helper just like you would use the Session class.

This helper method will only point to the default driver set inside config/session.py file. It will not point to the correct driver when it is changed using the SessionManager class. If you need to use other drivers, consider passing in a new function method through your view or changing the driver value inside config/session.py

{% if session().has('success') %}
    <div class="alert alert-success">
        {{ session().get('success') }}
    </div>
{% endif %}

You could use this to create a simple Jinja include template that can show success, warning or error messages. Your template could be located inside a resources/templates/helpers/messages.html:

{% if session().has('success') %}

    <div class="alert alert-success">
        {{ session().get('success') }}
    </div>

{% elif session().has('warning') %}

    <div class="alert alert-warning">
        {{ session().get('warning') }}
    </div>

{% if session().has('danger') %}

    <div class="alert alert-danger">
        {{ session().get('danger') }}
    </div>

{% endif %}

Then inside your working template you could add:

{% include 'helpers/messages.html' %}

Then inside your controller you could do something like:

def show(self):
    return request().redirect('/dashboard') \
        .session.flash('success', 'Action Successful!')

or as separate statements

def show(self, Session):
    Session.flash('success', 'Action Successful!')

    return request().redirect('/dashboard')

Which will show the correct message and message type.

Resetting Data

You can reset both the flash data and the session data through the reset method.

To reset just the session data:

def show(self, Session):
    Session.reset()

Or to reset only the flash data:

def show(self, Session):
    Session.reset(flash_only=True)

Remember that Masonite will reset flashed data at the end of a successful 200 OK request anyway so you will most likely not use the flash_only=True keyword parameter.

Deleting Data

You may want to delete session data as well. You can use the delete() method for that:

def show(self, Session):
    Session.set('key', 'value')
    Session.has('key') # Returns True
    Session.delete('key')
    Session.has('key') # Returns False
Service Container
Creating a Mail Driver
Queues and Jobs

Introduction

Introduction

The craft command tool is a powerful developer tool that lets you quickly scaffold your project with models, controllers, views, commands, providers, etc. which will condense nearly everything down to it’s simplest form via the craft namespace. No more redundancy in your development time creating boilerplate code. Masonite condenses all common development tasks into a single namespace.

For example, In Django you may need to do something like:

$ django-admin startproject
$ python manage.py runserver
$ python manage.py migrate

The craft tool condenses all commonly used commands into its own namespace

$ craft new
$ craft serve
$ craft migrate

All scaffolding of Masonite can be done manually (manually creating a controller and importing the view function for example) but the craft command tool is used for speeding up development and cutting down on mundane development time.

Commands

When craft is used outside of masonite directory, it will only show a few commands such as the new and install commands. Other commands such as commands for creating controllers or models are loaded in from the Masonite project itself.

Many commands are loaded into the framework itself and fetched when craft is ran in a Masonite project directory. This allows version specific Masonite commands to be efficiently handled on each subsequent version as well as third party commands to be loaded in which expands craft itself.

The possible commands for craft include:

Creating an Authentication System

To create an authentication system with a login, register and a dashboard system, just run:

 $ craft auth

This command will create several new templates, controllers and routes so you don’t need to create an authentication system from scratch, although you can. If you need a custom authentication system, this command will scaffold the project for you so you can go into these new controllers and change them how you see fit.

These new controllers are not apart of the framework itself but now apart of your project. Do not look at editing these controllers as editing the framework source code.

Creating Controllers

If you wish to scaffold a controller, just run:

$ craft controller Dashboard

This command will create a new controller under app/http/controllers/DashboardController.py. By convention, all controllers should have an appended “Controller” and therefore Masonite will append "Controller" to the controller created.

You can create a controller without appending "Controller" to the end by running:

$ craft controller Dashboard -e

This will create a app/http/controllers/Dashboard.py file with a Dashboard controller. Notice that "Controller" is not appended.

-e is short for --exact. Either flag will work.

You may also create resource controllers which include standard resource actions such as show, create, update, etc:

$ craft controller Dashboard -r

-r is short for --resource. Either flag will work.

You can also obviously combine them:

$ craft controller Dashboard -r -e

Creating a New Project

If you’d like to start a new project, you can run:

$ craft new project_name

This will download a zip file of the MasoniteFramework/masonite repository and unzip it into your current working directory. This command will default to the latest release of the repo.

You may also specify some options. The --version option will create a new project depending on the releases from the MasoniteFramework/masonite repository.

$ craft new project_name --version 1.3.0

Or you can specify the branch you would like to create a new project with:

$ craft new project_name --branch develop

After you have created a new project, you will have a requirements.txt file with all of the projects dependencies. In addition to this file, you will also have a .env-example file which contains a boiler plate of a .env file. In order to install the dependencies, as well as copy the example environment file to a .env file, just run:

$ craft install

The craft install command will also run craft key --store as well which generates a secret key and places it in the .env file.

Creating Migrations

All frameworks have a way to create migrations in order to manipulate database tables. Masonite uses a little bit of a different approach to migrations than other Python frameworks and makes the developer edit the migration file. This is the command to make a migration for an existing table:

$ craft migration name_of_migration —-table users

If you are creating a migration for a table that does not exist yet which the migration will create it, you can pass the --create flag like so:

$ craft migration name_of_migration --create users

These two flags will create slightly different types of migrations.

Migrating

After your migrations have been created, edited, and are ready for migrating, we can now migrate them into the database. To migrate all of your unmigrated migrations, just run:

$ craft migrate

Rolling Back and Rebuilding Migrations

You can also refresh and rollback all of your migrations and remigrate them.

This will essentially rebuild your entire database.

$ craft migrate:refresh

You can also rollback all migrations without remigrating

$ craft migrate:reset

Lastly, you can rollback just the last set of migrations you tried migrating

$ craft migrate:rollback

Models

If you'd like to create a model, you can run:

$ craft model ModelName

This will scaffold a model under app/ModelName and import everything needed.

If you need to create a model in a specific folder starting from the app folder, then just run:

$ craft model Models/ModelName

This will create a model in app/Models/ModelName.py.

Creating a Service Provider

Service Providers are a really powerful feature of Masonite. If you'd like to create your own service provider, just run:

$ craft provider DashboardProvider

This will create a file at app/providers/DashboardProvider.py

Creating Views

Views are simply html files located in resources/templates and can be created easily from running the command:

$ craft view blog

This command will create a template at resources/templates/blog.html

You can also create a view deeper inside the resources/templates directory.

$ craft view auth/home

This will create a view under resources/templates/auth/home.html but keep in mind that it will not create the directory for you. If the auth directory does not exist, this command will fail.

Creating Jobs

Jobs are designed to be loaded into queues. We can take time consuming tasks and throw them inside of a Job. We can then use this Job to push to a queue to speed up the performance of our application and prevent bottlenecks and slowdowns.

$ craft job SendWelcomeEmail

Packages

$ craft package name_of_package

Creating Commands

You can scaffold out basic command boilerplate:

$ craft command HelloCommand

This will create a app/commands/HelloCommand.py file with the HelloCommand class.

Running the WSGI Server

You can run the WSGI server by simply running:

$ craft serve

Encryption

To generate a secret key, we can run:

$ craft key

This will generate a 32 bit string which you can paste into your .env file under the KEY setting.

You may also pass the --store flag which will automatically add the key to your .env file:

$ craft key --store

This command is ran whenever you run craft install

Great! You are now a master at the craft command line tool.

Template Caching

Introduction

Sometimes your templates will not change that often and may have a lot of logic that takes times to run such as several loops or even a very large order of magnitude. If this page is being hit several times per day or even several times per second, you can use template caching in order to put less strain on your server.

This is a powerful feature that will reduce the load of your server for those resource hungry and complex templates.

Getting Started

This feature is introduced in Masonite 1.4 and above. You can check your Masonite version by running pip show masonite which will give you the details of the masonite package you have installed.

All caching configuration is inside config/cache.py which contains two settings, DRIVER and DRIVERS.

We can set the driver we want to use by specifying like so:

DRIVER = 'disk'

Like every other configuration, you'll need to specify the options inside the DRIVERS dictionary:

DRIVERS = {
    'disk': {
        'location': 'bootstrap/cache'
    }
}

All templates cached will be inside that folder. You may wish to specify another directory for your templates such as:

DRIVERS = {
    'disk': {
        'location': 'bootstrap/cache/templates'
    }
}

Caching Templates

In order to cache templates for any given amount of time, you can attach the .cache_for() method onto your view. This looks like:

def show(self):
    return view('dashboard/user').cache_for(5, 'seconds')

This will cache the template for 5 seconds. After 5 seconds, the next hit on that page will show the non cached template and then recache for another 5 seconds.

What has always been annoying in many libraries and frameworks is the distinguishhment between plural and singular such as second and seconds. So if we only want to do 1 minute then that would look like:

def show(self):
    return view('dashboard/user').cache_for(1, 'minute')

Options

There are several cache lengths we can use. Below is all the options for caching:

We can cache for several seconds:

def show(self):
    return view('dashboard/user').cache_for(1, 'second')

def show(self):
    return view('dashboard/user').cache_for(10, 'seconds')

or several minutes:

def show(self):
    return view('dashboard/user').cache_for(1, 'minute')

def show(self):
    return view('dashboard/user').cache_for(10, 'minutes')

or several hours:

def show(self):
    return view('dashboard/user').cache_for(1, 'hour')

def show(self):
    return view('dashboard/user').cache_for(10, 'hours')

or several days:

def show(self):
    return view('dashboard/user').cache_for(1, 'days')

def show(self):
    return view('dashboard/user').cache_for(10, 'days')

or several months:

def show(self):
    return view('dashboard/user').cache_for(1, 'month')

def show(self):
    return view('dashboard/user').cache_for(10, 'months')

or even several years:

def show(self):
    return view('dashboard/user').cache_for(1, 'year')

def show(self):
    return view('dashboard/user').cache_for(10, 'years')

Framework Hooks

Introduction

Currently there is only the Exception Hook that you can tie into but there will be other hooks in later releases of Masonite such as View Hooks and Mail Hooks.

Getting Started

Exception Hooks

The Exception Hook is fired when an exception is thrown in an application. Anytime you would normally see the debug view when developing is when this hook will fire. This hook may not be fired if the server does not boot up because of an exception depending on how far into the container the exception is thrown but any normal application exceptions encountered will fire this hook.

The exception hook to tie into is ExceptionHook. This means that the key in the container should end with ExceptionHook and Masonite will call it when the Exception Hook is thrown. We can load things into the container such as:

app.bind('SentryExceptionHook', YourObject())

Notice here that our key ends with ExceptionHook and that we instantiated the object. Let's explore creating this entirly from scratch.

Creating A Hook

Let's create a class called SentryHook and put it into app/hooks/sentry.py.

app/hooks/sentry.py
class SentryHook:
    def __init__(self):
        pass
    
    def load(self, app):
        self._app = app

This should be the basic structure for a hook. All hooks require a load method. This load method will always be passed the application container so it always requires that parameter. From here we can do whatever we need to by making objects from the container.

But for this example we actually don't need the container so we can ignore it.

Adding Sentry

This should be the finished hook:

from raven import Client
client = Client( 'https://874..:j8d@sentry.io/1234567')

class SentryHook:
    def __init__(self):
        pass
    
    def load(self, app):
        client.captureException()

Tieing Into The Exception Hook

Now let's walk through how we can simply tie this into the container so it will be called when an exception is thrown in our project.

Remeber that all we need to do is call is add it to the container and append the correct string to the key.

We can create a new Service Provider to store our hooks so let's make one.

$ craft provider HookProvider

This will create a new Service Provider inside app/providers/HookProvider.py that looks like:

from masonite.provider import ServiceProvider

class SentryServiceProvider(ServiceProvider):
    def register(self): 
        pass

    def boot(self): 
        pass

Now let's just add our hook to it:

from masonite.provider import ServiceProvider
from ..hooks.sentry import SentryHook

class SentryServiceProvider(ServiceProvider):
    def register(self): 
        self.app.bind('SentryExceptionHook', SentryHook())

    def boot(self): 
        pass

Notice that the key we binded our object to ends with "ExceptionHook." What we put before this part of the string is whatever you want to put. Also note that we also instantiated our SentryHook() and didn't put SentryHook

And finally add the Service Provider to our PROVIDERS constant in our config/application.py file:

...
# Application Providers 
'app.providers.UserModelProvider.UserModelProvider',
'app.providers.MiddlewareProvider.MiddlewareProvider',

# Sentry Provider
'app.providers.SentryServiceProvider.SentryServiceProvider',
...

CSRF Protection

Introduction

Masonite 1.4+ now has out of the box CSRF protection. CSRF, or Cross-Site Request Forgery is when malicious actors attempt to send requests (primarily POST requests) on your behalf. CSRF protection typically entails setting a unique token to the user for that page request that matches the same token on the server. This prevents any person from submitting a form without the correct token. There are many online resources that teach what CSRF does and how it works but Masonite makes it really simple to use.

If you are using Masonite 1.4 already then you already have the correct middleware and Service Providers needed. You can check which version of Masonite you are using by simply running pip show masonite and looking at the version number.

Getting Started

Usage

The CSRF features for Masonite are located in the CsrfProvider Service Provider and the CsrfMiddleware. If you do not wish to have CSRF protection then you can safely remove both of these.

The CsrfProvider simply loads the CSRF features into the container and the CsrfMiddleware is what actually generates the keys and checks if they are valid.

Templates

By default, all POST requests require a CSRF token. We can simply add a CSRF token in our forms by adding the {{ csrf_field|safe }} tag to our form like so:

<form action="/dashboard" method="POST">
    {{ csrf_field|safe }}

    <input type="text" name="first_name">
</form>

This will add a hidden field that looks like:

<input type="hidden" name="csrf_token" value="8389hdnajs8...">

If this token is changed or manipulated, Masonite will throw an InvalidCsrfToken exception from inside the middleware.

If you attempt a POST request without the {{ csrf_field|safe }} then you will receive a KeyError: 'csrf_token' exception. This just means you are either missing the Jinja2 tag or you are missing that route from the exempt class attribute in your middleware.

Exempting Routes

Not all routes may require CSRF protection such as OAuth authentication. In order to exempt routes from protection we can add it to the exempt class attribute in the middleware located at app/http/middleware/CsrfMiddleware.py:

class CsrfMiddleware:
    ''' Verify CSRF Token Middleware '''

    exempt = [
        '/oauth/github'
    ]

    ...

Now any POST routes that are to your-domain.com/oauth/github are not protected by CSRF. Use this sparingly as CSRF protection is crucial to application security but you may find that not all routes need it.

Queues and Jobs

Introduction

Almost all applications can make use of queues. Queues are a great way to make time intensive tasks immediate by sending the task into the background. It's great to send anything and everything into the queue that doesn't require an immediate return value -- such as sending an email or firing an API call. The queue system is loaded into masonite via the QueueProvider Service Provider.

Getting Started

All configuration settings by default are in the config/queue.py file. Out of the box, Masonite only supports the async driver which simply sends jobs into the background using multithreading. You are free to create more drivers. If you do create a driver, consider making it available on PyPi so others can also install it.

Jobs

Jobs are simply Python classes that inherit the Queueable class that is provided by Masonite. We can simply create jobs using the craft job command.

$ craft job SendWelcomeEmail

This will create a new job inside app/jobs/SendWelcomeEmail.py. Our job will look like:

from masonite.queues.Queueable import Queueable

class SendWelcomeEmail(Queueable):

    def __init__(self):
        pass

    def handle(self):
        pass

All job constructors are resolved by the container so we can simply pass anything we need as normal:

from masonite.queues.Queueable import Queueable

class SendWelcomeEmail(Queueable):

    def __init__(self, Request, Mail):
        self.request = Request
        self.mail = Mail

    def handle(self):
        pass

Whenever jobs are executed, it simply executes the handle method. Because of this we can send our welcome email:

from masonite.queues.Queueable import Queueable

class SendWelcomeEmail(Queueable):

    def __init__(self, Request, Mail):
        self.request = Request
        self.mail = Mail

    def handle(self):
        self.mail.driver('mailgun').to(self.request.user().email).template('mail/welcome').send()

That's it! We just created a job that can send to to the queue!

Running Jobs

We can run jobs by using the Queue alias from the container. Let's run this job from a controller method:

from app.jobs.SendWelcomeEmail import SendWelcomeEmail

def show(self, Queue):
    Queue.push(SendWelcomeEmail)

That's it! This job will be loaded into the queue. By default, Masonite uses the async driver which just sends tasks into the background.

We can also send multiple jobs to the queue by passing more of them into the .push() method:

from app.jobs.SendWelcomeEmail import SendWelcomeEmail
from app.jobs.TutorialEmail import TutorialEmail

def show(self, Queue):
    Queue.push(SendWelcomeEmail, TutorialEmail)

Middleware

Introduction

Middleware is an extremely important aspect of web applications as it allows you to run important code either before or after every request or even before or after certain routes. In this documentation we'll talk about how middleware works, how to consume middleware and how to create your own middleware. Middleware is only ran when the route is found and a status code of 200 will be returned.

Getting Started

Middleware classes are placed inside the app/http/middleware by convention but can be placed anywhere you like. All middleware are just classes that contain a before method and/or an after method.

There are four types of middleware in total:

  • Middleware ran before every request

  • Middleware ran after every request

  • Middleware ran before certain routes

  • Middleware ran after certain routes

There are what Masonite calls HTTP Middleware which are middleware designed to run on every request and Route Middleware which is middleware designed to only be called on certain requests, such as checking if a user is logged in.

Configuration

We have one of two configuration constants we need to work with. These constants both reside in our config/middleware.py file and are HTTP_MIDDLEWARE and ROUTE_MIDDLEWARE.

HTTP_MIDDLEWARE is a simple list which should contain an aggregation of your middleware classes. This constant is a list because all middleware will simply run in succession one after another, similar to Django middleware

Middleware is a string to the module location of your middleware class. If your class is located in app/http/middleware/DashboardMiddleware.py then the value we place in our middleware configuration will be a string: app.http.middleware.DashboardMiddleware.DashboardMiddleware. Masonite will locate the class and execute either the before method or the after method.

In our config/middleware.py file this type of middleware may look something like:

HTTP_MIDDLEWARE = [
    'app.http.middleware.DashboardMiddleware.DashboardMiddleware'
]

ROUTE_MIDDLEWARE is also simple but instead of a list, it is a dictionary with a custom name as the key and the middleware class as the value. This is so we can specify the middleware based on the key in our routes file.

In our config/middleware.py file this might look something like:

from app.http.middleware.RouteMiddleware import RouteMiddleware

ROUTE_MIDDLEWARE = {
    'auth': 'app.http.middleware.RouteMiddleware.RouteMiddleware'
}

Default Middleware

There are 3 default middleware that comes with Masonite. These middleware can be changed or removed to fit your application better.

Authentication Middleware

This middleware is design to redirect users to the login page if they are not logged in. This is loaded as a Route Middleware inside config/middleware.py and design to only be ran on specific routes you specify.

You can run this middleware on any route by specifying the key in the middleware method on your route:

routes/web.py
from masonite.helpers.routes import get
...

ROUTES = [
    ...
    get('/dashboard', 'DashboardController@show').middleware('auth')
]

CSRF Middleware

This middleware is an HTTP Middleware and runs on every request. This middleware checks for the CSRF token on POST requests. For GET requests, Masonite generates a new token. The default behavior is good for most applications but you may change this behavior to suit your application better.

Load User Middleware

This middleware checks if the user is logged in and if so, loads the user into the request object. This enables you to use something like:

app/http/controllers/YourController.py
def show(self):
    return request().user() # Returns the user or None

Creating Middleware

Again, middleware should live inside the app/http/middleware folder and should look something like:

class AuthenticationMiddleware:
    ''' Middleware class '''

    def __init__(self, Request):
        self.request = Request

    def before(self):
        pass

    def after(self):
        pass

Middleware constructors are resolved by the container so simply pass in whatever you like in the parameter list and it will be injected for you.

If Masonite is running a “before” middleware, that is middleware that should be ran before the request, Masonite will check all middleware and look for a before method and execute that. The same for “after” middleware.

You may exclude either method if you do not wish for that middleware to run before or after.

This is a boilerplate for middleware. It's simply a class with a before and/or after method. Creating a middleware is that simple. Let's create a middleware that checks if the user is authenticated and redirect to the login page if they are not. Because we have access to the request object from the Service Container, we can do something like:

class AuthenticationMiddleware:
    ''' Middleware class which loads the current user into the request '''

    def __init__(self, Request):
        self.request = Request

    def before(self):
        if not self.request.user():
            self.request.redirectTo('login')

    def after(self):
        pass

That's it! Now we just have to make sure our route picks this up. If we wanted this to execute after a request, we could use the exact same logic in the after method instead.

Since we are not utilizing the after method, we may exclude it all together. Masonite will check if the method exists before executing it.

Configuration

We have one of two configuration constants we need to work with. These constants both reside in our config/middleware.py file and are HTTP_MIDDLEWARE and ROUTE_MIDDLEWARE.

HTTP_MIDDLEWARE is a simple list which should contain an aggregation of your middleware classes. This constant is a list because all middleware will simply run in succession one after another, similar to Django middleware

Middleware is a string to the module location of your middleware class. If your class is located in app/http/middleware/DashboardMiddleware.py then the value we place in our middleware configuration will be a string: app.http.middleware.DashboardMiddleware.DashboardMiddleware. Masonite will locate the class and execute either the before method or the after method.

In our config/middleware.py file this type of middleware may look something like:

HTTP_MIDDLEWARE = [
    'app.http.middleware.DashboardMiddleware.DashboardMiddleware'
]

ROUTE_MIDDLEWARE is also simple but instead of a list, it is a dictionary with a custom name as the key and the middleware class as the value. This is so we can specify the middleware based on the key in our routes file.

In our config/middleware.py file this might look something like:

from app.http.middleware.RouteMiddleware import RouteMiddleware

ROUTE_MIDDLEWARE = {
    'auth': 'app.http.middleware.RouteMiddleware.RouteMiddleware'
}

Consuming Middleware

Using middleware is also simple. If we put our middleware in the HTTP_MIDDLEWARE constant then we don't have to worry about it anymore. It will run on every successful request, that is when a route match is found from our web.py file.

If we are using a route middleware, we'll need to specify which route should execute the middleware. To specify which route we can just append a .middleware() method onto our routes. This will look something like:

Get().route('/dashboard', 'DashboardController@show').name('dashboard').middleware('auth')
Get().route('/login', 'LoginController@show').name('login')

This will execute the auth middleware only when the user visits the /dashboard url and as per our middleware will be redirected to the named route of login

Awesome! You’re now an expert at how middleware works with Masonite.

Requests

Introduction

The Request class is initialized when the server first starts and is modified on every request. This means that the Request class acts as a singleton and is not reinitialized on every request. This presents both pros and cons during developing Masonite. It's great to not have to worry about a new object being instantiated everytime but the con is that some attributes need to be reset at the end of the request.

The Request class is loaded into the IOC container first so any Service Provider will have access to it. The IOC container allows all parts of the framework to be resolved by the IOC container and auto inject any dependencies they need.

Getting Started

The Request class is bound into the IOC container once when the server is first started. This takes the WSGI environment variables generated by your WSGI server as a parameter. Because of this, we reload the WSGI values on every request but the actual Request object does not change. In other words, the memory address of the Request object is always the same but the class attributes will change of every request. This is done already for you by the Masonite framework itself. This Request class is bound and initialized inside the AppProvider Service Provider. We grab this request object by simply passing in Request into the parameters of anything resolved by the Service Container such as middleware, drivers and controller methods like so:

def show(self, Request):
    # Request is the instance of the Request class
    pass

Masonite is smart enough to know that we need the Request class and it will inject it into our method for us.

Helper Function

Masonite ships with a HelpersProvider Service Provider which adds several helper functions. One of these helper functions is the request() function. This function will return the request object. Because of this, these two pieces of code are identical:

app/http/controllers/YourController.py
def show(self, Request):
    Request.input('username')
app/http/controllers/YourController.py
def show(self):
    request().input('username')

Notice we didn't import anything at the top of our file and also didn't retrieve any objects from the IOC container. Masonite helper functions act just like any other built in Python function.

Usage

The Request has several helper methods attached to it in order to interact with various aspects of the request.

In order to get the current request input variables such as the form data during a POST request or the query string during a GET request looks like:

app/http/controllers/YourController.py
def show(self, Request):
    Request.input('username')

There is no difference between any HTTP methods (GET, POST, PUT, etc) when it comes to getting input data. They are all retrieved through this .input() method so there is no need to make a distinction if the request is GET or POST

Method Options

Input Data

We can get all the request input variables such as input data from a form request or GET data from a query string. Note that it does not matter what HTTP method you are using, the input method will know what input data to get dependent on the current HTTP method (GET, POST, PUT, etc)

This will return all the available request input variables for that request as a dictionary.

app/http/controllers/YourController.py
# GET: /dashboard?user=Joe&status=1

def show(self, Request):
    return Request.all() # {'user': 'Joe', 'status': '1'}

To get a specific input:

app/http/controllers/YourController.py
# GET: /dashboard?firstname=Joe

def show(self, Request):
    return Request.input('firstname') # Joe

To check if some request input data exists:

app/http/controllers/YourController.py
# GET: /dashboard?firstname=Joe

def show(self, Request):
    return Request.has('firstname') # True

URL Parameters

To get the request parameter retrieved from the url. This is used to get variables inside: /dashboard/@firstname for example.

app/http/controllers/YourController.py
# GET: /dashboard/Joe

def show(self, Request):
    return Request.param('firstname') # Joe

Cookies

You may also set a cookie in the browser. The below code will set a cookie named key to the value of value.

By default, all cookies are encrypted with your secret key which is generated in your .env file when you installed Masonite. This is a security measure to ensure malicious Javascript code cannot fetch cookies if they are somehow retrieved. All cookies are set with the HTTP_ONLY flag meaning that Javascript cannot read them although you can turn this off using a parameter.

JSON Payloads

Sometimes you may want to handle incoming JSON requests. This could be form external API's like Github.

Masonite will detect that an incoming request is a JSON request and put the cast the JSON to a dictionary and load it into the payload request input. For example if you have an incoming request of:

incoming request
{
  "name": "Joe",
  "email": "Joe@email.com"
}

Then we can fetch this input in a controller like so:

app/http/controllers/YourController.py
def show(self, Request):
    Request.input('payload')['name'] # Joe

Creating

app/http/controllers/YourController.py
def show(self, Request):
    return Request.cookie('key', 'value')

Not Encrypting

If you choose to not encrypt your values and create cookies with the plain text value then you can pass a third value of True or False. You can also be more explicit if you like:

app/http/controllers/YourController.py
def show(self, Request):
    return Request.cookie('key', 'value', encrypt=False)

Expirations

All cookies are set as session cookies. This means that when the user closes out the browser completely, all cookies will be deleted.

app/http/controllers/YourController.py
def show(self, Request):
    return Request.cookie('key', 'value', expires="5 minutes")

This will set a cookie thats expires 5 minutes from the current time.

HttpOnly

Again, as a security measure, all cookies automatically are set with the HttpOnly flag which makes it unavailable to any Javascript code. You can turn this off:

app/http/controllers/YourController.py
def show(self, Request):
    return Request.cookie('key', 'value', http_only=False)

This will now allow Javascript to read the cookie.

Reading

You can get all the cookies set from the browser

app/http/controllers/YourController.py
def show(self, Request):
    return Request.get_cookies()

You can get a specific cookie set from the browser

app/http/controllers/YourController.py
def show(self, Request):
    return Request.get_cookie('key')

Again, all cookies are encrypted by default so if you set a cookie with encryption then this method will decrypt the cookie. If you set a cookie in plain text then you should pass the False as the second parameter here to tell Masonite not to decrypt your plain text cookie value.:

app/http/controllers/YourController.py
def show(self, Request):
    return Request.get_cookie('key', decrypt=False)

This will return the plain text version of the cookie.

If Masonite attempts to decrypt a cookie but cannot then Masonite will assume that the secret key that encrypted it was changed or the cookie has been tampered with and will delete the cookie completely.

If your secret key has been compromised then you may change the key at anytime and all cookies set on your server will be removed.

Deleting

You may also delete a cookie. This will remove it from the browser.

app/http/controllers/YourController.py
def show(self, Request):
    return Request.delete_cookie('key')

User

You can also get the current user from the request. This requires the LoadUserMiddleware middleware which is in Masonite by default. This will return an instance of the current user.

app/http/controllers/YourController.py
def show(self, Request):
    return Request.user()

Redirection

You can specify a url to redirect to

app/http/controllers/YourController.py
def show(self, Request):
    return Request.redirect('/home')

If the url contains http than the route will redirect to the external website

app/http/controllers/YourController.py
def show(self, Request):
    return Request.redirect('http://google.com')

You can redirect to a named route

app/http/controllers/YourController.py
def show(self, Request):
    return Request.redirectTo('dashboard')

You can also go back to a named route specified from the form input back. This will get the request input named back and redirect to that named route. This is great if you want to redirect the user to a login page and then back to where they came from. Just remember during your form submission that you need to supply a back input.

app/http/controllers/YourController.py
def show(self, Request):
    return Request.back()

This is equivalent to:

app/http/controllers/YourController.py
def show(self, Request):
    return Request.redirectTo(request.input('back'))

You can also specify the input parameter that contains the named route

app/http/controllers/YourController.py
def show(self, Request):
    return Request.back('redirect')

Sometimes your routes may require parameters passed to it such as redirecting to a route that has a url like: /url/@firstname:string/@lastname:string. For this you can use the send method. Currently this only works with named routes.

app/http/controllers/YourController.py
def show(self, Request):
    return Request.back().send({'firstname': 'Joseph', 'lastname': 'Mancuso'})

    return Request.redirectTo('dashboard').send({'firstname': 'Joseph', 'lastname': 'Mancuso'})

Encryption Key

You can load a specific secret key into the request by using:

Request.key(key)

This will load a secret key into the request which will be used for encryptions purposes throughout your Masonite project.

Note that by default, the secret key is pulled from your configuration file so you do NOT need to supply a secret key, but the option is there if you need to change it for testing and development purposes.

Headers

You can also get and set any headers that the request has.

You can get all WSGI information by printing:

app/http/controllers/YourController.py
def show(self, Request):
    print(Request.environ)

This will print the environment setup by the WSGI server. Use this for development purposes.

You can also get a specific header:

app/http/controllers/YourController.py
def show(self, Request):
    Request.header('AUTHORIZATION')

This will return whatever the HTTP_AUTHORIZATION header if one exists. If that does not exist then the AUTHORIZATION header will be returned. If that does not exist then None will be returned.

We can also set headers:

app/http/controllers/YourController.py
def show(self, Request):
    Request.header('AUTHORIZATION', 'Bearer some-secret-key')

Masonite will automatically prepend a HTTP_ to the header being set for standards purposes so this will set the HTTP_AUTHORIZATION header. If you do not want the HTTP prefix then pass a third parameter:

app/http/controllers/YourController.py
Request.header('AUTHORIZATION', 'Bearer some-secret-key', http_prefix=None)

This will set the AUTHORIZATION header instead of the HTTP_AUTHORIZATION header.

Status Codes

Masonite will set a status code of 404 Not Found at the beginning of every request. If the status code is not changed throughout the code, either through the developer or third party packages, as it passes through each Service Provider then the status code will continue to be 404 Not Found when the output is generated. You do not have to explicitly specify this as the framework itself handles status codes. If a route matches and your controller method is about to be hit then Masonite will set 200 OK and hit your route. This allows Masonite to specify a good status code but also allows you to change it again inside your controller method.

You could change this status code in either any of your controllers or even a third party package via a Service Provider.

For example, the Masonite Entry package sets certain status codes upon certain actions on an API endpoint. These can be 429 Too Many Requests or 201 Created. These status codes need to be set before the StartProvider is ran so if you have a third party package that sets status codes or headers, then they will need to be placed above this Service Provider in a project.

If you are not specifying status codes in a package and simple specifying them in a controller then you can do so freely without any caveats. You can set status codes like so:

Request.status('429 Too Many Requests')

This will set the correct status code before the output is sent to the browser. You can look up a list of HTTP status codes from an online resource and specify any you need to. There are no limitations to which ones you can use.

Changing Request Methods in Forms

Typically, forms only have support for GET and POST. You may want to change what HTTP method is used when submitting a form such as PATCH.

This will look like:

resources/templates/index.html
<form action="/dashboard" method="POST">
    <input type="hidden" name="request_method" value="PATCH">
</form>

or you can optionally use a helper method:

resources/templates/index.html
<form action="/dashboard" method="POST">
    {{ request_method('PATCH')|safe }}
</form>

When the form is submitted, it will process as a PUT request instead of a POST request.

This will allow this form to hit a route like this:

routes/web.py
from masonite.routes import Patch

ROUTES = [
    Patch().route('/dashboard', 'DashboardController@update')
]

About Managers

Introduction

Masonite uses an extremely powerful pattern commonly known as the Manager Pattern; also known as the Builder Pattern. Because Masonite uses classes with the XManager namespace, we will call it the Manager Pattern throughout this documentation.

Think of the Manager Pattern as attaching a Manager to a specific feature. This Manager is responsible for instantiating FeatureXDriver classes. For example, we attach a UploadManager to the upload feature. Now the UploadFeature will instantiate UploadXDriver classes.

For an actual example inside Masonite, there are currently two classes for the Upload feature: UploadDiskDriver and UploadS3Driver. Whenever we set the DRIVER in our config/storage.py file to s3, the UploadManager will use the UploadS3Driver to store our files.

This is extremely useful for extending functionality of the managers. If we need to upload to Google, we can just make a UploadGoogleDriver and put it inside the container. If we set our configuration DRIVER to google, our UploadManager will now use that class to store files.

Creating a Manager

Masonite obviously comes with several managers such as the UploadManager and the MailManager. Let's walk through how to create a new manager called the TaskManager.

Managers can live wherever they want but if you are developing a manager for the Masonite core package, they will be placed inside masonite/managers.

Let's create a new file: masonite/managers/TaskManager.py.

Great! Now all managers should inherit from the masonite.managers.Manager class. Our TaskManager should look something like:

Awesome! Inheriting from the Manager class will give our manager almost all the methods it needs. The only thing we need now is to tell this manager how to create drivers. So to do this all we need are two attributes:

Perfect. Managers are both extremely powerful and easy to create. That's it. That's our entire provider. The config attribute is the configuration file you want which is key in the container and the driver_prefix is the drivers you want to manager. In this case it is the TaskDriver. This manager will manage all drivers in the container that conform to the namespaces of Task{0}Driver like TaskTodoDriver and TaskNoteDriver.

Notice that the config is TaskConfig and not task. This attribute is the binding name and not the config name. We can bind the task config into the container like so:

Which will be required to use our new task manager since it relies on the task configuration. You can do this inside the Service Provider that will ship with this manager. We will create a Service Provider later on but for now just know that that's where that configuration comes from.

Using Our Manager

Great! We can put this Service Provider in our app/application.py file inside the PROVIDERS list. Once that is inside our providers list we can now use this new Task alias in our controllers like so:

Notice that we binded the TaskManager into the container under the Task key. Because of this we can now pass the Task in any parameter set that is resolved by the container like a controller method. Since we passed the Task into the parameter set, Masonite will automatically inject whatever the Task key from the container contains.

Part 1 - Creating Our First Route

Getting Started

All routes are located in routes/web.py and are extremely simple to understand. They consist of a request method and a route method. For example, to create a GET request it will look like:

We'll talk about the controller in a little bit.

Creating our Route:

We will start off by creating a view and controller to create a blog post.

A controller is a simple class that holds controller methods. These controller methods will be what our routes will call so they will contain all of our application logic.

Think of a controller method as a function in the views.py file if you are coming from the Django framework

Let's create our first route now. We can put all routes inside routes/web.py and inside the ROUTES list. You'll see we have a route for the home page. Let's add a route for creating blogs.

You'll notice here we have a BlogController@show string. This means "use the blog controller's show method to render this route". The only problem here is that we don't yet have a blog controller.

About Drivers

Introduction

Drivers are simply extensions to features that are managed by the Manager Pattern. If we have a UploadManager then we might also create a UploadDiskDriver and a UploadS3Driver which will be able to upload to both the file system (disk) and Amazon S3. In the future if we have to upload to Microsoft Azure or Google Cloud Storage then we simply create new drivers like UploadAzureDriver and UploadGoogleStorage which are very simple to create. Drivers can be as small as a single method or have dozens of methods. The Manager Pattern makes it dead simple to expand the functionality of a Manager and add capabilities to Masonite's features.

Creating a Driver

Let's go ahead and create a simple driver which is already in the framework called the UploadDiskDriver.

If you are creating a driver it can live wherever you like but if you are creating it for Masonite core then it should live inside masonite/drivers. For our UploadDiskDriver we will create the file: masonite/drivers/UploadDiskDriver.py.

We should make a class that looks something like:

Simple enough, now we can start coding what our API looks like. In the endgame, we want developers to do something like this from their controllers:

So we can go ahead and make a store method.

Ok great. Now here is the important part. Our Manager for this driver (which is the UploadManager) will resolve the constructor of this driver. This basically means that anything we put in our constructor will be automatically injected into this driver. So for our purposes of this driver, we will need the storage and the application configuration.

Now that we have our configuration we need injected into our class, we can go ahead and build out the store() method.:

Ok great! You can see that our store() method simply takes the file and write the contents of the fileitem to the disk.

Using Our Driver

That's it! Drivers are extremely simple and most drivers you create will be a simple class with a single method or two.

Introduction

Preface

We will be walking through step by step and go into great detail on each part we will be building.

Our application will consist of a blog that can create, read, update and delete posts. It will be very basic and won't have much styling. This is so you can get the basics down on the framework without too much HTML boilerplate code.

Hint Blocks

You'll see hint blocks that are green which you should follow if you want to learn more information about a topic.

You'll also see hint blocks that are blue. These should not be ignored and typically contain background information you need to further understand something.

Installation

Compiling Assets

Introduction

Understanding that modern frameworks need to handle modern web applications. Many developers are starting to use third party packages, like Sass, to write CSS. Normally, many people who write Sass in other frameworks will need to run other third party services like Webpack or grunt. Masonite tries to make this as simple as possible and comes with Sass built in. So you just need to write Sass and it will compile into CSS when you run the server.

Getting Started

Now although Masonite comes with the ability to compile Sass, it is deliberately missing the libsass dependency. This dependency takes several minutes to install and therefore was left out of the requirements to speed up the process of creating a new project.

Masonite can compile all of your Sass into CSS files when you run the server. The code needed to compile Sass is already inside the framework although it does not execute without libsass .

In order to activate this feature we can run:

Awesome! We're good to go. All Sass put inside resources/static will compile into resources/compiled .

Configuration

Masonite comes with a configuration file that will allow you to tweak the locations of files that Sass will be looked for, as well as where it will compile into. This setting page can be found in config/storage.py . The configuration constant looks something like:

ImportFrom Setting

This setting will look for base .sass and .scss files. Base Sass files are files without a preceding underscore. So style.scss is a base Sass file but _dashboard.scss is not. Once all the base Sass files are found, it will compile them into CSS and put them in the location specifed in the compileTo setting.

IncludePaths Setting

This setting is where Masonite will look for files anytime you want to include using the @include keyword in your sass files. Without the correct location here, Masonite will not find any files you include in your Sass files. This setting can be a list of directory locations.

CompileTo Setting

This setting specifies a single directory you want all of your Sass compiled down into.

This is all setup by default for you and works as soon as you install the libsass dependency.

Encryption

Introduction

Masonite comes with bcrypt out of the box but leaves it up to the developer to actually encrypt things like passwords. You can opt to use any other hashing library but bcrypt is the standard of a lot of libraries and comes with some one way hashing algorithms with no known vulnerabilities. Many of hashing algorithms like SHA-1 and MD5 are not secure and you should not use them in your application.

Background

Also, we make sure that Javascript cannot read your cookies. It's important to know that although your website may be secure, you are susceptible to attacks if you import third party Javascript packages (since those libraries could be hackable) which can read all cookies on your website and send them to the hacker.

Other frameworks use cryptographic signing which attached a special key to your cookies that prevents manipulation. This does't make sense as a major part of XSS protection is preventing third parties from reading cookies. It doesn't make sense to attach a digital signature to a plaintext cookie if you don't want third parties to see the cookie (such as a session id). Masonite takes one step further and encrypts the entire string and can only be decrypted using your secret key (so make sure you keep it secret).

Secret Key

In your .env file, you will find a setting called KEY=your-secret-key. This is the SALT that is used to encrypt and decrypt your cookies. It is important to change this key sometime before development. You can generate new secret keys by running:

This will generate a new key in your terminal which you can copy and paste into your .env file. Your config/application.py file uses this environment variable to set the KEY configuration setting.

Additionally you can pass the --store flag which will automatically set the KEY= value in your .env file for you:

Remember to not share this secret key as a loss of this key could lead to someone being able to decrypt any cookies set by your application. If you find that your secret key is compromised, just generate a new key.

Cryptographic Signing

You can use the same cryptographic signing that Masonite uses to encrypt cookies on any data you want. Just import the masonite.sign.Sign class. A complete signing will look something like:

By default, Sign() uses the encryption key in your config/application.py file but you could also pass in your own key.

Just remember to store the key you generated or you will not be able to decrypt any values that you encrypted.

Using bcrypt

Bcrypt is very easy to use an basically consists of a 1 way hash, and then a check to verify if that 1 way hash matches an input given to it.

It's important to note that any values passed to bcrypt need to be in bytes.

Hashing Passwords

Again, all values passed into bcrypt need to be in bytes so we can pass in a password:

Notice that the value passed in from the request was converted into bytes using the bytes() Python function.

Once the password is hashed, we can just safely store it into our database

Do not store unhashed passwords in your database. Also, do not use unsafe encryption methods like MD5 or SHA-1.

Checking Hashed Passwords

In order to check if a password matches it's hashed form, such as trying to login a user, we can use the bcrypt.checkpw() function:

This will return true if the string 'password' is equal to the models password.

More information on bcrypt can be found by reading it's documentation.

Authentication

Introduction

Masonite comes with some authentication out of the box but leaves it up to the developer to implement. Everything is already configured for you by default. The default authentication model is the app/User model but you can change this in the config/auth.py configuration file.

Configuration

There is only a single config/auth.py configuration file which you can use to set the authentication behavior of your Masonite project. If you wish to change the authentication model, to a app/Company model for example, feel free to do in this configuration file.

Authentication Model

Again the default authentication model is the app/User model which out of the box comes with a __auth__ class attribute. This attribute should be set to the column that you want to authenticate with. By default your app/User model will default to the email column but if you wish to change this to another column such as name, you can do so here. This will lead your model to look like:

All models that should be authenticated in addition to specifying a __auth__ attribute also needs to have a password field as well in order to use the out of the box authentication that comes with Masonite.

Authenticating a Model

If you want to authenticate a model, you can use the Auth facade that ships with Masonite. This is simply a class that is used to authenticate models with a .login() method.

In order to authenticate a model this will look like:

This will find a model with the supplied username, check if the password matches using bcrypt and return the model. If it is not found or the password does not match, it will return False.

Again all authenticating models need to have a password column. The column being used to authenticate, such as a username or email field can be specified in the model using the __auth__ class attribute.

Changing The Authentication Field

You may change the column to be authenticated by simply changing the column value of the __auth__ class attribute. This will look something like:

This will look inside the email column now and check that column and password. The authentication column is email by default.

Using Authentication

Retrieving the Authenticated User

Masonite ships with a LoadUser middleware that will load the user into the request if they are authenticated. Masonite uses the token cookie in order to retrieve the user using the remember_tokencolumn in the table.

Using this LoadUser middleware you can retrieve the current user using:

If you wish not to use middleware to load the user into the request you can get the request by again using the Auth class

Checking if the User is Authenticated

If you would like to simply check if the user is authenticated, Request.user() or Auth(Request).user() will return False if the user is not authenticated. This will look like:

Logging In By ID

Sometimes in your application you may want to sign in a user without the need for a username and password. This can be used on the administrative end of your application to login to other user's accounts to verify information.

This will login the user with the ID of 1 and skip over the username and password verification completely.

Logging In Once

All logging in methods will set a cookie in the user's browser which will then be verified on subsequent requests. Sometimes you may just want to login the user "once" which will authenticate the user but not set any cookies. We can use the once() method for this. This method can be used with all the previous methods by calling it first

This will return an instance of the user model just authenticated or it will return False if the authentication failed. This method will not save state.

This method is great for creating a stateless API or when you just need to verify a user with a username and password

Logging Out a User

If you wish to end the session for the user and log them out, you can do so by using the Auth class. This looks like:

This will delete the cookie that was set when logging in. This will not redirect the user to where they need to go. A complete logout view might look like:

Great! You’ve mastered how Masonite uses authentication. Remember that this is just out of the box functionality and you can create a completely different authentication system but this will suffice for most applications.

Protecting Routes

Masonite ships with an authentication middleware. You can use this middleware as a route middleware to protect certain routes from non authenticated users. This is great for redirecting users to a login page if they attempt to go to their dashboard.

You can use this middleware in your routes file like so:

By default this will redirect to the route named login. If you wish to redirect the user to another route or to a different URI, you can edit the middleware in app/http/middleware/AuthenticationMiddleware.py

Creating an Authentication System

You may of course feel free to roll your own authentication system if you so choose but Masonite comes with one out of the box but left out by default. In order to scaffold this authentication system you can of course use a craft command:

This will create some controllers, views and routes for you. This command should be used primarily on fresh installs of Masonite but as long as the controllers do not have the same names as the controllers being scaffolded, you will not have any issues.

The views scaffolded will be located under resources/templates/auth.

After you have ran the craft auth command, just run the server and navigate to http://localhost:8000/login and you will now have a login, registration and dashboard. Pretty cool, huh?

Read more about Service Providers under the documentation.

Jobs will be put inside the app/jobs directory. See the documentation for more information.

You may create a PyPi package with an added integrations.py file which is specific to Masonite. You can learn more about packages by reading the documentation. To create a package boilerplate, just run:

Masonite comes with a way to encrypt data and by default, encrypts all cookies set by the framework. Masonite uses a key to encrypt and decrypt data. Read the documentation for more information on encryption.

You can use whatever cache you like for caching templates. Only the disk driver is supported out of the box but you can create any drivers you like. Read the documentation on how to create drivers. If you do create a driver, consider making it available on PyPi so others can install it into their projects. If you'd like to contribute to the project and add to the drivers that come freshly installed with Masonite then please visit Masonite's GitHub repository and open an issue for discussing.

Framework hooks are essentially events that are emitted that you are able to "hook" into. If you want to add support for , which is an exception and error tracking solution, then you want to tie into the Exception Hook. When an exception is encountered it will look for your class in the container and execute it.

For the purposes of learning about Framework Hooks, we will walk through how to implement into a Masonite application by adding it to the Exception Hook.

Let's explore how we can simply add to our application. This should take about 5 minutes.

Ok great so now let's add sentry to our application. You can sign up with which will show you the basic 3 lines you need to add Sentry to any Python project.

That's it. The key in the client should be available through the dashboard.

That's it! Now everytime an exception is thrown, this will run our SentryHook class that we binded into the container and the exception should pop up in our dashboard.

If you are running a version of Masonite before 1.4 then check the upgrade guide for for learning how to upgrade.

Remember that anything that is resolved by the container is able to retrieve anything from the container by simply passing in parameters of objects that are located in the container. Read more about the container in the documentation.

Read more about this in the documentation.

Read more about the IOC container in the documentation.

Read more about helper functions in the documentation.

We can use our manager simply by loading it into the container. We can do this by creating a Service Provider. Learn more about how to create a Service Provider in the documentation. Let's show what a basic Service Provider might look like:

Read about how to create drivers for your Manager class under the documentation.

You can read more about routes in the documentation

Let's create the BlogController in the next step:

Depending on what type of driver you are making, you may need to inherit from a contract. To ensure this documentation is generalized, we'll leave out contracts for now. Contracts are essentially interfaces that ensure that your driver conforms to all other drivers of the same type. Read more about contracts in the documentation.

Great. If you're confused about how the dependency injection Service Container works then read the documentation.

So now that our driver is created, we can tell our Manager about it. Learn how to create managers under the documentation. Our manager will know of all drivers that are inside the Service Container. We can create a new service provider which we can use to register classes into our container. Here is an example of what the UploadProvider will look like:

Notice how we set our storage configuration in the container, binded our drivers and then binded our Manager. Again, our manager will be able to find all our UploadXDrivers that are loaded into the container. So if we set the DRIVER inside our configuration file to google, our manager will look for a UploadGoogleDriver inside our container. Read more about Managers in the documentation.

We will also roughly going over the but any extensive explanation you need will be found in the respective documentation locations.

This tutorial will assume you have already installed Masonite. If you haven't, be sure to read the guide to get a fresh install of Masonite up and running. Once you have one up and running or if you already have it running, go ahead and start at .

You can read the .

This feature uses for this kind of encryption. Because of this, we can generate keys using Fernet.

Service Provider
Queues and Jobs
Creating Packages
Encryption
About Drivers
Sentry
Sentry
Sentry
Sentry.io
Sentry.io
Sentry.io
Masonite 1.3 to 1.4
Service Container
Service Container
Service Container
Helper Functions
from masonite.managers.Manager import Manager

class TaskManager(Manager):
    pass
from masonite.managers.Manager import Manager

class TaskManager(Manager):

    config = 'TaskConfig'
    driver_prefix = 'Task'
from config import task

container.bind('TaskConfig', task)
from masonite.provider import ServiceProvider
from masonite.drivers.TaskTodoDriver import TaskTodoDriver
from masonite.managers.TaskManager import TaskManager
from config import task

class TaskProvider(ServiceProvider):

    wsgi = False

    def register(self):
        self.app.bind('TaskConfig', task)
        self.app.bind('TaskTodoDriver', TaskTodoDriver)
        self.app.bind('TaskManager', TaskManager(self.app))

    def boot(self, TaskManager, TaskConfig):
        self.app.bind('Task', TaskManager.driver(TaskConfig.DRIVER))
def show(self, Task):
    Task.method_here()
routes/web.py
Get().route('/url', 'Controller@method')
routes/web.py
ROUTES = [
    Get().route('/', 'WelcomeController@show').name('welcome'),

    # Blog
    Get().route('/blog', 'BlogController@show')
]
class UploadDiskDriver:
    pass
def show(self, Upload):
    Upload.store(request().input('file'))
class UploadDiskDriver:

    def store(self, fileitem, location=None):
        pass
class UploadDiskDriver:

    def __init__(self, StorageConfig, Application):
        self.config = StorageConfig
        self.appconfig = Application

    def store(self, fileitem, location=None):
        pass
class UploadDiskDriver:

    def __init__(self, StorageConfig, Application):
        self.config = StorageConfig
        self.appconfig = Application

    def store(self, fileitem, location=None):
        filename = os.path.basename(fileitem.filename)

        if not location:
            location = self.config.DRIVERS['disk']['location']

        location += '/'

        open(location + filename, 'wb').write(fileitem.file.read())

        return location + filename
from masonite.provider import ServiceProvider
from masonite.managers.UploadManager import UploadManager
from masonite.drivers.UploadDiskDriver import UploadDiskDriver
from config import storage


class UploadProvider(ServiceProvider):

    wsgi = False

    def register(self):
        self.app.bind('StorageConfig', storage)
        self.app.bind('UploadDiskDriver', UploadDiskDriver)
        self.app.bind('UploadManager', UploadManager(self.app))

    def boot(self, UploadManager, StorageConfig):
        self.app.bind('Upload', UploadManager.driver(StorageConfig.DRIVER))
pip install libsass
SASSFILES = {
    'importFrom': [
        'storage/static'
    ],
    'includePaths': [
        'storage/static/sass'
    ],
    'compileTo': 'storage/compiled'
}
$ craft key
$ craft key --store
from masonite.auth.Sign import Sign

sign = Sign()

sign.encrypt('value') # PSJDUudbs87SB....

sign.decrypt('value') # 'value'
from masonite.auth.Sign import Sign

encryption_key = b'SJS(839dhs...'

sign = Sign(encryption_key)

sign.encrypt('value') # PSJDUudbs87SB....

sign.decrypt('value') # 'value'
from masonite.auth.Sign import Sign
from cryptography.fernet import Fernet

encryption_key = Fernet.generate_key()

sign = Sign(encryption_key)

sign.encrypt('value') # PSJDUudbs87SB....

sign.decrypt('value') # 'value'
password = bcrypt.hashpw(
                bytes(request.input('password'), 'utf-8'), bcrypt.gensalt()
            )
User.create(
    name=request.input('name'),
    password=password,
    email=request.input('email'),
)
bcrypt.checkpw(bytes('password', 'utf-8'), bytes(model.password, 'utf-8'))
class User(Model):

    __fillable__ = ['name', 'email', 'password']

    __auth__ = 'name'
from masonite.facades.Auth import Auth

def show(self, Request):
    Auth(Request).login('user@email.com', 'password')
class User(Model):

    __fillable__ = ['name', 'email', 'password']

    __auth__ = 'email'
def show(self, Request):
    Request.user()
from masonite.facades.Auth import Auth

def show(self, Request):
    Auth(Request).user()
def show(self, Request):
    if Request.user():
        user_email = Request.user().email
def show(self, Auth, Request):
    Auth(Request).login_by_id(1):
def show(self, Auth, Request):
    Auth(Request).once().login_by_id(1)
    # or 
    Auth(Request).once().login('user@email.com', 'secret')
Auth(request).logout()
def logout(self, Request):
    Auth(Request).logout()
    return Request.redirect('/login')
Get().route('/dashboard', 'DashboardController@show').middleware('auth')
$ craft auth

Part 3 - Authentication

Getting Started

Most applications will require some form of authentication. Masonite comes with a craft command to scaffold out an authentication system for you. This should typically be ran on fresh installation of Masonite since it will create controllers routes and views for you.

For our blog, we will need to setup some a registration form so we can get new users to start posting to our blog. We can create an authentication system by running the craft command:

terminal
$ craft auth

We should get a success message saying that some new assets were created. You can check your controllers folder and you should see a few new controllers there that should handle registrations.

Database Setup

In order to register these users, we will need a database. Hopefully you already have some kind of local database setup like MySQL or Postgres. You can open up your MySQL client and create a database. You currently cannot create a database directly through Masonite.

Create a database and name it whatever you like. For the purposes of this tutorial, we can name it "blog"

Once that is done we just need to change a few environment variables so Masonite can connect to the database. These environment variable can be found in the .env file in the root of the project. Open that file up and you should see a few lines that look like:

.env
DB_DRIVER=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=masonite
DB_USERNAME=root
DB_PASSWORD=root

Go ahead and change those setting to your connection settings. The DB_DRIVER constant takes 3 values: mysql, postgres and sqlite.

Migrating

Once you have set the correct credentials, we can go ahead and migrate the database. Out of the box, Masonite has a migration for a users table which will be the foundation of our user. You can edit this user migration before migrating but the default configuration will suit most needs just fine and you can always add column at a later date.

terminal
$ craft migrate

This will create our users table for us along with a migrations table to keep track of any migrations we add later.

Creating Users

Now that we have the authentication and the migrations all migrated in, let's create our first user. Remember that we ran craft auth so we have a few new templates and controllers.

Go ahead and run the server:

terminal
$ craft serve

and head over to localhost:8000/register and fill out the form. You can use whatever name and email you like but for this purpose we will use:

Username: demo
Email: demo@email.com
Password: password

Once that's done we can move on to the next part.

Contracts

Introduction

Contracts are used when creating drivers to ensure they conform to Masonite requirements. They are a form of interface in other languages where the child class is required to have the minimum number of methods needed for that driver to work. It is a promise to the class that it has the exact methods required.

Contracts are designed to follow the "code to an interface and not an implementation" rule. While this rule is followed, all drivers of the same type are swappable.

Drivers are designed to easily switch the functionality of a specific feature such as switching from file system storage to Amazon S3 storage without changing any code. Because each driver someone creates can technically have whatever methods they want, this creates a technical challenge of having to change any code that uses a driver that doesn't support a specific method. For example a driver that does not have a store method while other drivers do will throw errors when a developer switches drivers.

Contracts ensure that all drivers of a similar type such as upload, queue and mail drivers all contain the same methods. While drivers that inherit from a contract can have more methods than required, they should not.

Getting Started

Contracts are currently used to create drivers and are located in the masonite.contracts namespace. Creating a driver and using a contract looks like:

from masonite.contracts.UploadContract import UploadContract

class UploadGoogleDriver(UploadContract):
    pass

Now this class will constantly throw exceptions until it overrides all the required methods in the class.

Contracts

There are several contracts that are required when creating a driver. If you feel like you need to have a new type of driver for a new feature then you should create a contract first and code to a contract instead of an implementation. Below are the types of contracts available. All contracts correspond to their drivers. So an UploadContract is required to create an upload driver.

BroadcastContract

CacheContract

MailContract

QueueContract

UploadContract

Service Providers
About Drivers
Routing
Part 2 - Creating Our First Controller
Contracts
Service Container
About Managers
About Managers
Service Container
Installation
Part 1 of the tutorial
bcrypt documentation here
pyca/cryptography

Database Migrations

Introduction

Database migrations in Masonite is very different than other Python frameworks. Other Python frameworks create migrations based on a model which historically uses Data Mapper type ORM's. Because Masonite uses an Active Record ORM by default, Migrations are completely separated from models. This is great as it allows a seamless switch of ORM's without interfering with migrations. In addition to creating this separation of migrations and models, it makes managing the relationship between models and tables extremely basic with very little magic which leads to faster debugging as well as fewer migration issues.

In this documentation, we'll talk about how to make migrations with Masonite.

Getting Started

Because models and migrations are separated, we never have to touch our model in order to make alterations to our database tables. In order to make a migration we can run a craft command:

$ craft migration name_of_migration_here --table dashboard

This command will create a migration for an existing table. A migration on an existing table will migrate into the database in a certain way so it's important to specify the --table flag in the command.

In order to create a migration file for a new table that doesn't yet exist (but will after the migration) you can instead use the --create flag like so:

$ craft migration name_of_migration_here --create dashboard

This will create a migration that will create a table, as well as migrate the columns you specify.

Migrating Columns

Inside the migration file you will see an up() method and a down() method. We are only interested in the up() method. This method specifies what to do when the migration file is migrated. The down() method is what is executed when the migration is rolled back. Lets walk through creating a blog migration.

$ craft migration create_blogs_table --create blogs

This will create a migration file located in databases/migrations . Lets open this file and add some columns.

After we open it we should see something an up() method that looks like this:

def up(self):
        """
        Run the migrations.
        """
        with self.schema.create('blogs') as table:
            table.increments('id')
            table.timestamps()

Inside our with statement we can start adding columns.

Lets go ahead and add some columns that can be used for a blog table.

def up(self):
        """
        Run the migrations.
        """
        with self.schema.create('blogs') as table:
            table.increments('id')
            table.string('title')
            table.text('body')
            table.integer('active')
            table.integer('user_id').unsigned()
            table.foreign('user_id').references('id').on('users')
            table.timestamps()

Ok let's go ahead and break down what we just created.

Foreign Keys

So adding columns is really straight forward and Orator has some great documentation on their website. In order to add a foreign key, we'll need an unsigned integer column which we specified above called:

table.integer('user_id').unsigned()

This will set up our column index to be ready for a foreign key. We can easily specify a foreign key by then typing

table.foreign('user_id').references('id').on('users')

What this does is sets a foreign key on the user_id column which references the id column on the users table. That's it! It's that easy and expressive to set up a foreign key.

Changing Columns

There are two types of columns that we will need to change over the course of developing our application. Changing columns is extremely simple. If you're using MySQL 5.6.6 and below, see the caveat below.

To change a column, we can just use the .change() method on it. Since we need to create a new migration to do this, we can do something like:

$ craft migration change_default_status --table dashboard

and then simply create a new migration but use the .change() method to let Masonite you want to change an existing column instead of adding a new one:

table.integer('status').nullable().default(0).change()

When we run craft migrate it will change the column instead of adding a new one.

Changing Foreign Keys Prior to MySQL 5.6.6

Because of the constraints that foreign keys put on columns prior to MySQL 5.6.6, it's not as straight forward as appending a .change() to the foreign key column. We must first:

  • drop the foreign key relationship

  • change the column

  • recreate the foreign key

We can do this simply like so:

table.drop_foreign('posts_user_id_foreign')
table.rename_column('user_id', 'author_id')
table.foreign('author_id').references('id').on('users')

Part 2 - Creating Our First Controller

Getting Started

All controllers are located in the app/http/controllers directory and Masonite promotes 1 controller per file. This has proven efficient for larger application development because most developers use text editors with advanced search features such as Sublime, VSCode or Atom. Switching between classes in this instance is simple and promotes faster development. It's easy to remember where the controller exactly is because the name of the file is the controller.

You can of course move controllers around wherever you like them but the craft command line tool will default to putting them in separate files.

Creating Our First Controller

Like most parts of Masonite, you can scaffold a controller with a craft command:

terminal
$ craft controller Blog

This will create a controller in app/http/controllers that looks like:

app/http/controller/BlogController.py
class BlogController:
    ''' Class Docstring Description '''

    def show(self):
        pass

Simple enough, right? You'll notice we have a show method. These are called "controller methods" and are similiar to what Django calls a "view."

Notice we now have our show method that we specified in our route.

Returning a View

We can return a view from our controller. A view in Masonite are html files. They are not Python objects themselves like other Python frameworks. Views are what the users will see. We can return a view by using the view() function:

app/http/controllers/BlogController.py
def show(self):
    return view('blog')

Notice here we didn't import anything. Masonite comes with several helper functions that act like built in Python functions. These helper functions make developing with Masonite really efficient.

Creating Our View

You'll notice now that we are returning the blog view but it does not exist.

All views are in the resources/templates directory. We can create a new file called resources/templates/blog.html or we can use another craft command:

terminal
$ craft view blog

This will create that file for us.

If we put some text in this file like:

resources/templates/blog.html
This is a blog

and then run the server

terminal
$ craft serve

and open up localhost:8000/blog, we will see "This is a blog"

In the next part we will start designing our blog application

We can use in order to build our migration file. First lets run a migration craft command to create a blog table:

Notice that we are using a context processor which is our schema builder. All we have to worry about is whats inside it. Notice that we have a table object that has a few methods that are related to columns. Most of these columns are pretty obvious and you can read about different you can use. We'll mention the foreign key here though.

Check the for more information on creating a migration file.

You can learn more about helper functions in the documentation

Orators Schema Builder
Orator Schema Columns
Orator documentation
Helper Functions

Part 5 - Models

Getting Started

Now that we have our tables and migrations all done and we have a posts table, let's create a model for it.

Models in Masonite are a bit different than other Python frameworks. Masonite uses Orator which is an Active Record implementation of an ORM. This bascially means we will not be building our model and then translating that into a migration. Models and migrations are separate in Masonite. Our models will take shape of our tables regardless of what the table looks like.

Creating our Model

Again, we can use a craft command to create our model:

terminal
$ craft model Post

Notice we used the singular form for our model. By default, Orator will check for the plural name of the class in our database (in this case posts) by simply appending an "s" onto the model. We will talk about how to specify the table explicitly in a bit.

The model created now resides inside app/Post.py and when we open it up it should look like:

app/Post.py
''' A Post Database Model '''
from config.database import Model

class Post(Model):
    pass

Simple enough, right? Like previously stated, we don't have to manipulate the model. The model will take shape of the table as we create or change migrations.

Table Name

Again, the table name that the model is attached to is the plural version of the model (by appending an "s") but if you called your table something different such as "blog" instead of "blogs" we can specify the table name explicitly:

app/Post.py
''' A Post Database Model '''
from config.database import Model

class Post(Model):
    __table__ = 'blog'

Mass Assignment

Orator by default protects against mass assignment as a security measure so we will explicitly need to set what columns we would like to be fillable:

app/Post.py
''' A Post Database Model '''
from config.database import Model

class Post(Model):
    __fillable__ = ['title', 'author_id', 'body']

Relationships

The relationship is pretty straight forward here. Remember that we created a foreign key in our migration. We can create that relationship in our model like so:

app/Post.py
''' A Post Database Model '''
from config.database import Model
from orator.orm import belongs_to

class Post(Model):
    __fillable__ = ['title', 'author_id', 'body']

    @belongs_to('author_id', 'id')
    def author(self):
        from app.User import User
        return User

Because of how Masonite does models, it is typically better to perform the import inside the relationship like we did above. If you import at the top you may encounter circular imports.

Part 7 - Showing Our Posts

Getting Started

Lets go ahead and show how we can show the posts we just created. In this part we will create 2 new templates to show all posts and a specific post.

Creating The Templates

Let's create 2 new templates.

Let's start with showing all posts

Creating The Controller

Let's create a controller for the posts to separate it out from the BlogController.

Great! So now in our show method we will show all posts and then we will create a single method to show a specific post.

Show Method

Let's get the show method to return the posts view with all the posts:

Posts Route

We need to add a route for this method:

Posts View

Our posts view can be very simple:

Go ahead and run the server and head over to localhost:8000/posts route. You should see a basic representation of your posts. If you only see 1, go to localhost:8000/blog to create more so we can show an individual post.

Showing The Author

Remember we made our author relationship before. Orator will take that relationship and make an attribute from it so we can display the author's name as well:

Let's repeat the process but change our workflow a bit.

Single Post Route

Next we want to just show a single post. We need to add a route for this method:

Notice here we have a @id string. We can use this to grab that section of the URL in our controller in the next section below.

Single Method

Let's create a single method so we show a single post.

We use the param method to fetch the id from the URL. Remember this key was set in the route above when we specified the @id

For a real application we might do something like @slug and then fetch it with request().param('slug').

Single Post View

We just need to display 1 post so lets just put together a simple view:

Go ahead and run the server and head over the localhost:8000/post/1 route and then localhost:8000/post/2 and see how the posts are different.

We won't go into much more detail here about different types of relationships but to learn more, read the documentation.

ORM
terminal
$ craft view posts
$ craft view single
terminal
$ craft controller Post
app/http/controllers/PostController.py
from app.Post import Post

...

def show(self):
    posts = Post.all()
    
    return view('posts', {'posts': posts})
routes/web.py
Get().route('/posts', 'PostController@show')
resources/templates/posts.html
{% for post in posts %}
    {{ post.title }}
    <br>
    {{ post.body }}
    <hr>
{% endfor %}
resources/templates/posts.html
{% for post in posts %}
    {{ post.title }} by {{ post.author.name }}
    <br>
    {{ post.body }}
    <hr>
{% endfor %}
routes/web.py
Get().route('/post/@id', 'PostController@single')
app/http/controllers/PostController.py
from app.Post import Post

...

def single(self):
    post = Post.find(request().param('id'))
    
    return view('single', {'post': post})
resources/templates/single.html
{{ post.title }}
<br>
{{ post.body }}
<hr>

Part 8 - Updating and Deleting Posts

Getting Started

By now, all of the logic we have gone over so far will take you a long way so let's just finish up quickly with updating and deleting a posts. We'll assume you are comfortable with what we have learned so far so we will run through this faster since this is just more of what were in the previous parts.

Update Controller Method

Let's just make an update method on the PostController:

app/http/controllers/PostController.py
def update(self):
    post = Post.find(request().param('id'))
    
    return view('update', {'post': post})
    
def store(self):
    post = Post.find(request().param('id'))
    
    post.title = request().input('title')
    post.body = request().input('body')
    
    post.save()
    
    return 'post updated'

Since we are more comfortable with controllers we can go ahead and make two at once. We made one that shows a view that shows a form to update a post and then one that actually updates the post with the database.

Create The View

$ craft view update
resources/templates/update.html
<form action="/post/{{ post.id }}/update" action="POST">
    {{ csrf_field|safe }}

    <label for="">Title</label>
    <input type="text" name="title"><br>

    <label>Body</label>
    <textarea name="body"></textarea><br>

    <input type="submit" value="Update">
</form>

Create The Routes:

Remember we made 2 controller methods so let's attach them to a route here:

routes/web.py
Get().route('/post/@id/update', 'PostController@update'),
Post().route('/post/@id/update', 'PostController@store'),

That should be it! We can now update our posts.

Delete Method

Let's expand a bit and made a delete method.

app/http/controllers/PostController.py
def delete(self):
    post = Post.find(request().param('id'))
    
    post.delete()
    
    return 'post deleted'

Make the route:

routes/web.py
Get().route('/post/@id/delete', 'PostController@delete'),

Notice we used a GET route here, It would be much better to use a POST method but for simplicity sake will assume you can create one by now. We will just add a link to our update method which will delete the post.

Update the Template

We can throw a delete link right inside our update template:

resources/templates/update.html
<form action="/post/{{ post.id }}/update" action="POST">
    {{ csrf_field|safe }}

    <label for="">Title</label>
    <input type="text" name="title"><br>

    <label>Body</label>
    <textarea name="body"></textarea><br>

    <input type="submit" value="Update">
    
    <a href="/post/{{ post.id }}/delete"> Delete </a>
</form>

Great! You now have a blog that you can use to create, view, update and delete posts! Go on to create amazing things!

Part 6 - Designing Our Blog

Getting Started

Let's setup a little HTML so we can learn a bit more about how views work. In this part we will setup a really basic template in order to not clog up this part with too much HTML but we will learn the basics enough that you can move forward and create a really awesome blog template (or collect one from the internet).

Now that we have all the models and migrations setup, we have everything in the backend that we need to create layout and start creating and updating blog posts.

We will check if the user logged in before creating a template.

The Template For Creating

The template for creating will be located at /blog/create and will be a simple form for creating a blog post

resources/templates/blog.html
<form action="/blog/create" method="POST">
    {{ csrf_field|safe }}

    <input type="name" name="title">
    <textarea name="body"></textarea>
</form>

Notice here we have this strange {{ csrf_field|safe }} looking text. Masonite comes with CSRF protection so we need a token to render with the CSRF field.

Now because we have a foreign key in our posts table, we need to make sure the user is logged in before creating this so let's change up our template a bit:

resources/templates/blog.html
{% if auth() %}
    <form action="/blog/create" method="POST">
        {{ csrf_field|safe }}

        <label> Title </label>
        <input type="name" name="title"><br>

        <label> Body </label>
        <textarea name="body"></textarea>

        <input type="submit" value="Post!">
    </form>
{% else %}
    <a href="/login">Please Login</a>
{% endif %}

Static Files

For simplicity sake, we won't by styling our blog with something like Bootstrap but it is important to learn how static files such as CSS files work with Masonite so let's walk through how to add a CSS file and add it to our blog.

Firstly, head to storage/static/ and make a blog.css file and throw anything you like in it. For this tutorial we will make the html page slightly grey.

storage/static/blog.css
html {
    background-color: #ddd;
}

Now we can add it to our template like so:

resources/templates/blog.html
<link href="/static/blog.css" rel="stylesheet">
{% if auth() %}
    <form action="/blog/create" method="POST">
        {{ csrf_field|safe }}

        <label> Title </label>
        <input type="name" name="title"><br>

        <label> Body </label>
        <textarea name="body"></textarea>

        <input type="submit" value="Post!">
    </form>
{% else %}
    <a href="/login">Please Login</a>
{% endif %}

That's it. Static files are really simple. It's important to know how they work but for this tutorial we will ignore them for now and focus on more of the backend.

Javascript files are the same exact thing:

resources/templates/blog.html
<link href="/static/blog.css" rel="stylesheet">
{% if auth() %}
    <form action="/blog/create" method="POST">
        {{ csrf_field|safe }}

        <label> Title </label>
        <input type="name" name="title"><br>

        <label> Body </label>
        <textarea name="body"></textarea>

        <input type="submit" value="Post!">
    </form>
{% else %}
    <a href="/login">Please Login</a>
{% endif %}

<script src="/static/script.js"></script>

The Controller For Creating And The Container

Notice that our action is going to /blog/create so we need to direct a route to our controller method. In this case we will direct it to a store method.

Let's open back up routes/web.py and create a new route. Just add this to the ROUTES list:

routes/web.py
Post().route('/blog/create', 'BlogController@store'),

and create a new store method on our controller:

app/http/controllers/BlogController.py
....
def show(self): 
    return view('blog')

# New store Method
def store(self): 
     pass

Now notice above in the form we are going to be receiving 2 form inputs: title and body. So let's import the Post model and create a new post with the input.

app/http/controllers/BlogController.py
from app.Post import Post
...

def store(self, Request):
    Post.create(
        title=Request.input('title'),
        body=Request.input('body'),
        author_id=Request.user().id
    )

    return 'post created'

More likely, you will use the request helper and it will look something like this instead:

app/http/controllers/BlogController.py
from app.Post import Post
...

def store(self):
    Post.create(
        title=request().input('title'),
        body=request().input('body'),
        author_id=request().user().id
    )

    return 'post created'

Also notice we used an input() method. Masonite does not discriminate against different request methods so getting input on a GET or a POST request doesn't matter. You will always use this input method.

Go ahead and run the server using craft serve and head over to localhost:8000/blog and create a post. This should hit the /blog/create route with the POST request method and we should see "post created".

Part 4 - Migrations

Getting Started

Now that we have our authentication setup and we are comfortable with migrating our migrations, let's create a new migration where we will store our posts.

Our posts table should have a few obvious columns that we will simplify for this tutorial part. Let's walk through how we create migrations with Masonite

Craft Command

This command simply creates the basis of a migration that will create the posts table. By convention, table names should be plural (and model names should be singular but more on this later).

This will create a migration in the databases/migrations folder. Let's open that up and starting on line 6 we should see look like:

Lets add a title, an author, and a body to our posts tables.

Now we can migrate this migration to create the posts table

Masonite uses Jinja2 templating so if you don't understand this templating, be sure to .

For more information on static files, checkout the documentaton.

Notice that we used Request here. This is the Request object. Where did this come from? This is the power and beauty of Masonite and your first introduction to the . The is an extremely powerful implementation as allows you to ask Masonite for an object (in this case Request) and get that object. This is an important concept to grasp so be sure to read the documentation further.

Read more about the here.

Notice we used the request() function. This is what Masonite calls which speed up development. We didn't import anything but we are able to use them. This is because Masonite ships with a that adds builtin functions to the project.

Not surprisingly, we have a craft command to create migrations. You can read more about but we'll simplify it down to the command and explain a little bit of what's going on:

This should be fairly straight forward but if you want to learn more, be sure to read the documentation.

read their documentation
Static Files
Service Container
Service Container
Service Container
Helper Functions
Service Provider
terminal
$ craft migration create_posts_table --create posts
databases/migrations/2018_01_09_043202_create_posts_table.py
def up(self):
    """
    Run the migrations.
    """
    with self.schema.create('posts') as table:
        table.increments('id')
        table.timestamps()
databases/migrations/2018_01_09_043202_create_posts_table.py
def up(self):
    """
    Run the migrations.
    """
    with self.schema.create('posts') as table:
        table.increments('id')
        table.string('title')

        table.integer('author_id').unsigned()
        table.foreign('author_id').references('id').on('users')

        table.string('body')
        table.timestamps()
terminal
$ craft migrate
Database Migrations here
Database Migrations