Only this pageAll pages
Powered by GitBook
1 of 93

v2.3

Loading...

Loading...

Loading...

The Basics

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

The Craft Command

Loading...

Loading...

Architectural Concepts

Loading...

Loading...

Loading...

Advanced

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Useful Features

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Security

Loading...

Loading...

Loading...

Loading...

Loading...

Orator ORM

Loading...

Managers and Drivers

Loading...

Loading...

Loading...

Official Packages

Loading...

Loading...

Loading...

Loading...

Masonite Essentials

Loading...

Tutorials

Loading...

How-to Guides

Deployment

Loading...

Loading...

Masonite ORM [In development]

Loading...

Prologue

Loading...

Loading...

Loading...

Loading...

Loading...

What's New

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Upgrade Guide

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Controllers

Introduction

Controllers are a vital part of Masonite and is mainly what differs it from other Python frameworks which all implement the MVC structure differently. Controllers are simply classes with methods. These methods take a self parameter which is the normal self that Python 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 a full class they can manipulate however they want. In other words, controller methods may utilize class attributes, private methods and class constructors to break up and abstract 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. This is very opionated but you can obviously put 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:

terminal
$ craft controller Dashboard -r

or

terminal
$ 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. Our controller methods at a minimum should look like:

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

If you are new to Python, 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.

Container Resolving

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

app/http/controllers/DashboardController.py
from masonite.request import Request
...

def show(self, request: Request):
    print(request) # Grabbed the Request object from the container

or by specifying them in the constructor:

app/http/controllers/DashboardController.py
from masonite.request import Request

class DashboardController:

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

    def show(self):
        print(self.request) # Grabbed the Request object from the container

If you need a class in multiple controller methods then it is recommended to put it into the constructor in order to keep the controller DRY.

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.

Returning JSON

You can return JSON in a few different ways. The first way is returning a dictionary which will then be parsed to JSON:

app/http/controllers/DashboardController.py
def show(self):
    return {'key': 'value'}

you may return a list:

app/http/controllers/DashboardController.py
def show(self):
    return ['key', 'value']

Or you may even return a model instance or collection. Take these 2 code snippets as an example:

app/http/controllers/DashboardController.py
from app.User import User

def show(self):
    return User.find(1)

or

app/http/controllers/DashboardController.py
from app.User import User

def show(self):
    return User.where('active', 1).get()

Returning a paginated response directly will include the collection data, along with results metadata.

Query parameters accepted are: 'page_size' and 'page'. These will be handled directly as part of the response, there is no need to pass them in explicitly.

Orator classes LengthAwarePaginator and Paginator will return slightly different responses.

LengthAwarePaginator:

from app.User import User

# with query params ?page_size=10&page=2
def index(self, request: Request):
    return User.paginate()

returns:

{
  "total": 44,
  "count": 10,
  "per_page": 10,
  "current_page": 2,
  "last_page": 5,
  "from": 11,
  "to": 20,
  "data": [ ... ]
}

Paginator:

from app.User import User

# no query params, therefore uses Orator default (15, 1)
def index(self, request: Request):
    return User.simple_paginate()

returns:

{
  "count": 15,
  "per_page": 15,
  "current_page": 1,
  "from": 1,
  "to": 15,
  "data": [ ... ]
}

Passing Route Parameters

Optionally you can pass route parameters along with your resolving code. This is useful to keep a nice clean codebase.

For example, these two code snippets are the same:

app/http/controllers/DashboardController.py
from masonite.request import Request
from masonite.view import View
...

def show(self, request: Request, view: View):
    return User.find(request.param('user_id'))

And this:

app/http/controllers/DashboardController.py
from masonite.view import View
...

def show(self, user_id, view: View):
    return User.find(user_id)

You can specify parameters along with any other container resolving.

Helper Functions

Built in global helper functions were removed by default in v2.1 though they are not deprecated and you can use them as you wish.

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 which we call "Built in Helper Functions" which you may see them referred to as.

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.

You can continue to use these helper functions as much as you like but most developers use these to quickly mock things up and then come back to refactor later.

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

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

Built In Helpers

The below list of helpers are "builtin" helpers meaning they are global in the same way that the print method is global. These helpers can be used without any imports.

Request

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

is exactly the same as:

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.

is exactly the same as:

Mail

Instead of resolving the mail class you can use the mail helper:

is exactly the same as:

Auth

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

is exactly the same as:

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

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

Container

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

is exactly the same as:

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.

is exactly the same as:

Resolve

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

is exactly the same as:

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

Die and Dump

Die and dump is a common way to debug objects in PHP and other programming languages. Laravel has the concept of dd() which dies and dumps the object you need to inspect.

dd() is essentially adding a break point in your code which dumps the properties of an object to your browser.

For example we can die and dump the user we find:

You may also specify several parameters for see several values dumped at once:

If we then go to the browser and visit this URL as normal then we can now see the object fully inspected which will kill the script wherever it is in place and throw an exception but instead of showing the normal debugger it will use a custom exception handler and show the inspection of the object instead:

Non Built In Helpers

There are several helper methods that require you to import them in order to use them. These helpers are not global like the previous helpers.

Config

The config helper is used to get values in the config directory. For example in order to get the location in the config/storage.py file for example.

This function can be used to retrieve values from any configuration file but we will use the config/storage.py file as an example.

With a config/storage.py file like this:

We can get the value of the west key in the location inner dictionary like so:

Instead of importing the dictionary itself:

Note the use of the lowercase storage.drivers.s3 instead of storage.DRIVERS.s3. Either or would work because the config function is uppercase and lowercase insensitive.

Optional

This helper that allows you to wrap any object in this helper and call attributes or methods on it even if they don't exist. If they exist then it will return the method, if it doesn't exist it will return None.

Take this example where we would normally write:

We can now use this code snippet instead:

Compact

Compact is a really nice helper that allows you to stop making those really repetitive dictionary statements in your controller methods

take this for example:

Notice how our Python variables are exactly the same as what we want our variables to be in our template.

With the compact function, now you can do:

You can also pass in a dictionary which will update accordingly:

Collect

You can use the same Collection class that orator uses when returning model collections. This can be used like so:

You have access to all the methods on a normal collection object.

Query String

Masonite uses this helper internally but if you find a need to parse query strings in the same way Masonite does then you can use this helper like this:

Sponsors

Gold Sponsors

Current Sponsors

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 some form of HTTP route classes (like Get() or Post()). At the bare minimum, a route will look like:

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:

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:

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:

Route Groups

Some routes may be very similar. We may have a group of routes under the same domain, uses the same middleware or even start with the same prefixes. In these instances we should group our routes together so they are more DRY and maintainable.

We can add route groups like so:

This alone is great to group routes together that are similar but in addition to this we can add specific attributes to the entire group like adding middleware:

In the case you are only using one middleware:

The , at the end of 'auth' ensures that it's treated as a tuple and not as an array of strings.

In this instance we are adding these 2 middleware to all of the routes inside the group. We have access to a couple of different methods. Feel free to use some or all of these options:

The prefix parameter will prefix that URL to all routes in the group as well as the name parameter. The code above will create routes like /dashboard/url1 with the name of post.create. As well as adding the domain and middleware to the routes.

All of the options in a route group are named parameters so if you think adding a groups attribute at the end is weird you can specify them in the beginning and add the routes parameter:

Multiple Route Groups

Even more awesome is the ability to nest route groups:

This will go to each layer and generate a route list essentially from the inside out. For a real world example we refactor routes from this:

into this:

This will likely be the most common way to build routes for your application.

View Routes

You can also use View routes which is just a method on the normal route class:

You can use this view method with any route class.

Redirect Route

You can also redirect right from the routes list using a Redirect route class:

You do not have to specify the last 2 parameters. The default is a 302 response on GET methods.

Match Routes

You may have noticed above that we have a Match route class. This can match several incoming request methods. This is useful for matching a route with both PUT and PATCH.

The request methods are not case sensitive. They will be converted to uppercase on the backend. So ['Put', 'Patch'] will work just fine

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:

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:

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:

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:

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

You can also import the class directly and reference the method you want to use:

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:

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:

Optional Route Parameters

Sometimes you want to optionally match routes and route parameters. For example you may want to match /dashboard/user and /dashboard/user/settings to the same controller method. In this event you can use optional parameters which are simply replacing the @ with a ?:

You can also set default values if the route is not hit:

Route Compilers

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.

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:

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

These are called "Route Compilers" because they compile the route differently depending on what is specified. If you specify :int or :integer it will compile to a different regex than if you specified :string.

Adding Route Compilers

We can add route compilers to our project by specifying them in a Service Provider.

Make sure you add them in a Service Provider where wsgi is False. We can add them on the Route class from the container using the compile method. A completed example might look something like this:

We just need to call the compile() method on the Route class and make sure we specify a regex string by preceding an r to the beginning of the string.

Your regex should be encapsulated in a group. If you are not familiar with regex, this basically just means that your regex pattern should be inside parenthesis like the example above.

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.

Out of the box this feature will not work and is turned off by default. We will need to add a call on the Request class in order to activate subdomains. We can do this in the boot method of one of our Service Providers that has wsgi=False:

To use subdomains we can use the .domain() method on our routes like so:

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 any subdomain. For this we can pass in an asterisk instead.

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:

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

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

To become a sponsor head to the page.

To become a gold sponsor head to the page.

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

Service Container
Views
masonite.providers.HelpersProvider
class HelpersProvider(ServiceProvider):

    wsgi = False

    def register(self):
        pass

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

        view.share({'request': request.helper, 'auth': request.user})
def show(self):
    request().input('id')
def show(self, request: Request):
    request.input('id')
def show(self):
    return view('template_name')
def show(self, view: View):
    return view.render('template_name')
def show(self):
    mail_helper().to(..)
from masonite import Mail

def show(self, mail: Mail):
    mail.to(..)
def show(self):
    auth().id
def show(self, request: Request):
    request.user().id
def show(self):
    if auth():
        auth().id
def show(self):
    container().make('User')
def show(self, request: Request):
    request.app().make('User')
def show(self):
    env('S3_SECRET', 'default')
import os

def show(self):
    os.environ.get('S3_SECRET', 'default')
def some_function(request: Request):
    print(request)

def show(self):
    resolve(some_function)
def some_function(request: Request):
    print(request)

def show(self, request: Request):
    request.app().resolve(some_function)
from app.User import User

def show(self):
    dd(User.find(7))
from app.User import User

def show(self, request: Request):
    dd(request, User.find(7))
config/storage.py
DRIVERS = {
    's3': {
        'client': 'Hgd8s...'
        'secret': 'J8shk...'
        'location': {
            'west': 'http://west.amazon.com/..'
            'east': 'http://east.amazon.com/..'            
        }
    }
}
from masonite.helpers import config

def show(self):
    west = config('storage.drivers.s3.location.west')
from config import storage

def show(self):
    west = storage.DRIVERS['s3']['location']['west']
def show(self):
    user = User.find(1)
    if user and user.id == 5:
        # do code
        ...
def show(self):
    if optional(User.find(1)).id == 5:
        # do code
        ...
def show(self, view: View):
    posts = Post.all()
    users = User.all()
    articles = Articles.all()
    return view.render('some.template', {'posts': posts, 'users': users, 'articles': articles})
from masonite.helpers import compact

def show(self, view: View):
    posts = Post.all()
    users = User.all()
    articles = Articles.all()
    return view.render('some.template', compact(posts, users, articles))
from masonite.helpers import compact

def show(self, view: View):
    posts = Post.all()
    users = User.all()
    user_blogs = Blog.where('user_id', 1).get()
    return view.render('some.template', compact(posts, users, {'blogs': user_blogs}))
from masonite.helpers import collect

def show(self):
    collection = collect([1,2,3])

    if collection.first() == 1:
        # do action
from masonite.helpers import query_string
qs = "param=value&filters[name]=Joe&filters[age]=25"

query_string(qs)
"""
{
    "param": "value",
    "filters": {
        "name": "Joe",
        "age": "25"
    }
}
"""
routes/web.py
Get('/url/here', 'WelcomeController@show')
routes/web.py
Post('/url/here', 'WelcomeController@store')
routes/web.py
...
from app.http.controllers.DashboardController import DashboardController


ROUTES = [
    Get('/url/here', DashboardController.show)
]
routes/web.py
from masonite.routes import Get, Post, Put, Patch, Delete, Match, Options, Trace, Connect

Get(..)
Post(..)
Put(..)
Patch(..)
Delete(..)
Match(..)
Options(..)
Trace(..)
Connect(..)
from masonite.routes import RouteGroup, Get

ROUTES = [

    RouteGroup([
        Get('/url1', ...),
        Get('/url2', ...),
        Get('/url3', ...),
    ]),

]
ROUTES = [

    RouteGroup([
        Get('/url1', ...),
        Get('/url2', ...),
        Get('/url3', ...),
    ], middleware=('auth', 'jwt')),

]
ROUTES = [

    RouteGroup([
        Get('/url1', ...),
        Get('/url2', ...),
        Get('/url3', ...),
    ], middleware=('auth',)),

]
ROUTES = [

    RouteGroup([
        Get('/url1', ...).name('create'),
        Get('/url2', ...).name('update'),
        Get('/url3', ...).name('delete'),
    ], 
    middleware=('auth', 'jwt'),
    domain='subdomain',
    prefix='/dashboard',
    namespace='auth.',
    name='post.',
    add_methods=['OPTIONS']
    ),

]
RouteGroup(middleware=('auth', 'jwt'), name='post.', routes = [
    Get('/url1', ...).name('create'),
    Get('/url2', ...).name('update'),
    Get('/url3', ...).name('delete'),
]),
ROUTES = [

    RouteGroup([
        Get('/url1', ...).name('create'),
        Get('/url2', ...).name('update'),
        Get('/url3', ...).name('delete'),
        RouteGroup([
            Get('/url4', ...).name('read'),
            Get('/url5', ...).name('put'),
        ], prefix='/users', name='user.'),
    ], prefix='/dashboard', name='post.', middleware=('auth', 'jwt')),

]
ROUTES = [
    Get().domain('www').route('/', 'WelcomeController@show').name('welcome'),
    Post().domain('www').route('/invite', 'InvitationController@send').name('invite'),
    Get().domain('www').route('/dashboard/apps', 'AppController@show').name('app.show').middleware('auth'),
    Get().domain('www').route('/dashboard/apps/create', 'AppController@create').name('app.create').middleware('auth'),
    Post().domain('www').route('/dashboard/apps/create', 'AppController@store').name('app.store'),
    Post().domain('www').route('/dashboard/apps/delete', 'AppController@delete').name('app.delete'),
    Get().domain('www').route('/dashboard/plans', 'PlanController@show').name('plans').middleware('auth'),
    Post().domain('www').route('/dashboard/plans/subscribe', 'PlanController@subscribe').name('subscribe'),
    Post().domain('www').route('/dashboard/plans/cancel', 'PlanController@cancel').name('cancel'),
    Post().domain('www').route('/dashboard/plans/resume', 'PlanController@resume').name('resume'),

    Post().domain('*').route('/invite', 'InvitationController@subdomain').name('invite.subdomain'),
    Get().domain('*').route('/', 'WelcomeController@subdomain').name('welcome'),
]

ROUTES = ROUTES + [
    Get().domain('www').route('/login', 'LoginController@show').name('login'),
    Get().domain('www').route('/logout', 'LoginController@logout'),
    Post().domain('www').route('/login', 'LoginController@store'),
    Get().domain('www').route('/register', 'RegisterController@show'),
    Post().domain('www').route('/register', 'RegisterController@store'),
    Get().domain('www').route('/home', 'HomeController@show').name('home'),
]
ROUTES = [

    RouteGroup([
        # Dashboard Routes
        RouteGroup([
            # App Routes
            RouteGroup([
                Get('', 'AppController@show').name('show'),
                Get('/create', 'AppController@create').name('create'),
                Post('/create', 'AppController@store').name('store'),
                Post('/delete', 'AppController@delete').name('delete'),
            ], prefix='/apps', name='app.'),

            Get('/plans', 'PlanController@show').name('plans'),
            Post('/plans/subscribe', 'PlanController@subscribe').name('subscribe'),
            Post('/plans/cancel', 'PlanController@cancel').name('cancel'),
            Post('/plans/resume', 'PlanController@resume').name('resume'),
        ], prefix="/dashboard", middleware=('auth',)),

        # Login and Register Routes
        Get('/login', 'LoginController@show').name('login'),
        Get('/logout', 'LoginController@logout'),
        Post('/login', 'LoginController@store'),
        Get('/register', 'RegisterController@show'),
        Post('/register', 'RegisterController@store'),
        Get('/home', 'HomeController@show').name('home'),

        # Base Routes
        Get('/', 'WelcomeController@show').name('welcome'),
        Post('/invite', 'InvitationController@send').name('invite'),
    ], domain='www'),


    # Subdomain invitation routes
    Post().domain('*').route('/invite', 'InvitationController@subdomain').name('invite.subdomain'),
    Get().domain('*').route('/', 'WelcomeController@subdomain').name('welcome'),
]
ROUTES = [
    Get().view('/template', 'some/template', {'key': 'value'})
]
from masonite.routes import Redirect

ROUTES = [
    Redirect('/old/route', '/new/route', status=302, methods=['GET', 'POST'])
]
Match(['PUT', 'PATCH']).route(...)
routes/web.py
Get('/dashboard', 'DashboardController@show').name('dashboard')
routes/web.py
Get('/dashboard', 'DashboardController@show').middleware('auth', 'anothermiddleware')
routes/web.py
Get('/dashboard', 'users.DashboardController@show')
Get('/dashboard', '/thirdparty.package.users.DashboardController@show')
from app.controllers.SomeController import SomeController

Get('/dashboard', SomeController.show)
routes/web.py
Get('/dashboard/@id', 'Controller@show')
app/http/controller/YourController.py
def show(self, request: Request):
    request.param('id')
routes/web.py
Get('/dashboard/user/?option', 'Controller@show')
routes/web.py
Get('/dashboard/user/?option', 'Controller@show').default({
    'option': 'settings'
})
routes/web.py
Get('/dashboard/@id:int', 'Controller@show')
routes/web.py
Get('/dashboard/@id:string', 'Controller@show')
app/http/providers/RouteCompileProvider.py
from masonite.provider import ServiceProvider
from masonite.routes import Route


class RouteCompilerProvider(ServiceProvider):

    wsgi = False
    ...

    def boot(self, route: Route):
        route.compile('year', r'([0-9]{4})')
app/providers/UserModelProvider.py
wsgi = False
...
def boot(self, request: Request):
    request.activate_subdomains()
routes/web.py
Get().domain('joseph').route('/dashboard', 'Controller@show')
routes/web.py
Get().domain('*').route('/dashboard', 'Controller@show')
app/http/controllers/YourController.py
def show(self, request: Request):
    print(request.param('subdomain'))

Static Files

Introduction

Masonite tries to make static files extremely easy and comes with whitenoise out of the box. Whitenoise 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:

config/storage.py
STATICFILES = {
    'storage/assets/css': 'assets/',
}

Now in our templates we can use:

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

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

Static Template Function

All templates have a static function that can be used to assist in getting locations of static files. You can specify the driver and locations you want using the driver name or dot notation.

Take this for example:

config/storage.py
....
's3': {
  's3_client': 'sIS8shn...'
  ...
  'location': 'https://s3.us-east-2.amazonaws.com/bucket'
  },
....
...
<img src="{{ static('s3', 'profile.jpg') }}" alt="profile">
...

this will render:

<img src="https://s3.us-east-2.amazonaws.com/bucket/profile.jpg" alt="profile">

You can also make the config location a dictionary and use dot notation:

config/storage.py
....
's3': {
  's3_client': 'sIS8shn...'
  ...
  'location': {
    'east': 'https://s3.us-east-2.amazonaws.com/east-bucket',
    'west': 'https://s3.us-west-16.amazonaws.com/west-bucket'
  },
....

and use the dot notation like so:

...
<img src="{{ static('s3.east', 'profile.jpg') }}" alt="profile">
...
<img src="{{ static('s3.west', 'profile.jpg') }}" alt="profile">
...

Serving "Root" Files

Sometimes you may need to serve files that are normally in the root of your application such as a robots.txt or manifest.json. These files can be aliased in your STATICFILES directory in config/storage.py. They do not have to be in the root of your project but instead could be in a storage/root or storage/public directory and aliased with a simple /.

For example a basic setup would have this as your directory:

resources/
routes/
storage/
  static/
  root/
    robots.txt
    manifest.json

and you can alias this in your STATICFILES constant:

config/storage.py
STATICFILES = {
    # folder          # template alias
    'storage/static': 'static/',
    ...
    'storage/root': '/'
}

You will now be able to access localhost:8000/robots.txt and you will have your robots.txt served correctly and it can be indexed by search engines properly.

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

GitHub Sponsors
GitHub Sponsors
Middleware

Requests

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 every time 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):
    request #== <masonite.request.Request>

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:

def show(self, request: Request):
    request.input('username')
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:

def show(self, request: 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

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.

# GET: /dashboard?user=Joe&status=1

def show(self, request: Request):
    return request.all() # {'user': 'Joe', 'status': '1'}

This method will get all of the request input variables to include any internal framework variables completely handled internally such as __token and __method. You can exclude them by passing in False into the method or specifying it explicitly:

# GET: /dashboard?user=Joe&status=1&__token=837674634

def show(self, request: Request):
    return request.all(internal_variables=False) # {'user': 'Joe', 'status': '1'}

To get a specific input:

# GET: /dashboard?firstname=Joe

def show(self, request: Request):
    return request.input('firstname') # Joe

Query Strings

Sometimes you want to only get the query strings or access the query strings. These can be times where you are posting to POST: /dashboard?firstname=Joe and want to access both the POST input AND the URL query strings.

You can do so simply:

# POST: /dashboard?firstname=Joe

def show(self, request: Request):
    request.query('firstname') # Joe
    request.query('firstname', 'default') # Joe
    request.query('lastname', 'default') # default

There may be times you have multiple parameters you need to fetch. By default Masonite will only fetch the first one but you may have an example like this:

# POST: /dashboard?firstname=Joe&firstname=Bob

def show(self, request: Request):
    request.query('firstname') # Joe
    request.query('firstname', multi=True) # ['Joe', 'Bob']

You may also get all the query params:

# POST: /dashboard?firstname=Joe

def show(self, request: Request):
    return request.all_query() # {'firstname': 'Joe'}

Input Cleaning

Input data will be cleaned of HTML tags and other security measures. This may cause unwanted return values if you are expecting something like a JSON string. If you want to opt to not clean the input you can specify that as a keyword argument:

request.input('firstname', clean=False) # Joe

Quotes

By default, Masonite will clean quotes in order to sanitize the input. If you would to preserve quotes you can set whether you would like quotes to be cleaned. A value of False will keep the quotes:

request.input('firstname', quotes=False)

To check if some request input data exists:

# GET: /dashboard?firstname=Joe

def show(self, request: Request):
    return request.has('firstname') # True

Getting Dictionary Input

If your input is a dictionary you have two choices how you want to access the dictionary. You can either access it normally:

request.input('payload')['user']['address'] # 123 Smith Rd

Or you can use dot notation to fetch the value for simplicity:

request.input('payload.user.address') # 123 Smith Rd

You can also use a * wildcard to get all values from a dictionary list. Take this code example:

"""
Payload: 
"user": {
    "id": 1,
    "addresses": [
        {"id": 1, 'street': "A Street"},
        {"id": 2, 'street': "B Street"}
    ] 
}
"""

request.input('user.addresses.*.id') # [1,2]

You may also use list indexes to fetch data:

"""
Payload: 
"user": {
    "id": 1,
    "addresses": [
        {"id": 1, 'street': "A Street"},
        {"id": 2, 'street': "B Street"}
    ] 
}
"""

request.input('user.addresses.2.street') # B Street

Only

You can only get a certain set of parameters if you have a need to do so. This can be used like:

# GET: /dashboard?firstname=Joe&lastname=Mancuso&active=1

def show(self, request: Request):
    return request.only('firstname', 'active') # {'firstname': 'Joe', 'active': '1'}

Without

We can specify a set of parameters to exclude from the inputs returned. For example:

# GET: /dashboard?firstname=Joe&lastname=Mancuso&active=1

def show(self, request: Request):
    return request.without('lastname') # {'firstname': 'Joe', 'active': '1'}

Notice it returned everything besides lastname.

URL Parameters

To get the request parameter retrieved from the url. This is used to get variables inside: /dashboard/@firstname for example.

# Route: /dashboard/@firstname
# GET: /dashboard/Joe

def show(self, request: Request):
    return request.param('firstname') # Joe

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:

{
    "name": "Joe",
    "email": "Joe@email.com"
}

Then we can fetch this input in a controller using the normal input() method like so:

def show(self, request: Request):
    request.input('name') # 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.

Creating

def show(self, request: 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:

def show(self, request: 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.

def show(self, request: 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:

def show(self, request: 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

def show(self, request: Request):
    return request.get_cookies()

You can get a specific cookie set from the browser

def show(self, request: 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.:

def show(self, request: 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.

def show(self, request: 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.

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

Routes

You can also get a route URL via the route name. Let's say we have a route like this:

Get('/dashboard').name('dashboard')

We can get the URL from the route name like so:

def show(self):
    request().route('dashboard') # /dashboard

Route Parsing

if we have route parameters like this:

Get('/dashboard/@user').name('dashboard.user')

then we can pass in a dictionary:

def show(self, request: Request):
    request.route('dashboard.user', {'user': 1}) # /dashboard/1

You may also pass a list if that makes more sense to you:

def show(self, request: Request):
    request.route('dashboard.user', [1]) # /dashboard/1

This will inject that value for each parameter in order. For example if we have this route:

Get('/dashboard/@user/@id/@slug').name('dashboard.user')

then we can use:

def show(self, request: Request):
    request.route('dashboard.user', [1, 2, 'some-slug']) 
    # /dashboard/1/2/some-slug

Contains

We can also check if a route contains a specific pattern:

# GET /dashboard/user/1
def show(self, request: Request):
    request.contains('/dashboard/*/1') #== True

You can also use this in a template and pass in a show parameter to return a string instead. This is useful if you want to show active status classes depending on the current route:

<a href=".." class="{{ request().contains('/dashboard/*/edit', show='active')">
<a href=".." class="{{ request().contains('/dashboard/*/create', show='active')">

Current URL

We can get the current url with:

def show(self, request: Request):
    return request.path #== /dashboard/user

Redirection

You can specify a url to redirect to

def show(self, request: Request):
    return request.redirect('/home')

If the url contains http than the route will redirect to the external website

def show(self, request: Request):
    return request.redirect('http://google.com')

You can redirect to a named route

def show(self, request: Request):
    return request.redirect_to('dashboard')

You can also use the name parameter on the redirect method:

def show(self, request: Request):
    return request.redirect(name="dashboard")

You can also redirect to a specific controller. This will find the URL that is attached to the controller method

def show(self, request: Request):
    return request.redirect(controller="WelcomeController@show")

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.

Redirecting to a named route with URL parameters:

def show(self, request: Request):
    return request.redirect_to('dashboard', {'firstname': 'Joseph', 'lastname': 'Mancuso'})

Redirecting to a url in your application with URL parameters:

def show(self, request: Request):
    return request.redirect('dashboard/@id', {'id': '1'})

Form Back Redirection

Masonite will check for a __back input and redirect to that route. We can specify one using the back() view helper function:

<form action="{{ route('dashboard.create') }}" method="POST">
    {{ csrf_field }}
    {{ back() }}
</form>

By default the back helper will return the current path so you can easily go back to the previous page after the form is submitted.

You can also specify a path to go back to:

<form action="{{ route('dashboard.create') }}" method="POST">
    {{ csrf_field }}
    {{ back('/another/path') }}
</form>

Redirecting Back

After submitting the form you can redirect back to wherever the back template method was pointing to using the back() method:

def show(self, request: Request):
    ...
    return request.back()

This will also flash the current inputs to the session. You can then get the inputs using the {{ old('key') }} template helper:

<form>
  <input type="text" name="email" value="{{ old ('email') }}">
  ...
</form>

Redirecting Back To Intended Routes

There are times where you want your user to visit a page where they must be logged in. You may have a check in your controller to redirect a user to the login page if they are not authenticated. You may easily redirect them back to the intended page after they login.

To activate the intended route you simply need to append .then_back() after the redirection. For example you may have this:

Assume the current URL is /dashboard

from masonite.request import Request

class DashboardController:

    def show(self, request: Request):
        if not self.request.user():
            return request.redirect('/login').then_back()

When you go to your login page you'll need to have the normal {{ back() }} form helper:

<form action="{{ route('login') }}" method="POST">
    {{ csrf_field }}
    {{ back() }}
    ...
</form>

In your LoginController (or wherever this form submits to) you simply need to use the redirect_intended method on the request class:

from masonite.request import Request

class LoginController(Controller):

    def store(self, request: Request)
        # ...
        if auth.login(request.input('email'), request.input('password')):
            return request.redirect_intended(default='/home')

This will redirect to the /dashboard route (because that was the intended route). If no route is intended then it will simply redirect to the default you specify (which is /home).

Redirecting Back With Errors

You can redirect back with validation error message or redirect back with input value:

def show(self, request: Request):
    errors = request.validate(
        required(['email', 'password'])
    )

    if errors:
        return request.back().with_errors(errors)

Redirecting Back With Inputs

When redirecting back there are times where you will also want to flash the inputs to the session. With this you can simply use the back() method but if you want a bit more control you can use the with_input() method.

def show(self, request: Request):
    errors = request.validate(
        required(['email', 'password'])
    )

    if errors:
        return request.redirect('/dashboard/errors').with_input()

You can then get the inputs using the {{ old('key') }} template helper:

<form>
  <input type="text" name="email" value="{{ old ('email') }}">
  ...
</form>

Default Back URL

We can also specify a default route just in case a form submitted does not specify one using a form helper:

def show(self, request: Request):
    return request.back(default='/hit/route')

This will check for the __back input and if it doesn't exist it will use this default route. This is the same as a redirect if you don't use the back() helper.

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:

def show(self, request: 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:

def show(self, request: 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:

def show(self, request: 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:

request.header('AUTHORIZATION', 'Bearer some-secret-key')

This will set the AUTHORIZATION header instead of the HTTP_AUTHORIZATION header.

You can also set headers with a dictionary:

request.header({
    'AUTHORIZATION': 'Bearer some-secret-key',
    'Content-Type': 'application/json'
})

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')

You can also use an integer which will find the correct status code for you:

request.status(429)

This snippet is exactly the same as the string based snippet above.

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.

Get Request Method Type

You can get the request method simply:

# PUT: /dashboard

def show(self, request: Request):
    return request.get_request_method() # 'PUT'

Changing Request Methods in Forms and URLs

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:

<form action="/dashboard" method="POST">
    <input type="hidden" name="__method" value="PATCH">
</form>

or you can optionally use a helper method:

<form action="/dashboard" method="POST">
    {{ request_method('PATCH') }}
</form>

When the form is submitted, it will process as a PATCH request instead of a POST request.

This will allow this form to hit a route like this:

from masonite.routes import Patch

ROUTES = [
    Patch('/dashboard', 'DashboardController@update')
]

You can also specify the request method in the query string of the url to change it on a link:

<a href="/dashboard?__method=PATCH" rel="nofollow">Dashboard patch</a>

This link will use the same route as above.

Changing the request method on a link from the default GET method should be done with caution. It can be useful while testing, but is not typically recommended. Adding rel="nofollow" may prevent search engines from following the link and causing data corruption.

Request Information

You can get information related to the request like the scheme, domain and other attribute by accessing them right from the request class:

Getting Domain Information

You can get different parts of the domain using the below methods:

request.scheme() # https
request.host() # www.masonitecasts.com
request.port() # 8000

Path Information

You can easily get the current path and query string information:

request.full_path() # /dashboard/hello%20world
request.full_path(quoted=False) # /dashboard/hello world
request.query_string() # email=joe@masoniteproject.com
request.url() # www.masonitecasts.com/dashboard/hello%20world
request.url(include_standard_port=True) # www.masonitecasts.com:443/dashboard/hello%20world
request.full_url() # https://www.masonitecasts.com/dashboard/hello%20world

Validation

There is a convenient helper method you can use the validate the request. You can import the Validator class and use validation like so:

from masonite.request import Request
from masonite.validation import Validator

def show(self, request: Request, validate: Validator):
    errors = request.validate(
      validate.required('user')
    )

    if errors:
      return request.back().with_errors(errors)

Creating a Blog

Preface

This section of the documentation will contain various tutorials. These are guides that are designed to take you from beginning to end on building various types of projects with Masonite. We may not explain things in much detail for each section as this part of the documentation is designed to just get you familiar with the inner workings of Masonite.

Since this section of the documentation is designed to just get you up and coding with Masonite, any further explanations that should be presented are inside various "hint blocks." Once you are done with the tutorial or simply want to learn more about a topic it is advised that you go through each hint block and follow the links to dive deeper into the reference documentation which does significantly more explaining.

Hint Blocks

You will see various hint blocks throughout the tutorials. Below are examples of what the various colors represent.

You'll see hint blocks that are green which you should follow if you want to learn more information about the topic currently being discussed.

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

Creating a Blog

In this tutorial we will walk through how to create a blog. We will touch on all the major systems of Masonite and it should give you the confidence to try the more advanced tutorials or build an application yourself.

Routing

Typically your first starting point for your Masonite development flow will be to create a route. All routes are located in routes/web.py and are extremely simple to understand. They consist of a request method and a route method. Routing is simply stating what incoming URI's should direct to which controllers.

For example, to create a GET request route it will look like:

from masonite.routes import Get

Get('/url', 'Controller@method')

We'll talk more 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's business 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.

ROUTES = [
    Get('/', 'WelcomeController@show').name('welcome'),

    # Blog Routes
    Get('/blog', 'BlogController@show')
]

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.

Creating a Controller

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. If this seems weird to you it might be worth a try to see if you like this opinionated layout.

Creating Our First Controller

Like most parts of Masonite, you can scaffold a controller with a craft command:

$ craft controller Blog

This will create a controller in app/http/controllers directory that looks like this:

"""A BlogController Module."""

from masonite.request import Request
from masonite.view import View
from masonite.controllers import Controller


class BlogController(Controller):
    """BlogController Controller Class."""

    def __init__(self, request: Request):
        """BlogController Initializer

        Arguments:
            request {masonite.request.Request} -- The Masonite Request class.
        """
        self.request = request

    def show(self, view: View):
        pass

Simple enough, right? You'll notice we have a show method we were looking for. These are called "controller methods" and are similiar to what Django calls a "view."

But also notice we now have our show method that we specified in our route earlier.

Returning a View

We can return a lot of different things in our controller but for now we can return a view from our controller. A view in Masonite are html files or "templates". They are not Python objects themselves like other Python frameworks. Views are what the users will see (or view).

This is important as this is our first introduction to Python's IOC container. We specify in our parameter list that we need a view class and Masonite will inject it for us.

For now on we won't focus on the whole controller but just the sections we are worried about. A ... means there is stuff in between code that we are not worried about:

from masonite.view import View 
...
def show(self, view: View):
    return view.render('blog')

Notice here we "type hinted" the View class. This is what Masonite calls "Auto resolving dependency injection". If this doesn't make sense to you right now don't worry. The more you read on the more you will understand.

Creating Our View

You'll notice now that we are returning the blog view but it does not exist yet.

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:

$ craft view blog

This will create that template we wanted above for us.

We can put some text in this file like:

This is a blog

and then run the server

$ craft serve

and open up http://localhost:8000/blog. You will see "This is a blog" in your web browser.

Authentication

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 installations of Masonite since it will create controllers routes and views for you.

For our blog, we will need to setup some form of registration so we can get new users to start posting to our blog. We can create an authentication system by running the craft command:

$ 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.

We will check what was created for us in a bit.

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 but we will assume that you do not. In this case we can just use SQLite.

Now we just need to change a few environment variables so Masonite can create the SQLite 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:

DB_CONNECTION=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 by adding sqlite to the DB_CONNECTION variable and whatever you want for your database which will be created for you when you migrate. We will call it blog.db:

DB_CONNECTION=sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=blog.db
DB_USERNAME=root
DB_PASSWORD=root

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 or remove columns at a later date.

$ 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:

$ craft serve
Username: demo
Email: demo@email.com
Password: password

Migrations

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

$ craft migration create_posts_table --create posts

This command simply creates the start 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 something that looks like:

def up(self):
    """
    Run the migrations.
    """
    with self.schema.create('posts') as table:
        table.increments('id')
        table.timestamps()

Lets add a title, an author, and a body to our posts tables.

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()

Now we can migrate this migration to create the posts table

$ craft migrate

Models

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:

$ 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:

"""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:

"""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:

"""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:

"""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, some models may rely on each other so it is typically better to perform the import inside the relationship like we did above to prevent any possibilities of circular imports.

Designing Our Blog

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 a layout and start creating and updating blog posts.

We will also check if the user is logged in before creating a template.

The Template For Creating

The URL for creating will be located at /blog/create and will be a simple form for creating a blog post

<form action="/blog/create" method="POST">
    {{ csrf_field }}

    <input type="name" name="title">
    <textarea name="body"></textarea>
</form>

Notice here we have this strange {{ csrf_field }} 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:

{% if auth() %}
    <form action="/blog/create" method="POST">
        {{ csrf_field }}

        <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 %}

auth() is a view helper function that either returns the current user or returns None.

Static Files

For simplicity sake, we won't be 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.

html {
    background-color: #ddd;
}

Now we can add it to our template like so right at the top:

<link href="/static/blog.css" rel="stylesheet">
{% if auth() %}
    <form action="/blog/create" method="POST">
        {{ csrf_field }}

        <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:

<link href="/static/blog.css" rel="stylesheet">
{% if auth() %}
    <form action="/blog/create" method="POST">
        {{ csrf_field }}

        <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:

from masonite.routes import Get, Post
...

Post('/blog/create', 'BlogController@store'),

and create a new store method on our controller:

...
def show(self, view: View): 
    return view.render('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.

from app.Post import Post
from masonite.request import Request
...

def store(self, request: Request):
    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 http://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".

Showing Our Posts

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.

$ craft view posts
$ craft view single

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.

$ craft controller Post

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:

from app.Post import Post

...

def show(self, view: View):
    posts = Post.all()

    return view.render('posts', {'posts': posts})

Posts Route

We need to add a route for this method:

Get('/posts', 'PostController@show')

Posts View

Our posts view can be very simple:

{% for post in posts %}
    {{ post.title }}
    <br>
    {{ post.body }}
    <hr>
{% endfor %}

Go ahead and run the server and head over to http://localhost:8000/posts route. You should see a basic representation of your posts. If you only see 1, go to http://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:

{% for post in posts %}
    {{ post.title }} by {{ post.author.name }}
    <br>
    {{ post.body }}
    <hr>
{% endfor %}

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:

Get('/post/@id', 'PostController@single')

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.

from app.Post import Post
from masonite.request import Request
from masonite.view import View
...

def single(self, view: View, request: Request):
    post = Post.find(request.param('id'))

    return view.render('single', {'post': 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:

{{ post.title }}
<br>
{{ post.body }}
<hr>

Go ahead and run the server and head over the http://localhost:8000/post/1 route and then http://localhost:8000/post/2 and see how the posts are different.

Updating and Deleting Posts

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:

def update(self, view: View, request: Request):
    post = Post.find(request.param('id'))

    return view.render('update', {'post': post})

def store(self, request: Request):
    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
<form action="/post/{{ post.id }}/update" method="POST">
    {{ csrf_field }}

    <label for="">Title</label>
    <input type="text" name="title" value="{{ post.title }}"><br>

    <label>Body</label>
    <textarea name="body">{{ post.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:

Get('/post/@id/update', 'PostController@update'),
Post('/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.

from masonite.request import Request
...

def delete(self, request: Request):
    post = Post.find(request.param('id'))

    post.delete()

    return 'post deleted'

Make the route:

Get('/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:

<form action="/post/{{ post.id }}/update" method="POST">
    {{ csrf_field }}

    <label for="">Title</label>
    <input type="text" name="title" value="{{ post.title }}"><br>

    <label>Body</label>
    <textarea name="body">{{ post.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!

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 break apart the provider list into two separate lists. The first list is called the WSGIProviders list which are providers where wsgi=True (which they are by default). We will use this list of a smaller amount of providers in order to speed up the application since now we won't need to run through all providers and see which ones need to run.

While we are in the loop we also create a list of providers where wsgi=False and boot those providers. 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 if you do not want to use Whitenoise.

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:

from masonite.request import Request

class SomeMiddleware:

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

    ...

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 searches through the routes, finds which one to hit and exectues the controller and controller method.

Status Code Provider

This provider is responsible for showing the nice HTTP status codes you see during development and production. This Service Provider also allows custom HTTP error pages by putting them into the resources/templates/errors directory.

Nothing too special about this Service Provide. You can remove this if you want it to show the default WSGI server error.

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:

terminal
$ craft command Hello

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

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

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:

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

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

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')))

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:

terminal
$ craft provider HelloProvider

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

app/providers/HelloProvider.py
''' A HelloProvider Service Provider '''
from masonite.provider import ServiceProvider


class HelloProvider(ServiceProvider):

    def register(self):
        pass

    def boot(self):
        pass

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:

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

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/providers.py file:

config/providers.py
from app.providers.HelloProvider import HelloProvider

PROVIDERS = [
...
    # Application Providers
    UserModelProvider,
    MiddlewareProvider,

    # New Hello Provider
    HelloProvider,
]

That's it! Now if we run:

terminal
$ craft

We will see our new hello command:

terminal
  help              Displays help for a command
  hello             Say hello to you
  install           Installs all of Masonite's dependencies

and if we run:

terminal
$ craft hello Joseph

We will see an output of:

terminal
Hello Joseph

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 its 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:

Tinker Command

You can "tinker" around with Masonite by running:

$ craft tinker

This command will start a Python shell but also imports the container by default. So we can call:

Type `exit()` to exit.
>>> app
<masonite.app.App object at 0x10cfb8d30>
>>> app.make('Request')
<masonite.request.Request object at 0x10d03c8d0>
>>> app.collect("Mail*Driver")
{'MailSmtpDriver': <class 'masonite.drivers.MailSmtpDriver.MailSmtpDriver'>,
'MailMailgunDriver': <class 'masonite.drivers.MailMailgunDriver.MailMailgunDriver'>
}
>>> exit()

And play around the container. This is a useful debugging tool to verify that objects are loaded into the container if there are any issues.

Show Routes Command

Another useful command is the show:routes command which will display a table of available routes that can be hit:

$ craft show:routes

This will display a table that looks like:

========  =======  =======  ========  ============
Method    Path     Name     Domain    Middleware
========  =======  =======  ========  ============
GET       /        welcome
GET       /home    home
GET       /user 
POST      /create  user   
========  =======  =======  ========  ============

Application Information

If you are trying to debug your application or need help in the Slack channel, it might be beneficial to see some useful information information about your system and environment. In this case we have a simple command:

$ craft info

This will give some small details about the current system which could be useful to someone trying to help you. Running the command will give you something like this:

Environment Information
-------------------------  ------------------
System Information         MacOS x86_64 64bit
System Memory              8 GB
Python Version             CPython 3.6.5
Virtual Environment        ✓
Masonite Version           2.0.6
Craft Version              2.0.7
APP_ENV                    local
APP_DEBUG                  True

Feel free to contribute any additional information you think is necessary to the command in the core repository.

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 Validators

Validators are classes based on validating form or request input. We can create validators by running:

$ craft validator LoginValidator

Creating Model Definition Docstrings

Masonite uses Orator which is an active record style ORM. If you are coming from other Python frameworks you may be more familiar with Data Mapper ORM's like Django ORM or SQLAlchemy. These style ORM's are useful since the names of the column in your table are typically the names of class attributes. If you forget what you named your column you can typically just look at the model but if your model looks something like:

class User(Model):
    pass

Then it is not apparent what the tables are. We can run a simple command though to generate a docstring that we can throw onto our model:

$ craft model:docstring table_name

Which will generate something like this in the terminal:

"""Model Definition (generated with love by Masonite)

id: integer default: None
name: string(255) default: None
email: string(255) default: None
password: string(255) default: None
remember_token: string(255) default: None
created_at: datetime(6) default: CURRENT_TIMESTAMP(6)
updated_at: datetime(6) default: CURRENT_TIMESTAMP(6)
customer_id: string(255) default: None
plan_id: string(255) default: None
is_active: integer default: None
verified_at: datetime default: None
"""

We can now copy and paste that onto your model and change whatever we need to:

class User(Model):
    """Model Definition (generated with love by Masonite)

    id: integer default: None
    name: string(255) default: None
    email: string(255) default: None
    password: string(255) default: None
    remember_token: string(255) default: None
    created_at: datetime(6) default: CURRENT_TIMESTAMP(6)
    updated_at: datetime(6) default: CURRENT_TIMESTAMP(6)
    customer_id: string(255) default: None
    plan_id: string(255) default: None
    is_active: integer default: None
    verified_at: datetime default: None
    """
    pass

You can also specify the connection to use using the --connection option.

$ craft model:docstring table_name --connection amazon_db

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 boilerplate 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.

Model Shortcuts

You can also use the -s and -m flags to create a seed or model at the same time.

$ craft model ModelName -s -m

This is a shortcut for these 3 commands:

$ craft model ModelName
$ craft seed ModelName
$ craft migration create_tablename_table --create tablename

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

Running Queues

Masonite has a queue feature that allows you to load the jobs you create in the section above and run them either asyncronously or send them off to a message broker like RabbitMQ. You may start the worker by running:

$ craft queue:work

Packages

$ craft package name_of_package

Creating Commands

You can scaffold out basic command boilerplate:

$ craft command Hello

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

This will set the server to auto-reload when you make file changes.

You can also set it to not auto-reload on file changes:

$ craft serve --dont-reload

or the shorthand

$ craft serve -d

If you have unmigrated migrations, Masonite will recommend running craft migrate when running the server.

Live Reloading

The serve command also has a live reloading option which will refresh any connected browsers so you can more quickly prototype your jinja templates.

This is not a great tool for changing Python code and any Python code changes may still require a browser refresh to see the changes.

$ craft serve --live-reload

Host and Port

You can bind to a host using -b and/or a port using -p

$ craft serve -b 127.0.0.1 -p 8080

Reloading Interval

Masonite comes with a pretty quick auto reloading development server. By default there will be a 1 second delay between file change detection and the server reloading. This is a fairly slow reloading interval and most systems can handle a much faster interval while still properly managing the starting and killing of process PID's.

You can change the interval to something less then 1 using the -i option:

$ craft serve -r -i .1

You will notice a considerably faster reloading time on your machine. If your machine can handle this interval speed (meaning your machine is properly starting and killing the processes) then you can safely proceed using a lower interval during development.

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.

Testing

You can also scaffold out basic test cases. You can do this by running:

$ craft test NameOfTest

This will scaffold the test in the base tests/ directory and you can move it to wherever you like from there.

Publish

Masonite has the concept of publishing where you can publish specific assets to a developers application. This will allow them to make tweaks to better fit your package to their needs.

You can publish a specific provider by running:

$ craft provider ProviderName

You can also optionally specify a tag:

$ craft provider ProviderName --tag name

You should read more about publishing at the publishing documentation page.

Views

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 the resources/templates directory.

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

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

app/http/controllers/YourController.py
from masonite.view import View

def show(self, view: View):
    return view.render('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, view: View):
    return view.render('/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.

Caveats

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
...

ensuring there is a __init__.py file. This is a Jinja2 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, view: View):
    return view.render('/package/dashboard')

will perform an absolute import for your Masonite project. For example it will locate:

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

Or find the package in a completely separate third part module. So if you are making a package for Masonite then keep this in mind of where you should put your templates.

Passing Data to Views

Most 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, view: View, request: Request):
    return view.render('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>

View Syntax

Masonite also enables Jinja2 Line Statements by default which allows you to write syntax the normal way:

{% extends 'nav/base.html' %}

{% block content %}
    {% for element in variables %}
        {{ element }}
    {% endfor %}

    {% if some_variable %}
        {{ some_variable }}
    {% endif %}

{% endblock %}

Or using line statements with the @ character:

@extends 'nav/base.html'

@block content
    @for element in variables
        {{ element }}
    @endfor

    @if some_variable
        {{ some_variable }}
    @endif

@endblock

The choice is yours on what you would like to use but keep in mind that line statements need to use only that line. Nothing can be after after or before the line.

Adding Environments

You can also add Jinja2 environments to the container which will be available for use in your views. This is typically done for third party packages such as Masonite Dashboard. You can extend views in a Service Provider in the boot method. Make sure the Service Provider has the wsgi attribute set to False. This way the specific Service Provider will not keep adding the environment on every request.

from masonite.view import View

wsgi = False

...

def boot(self, view: View):
    view.add_environment('dashboard/templates')

By default the environment will be added using the PackageLoader Jinja2 loader but you can explicitly set which loader you want to use:

from jinja2 import FileSystemLoader
from masonite.view import View
...
wsgi = False

...

def boot(self, view: View):
    view.add_environment('dashboard/templates', loader=FileSystemLoader)

The default loader of PackageLoader will work for most cases but if it doesn't work for your use case, you may need to change the Jinja2 loader type.

Using Dot Notation

If using a / doesn't seem as clean to you, you can also optionally use dots:

def show(self, view: View):
    view.render('dashboard.user.show')

if you want to use a global view you still need to use the first /:

def show(self, view: View):
    view.render('/dashboard.user.show')

Helpers

There are quite a few built in helpers in your views. Here is an extensive list of all view helpers:

Request

You can get the request class:

<p> Path: {{ request().path }} </p>

Static

You can get the location of static assets:

If you have a configuration file like this:

config/storage.py
....
's3': {
  's3_client': 'sIS8shn...'
  ...
  'location': 'https://s3.us-east-2.amazonaws.com/bucket'
  },
....
...
<img src="{{ static('s3', 'profile.jpg') }}" alt="profile">
...

this will render:

<img src="https://s3.us-east-2.amazonaws.com/bucket/profile.jpg" alt="profile">

CSRF Field

You can create a CSRF token hidden field to be used with forms:

<form action="/some/url" method="POST">
    {{ csrf_field }}
    <input ..>
</form>

CSRF Token

You can get only the token that generates. This is useful for JS frontends where you need to pass a CSRF token to the backend for an AJAX call

<p> Token: {{ csrf_token }} </p>

Current User

You can also get the current authenticated user. This is the same as doing request.user().

<p> User: {{ auth().email }} </p>

Request Method

On forms you can typically only have either a GET or a POST because of the nature of html. With Masonite you can use a helper to submit forms with PUT or DELETE

<form action="/some/url" method="POST">
    {{ request_method('PUT') }}
    <input ..>
</form>

This will now submit this form as a PUT request.

Route

You can get a route by it's name by using this method:

<form action="{{ route('route.name') }}" method="POST">
    ..
</form>

If your route contains variables you need to pass then you can supply a dictionary as the second argument.

<form action="{{ route('route.name', {'id': 1}) }}" method="POST">
    ..
</form>

or a list:

<form action="{{ route('route.name', [1]) }}" method="POST">
    ..
</form>

Another cool feature is that if the current route already contains the correct dictionary then you do not have to pass a second parameter. For example if you have a 2 routes like:

Get('/dashboard/@id', 'Controller@show').name('dashboard.show'),
Get('/dashhboard/@id/users', 'Controller@users').name('dashhboard.users')

If you are accessing these 2 routes then the @id parameter will be stored on the user object. So instead of doing this:

<form action="{{ route('dashboard.users', {'id': 1}) }}" method="POST">
    ..
</form>

You can just leave it out completely since the id key is already stored on the request object:

<form action="{{ route('dashboard.users') }}" method="POST">
    ..
</form>

Back

This is useful for redirecting back to the previous page. If you supply this helper then the request.back() method will go to this endpoint. It's typically good to use this to go back to a page after a form is submitted with errors:

<form action="/some/url" method="POST">
    {{ back(request().path) }}
</form>

Now when a form is submitted and you want to send the user back then in your controller you just have to do:

def show(self, request: Request):
    # Some failed validation
    return request.back()

Old

The request.back() method will also flash the current inputs to the session so you can get them when you land back on your template. You can get these values by using the old() method:

<form>
  <input type="text" name="email" value="{{ old('email') }}">
  ...
</form>

Session

You can access the session here:

<p> Error: {{ session().get('error') }} </p>

Sign

You can sign things using your secret token:

<p> Signed: {{ sign('token') }} </p>

Unsign

You can also unsign already signed string:

<p> Signed: {{ unsign('signed_token') }} </p>

Encrypt

This is just an alias for sign

Decrypt

This is just an alias for unsign

Config

This allows you to easily fetch configuration values in your templates:

<h2> App Name: {{ config('application.name') }}</h2>

Optional

Allows you to fetch values from objects that may or may not be None. Instead of doing something like:

{% if auth() and auth().name == 'Joe' %}
    <p>Hello!</p>
{% endif %}

You can use this helper:

{% if optional(auth()).name == 'Joe' %}
    <p>Hello!</p>
{% endif %}

DD

This is the normal dd helper you use in your controllers

Hidden

You can use this helper to quickly add a hidden field

<form action="/" method="POST">
    {{ hidden('secret' name='secret-value') }}
</form>

Exists

Check if a template exists

{% if exists('auth/base') %}
    {% extends 'auth/base.html' %}
{% else %}
    {% extends 'base.html' %}
{% endif %}

Cookie

Gets a cookie:

<h2> Token: {{ cookie('token') }}</h2>

Url

Get the URL to a location:

<form action="{{ url('/about', full=True) }}" method="POST">

</form>

Jinja2

Below are some examples of the Jinja2 syntax which Masonite uses to build views.

Line Statements

It's important to note that Jinja2 statements can be rewritten with line statements and line statements are preferred in Masonite. In comparison to Jinja2 line statements evaluate the whole line, thus the name line statement.

So Jinja2 syntax looks like this:

{% if expression %}
    <p>do something</p>
{% endif %}

This can be rewritten like this with line statement syntax:

@if expression
    <p>do something</p>
@endif

It's important to note though that these are line statements. Meaning nothing else can be on the line when doing these. For example you CANNOT do this:

<form action="@if expression: 'something' @endif">

</form>

But you could achieve that with the regular formatting:

<form action="{% if expression %} 'something' {% endif %}">

</form>

Whichever syntax you choose is up to you.

Variables

You can show variable text by using {{ }} characters:

<p>
    {{ variable }}
</p>
<p>
    {{ 'hello world' }}
</p>

If statement

If statements are similar to python but require an endif!

Line Statements:

@if expression
    <p>do something</p>
@elif expression
    <p>do something else</p>
@else
    <p>above all are false</p>
@endif

Using alternative Jinja2 syntax:

{% if expression %}
    <p>do something</p>
{% elif %}
    <p>do something else</p>
{% else %}
    <p>above all are false</p>
{% endif %}

For Loops

For loop look similar to the regular python syntax.

Line Statements:

@for item in items
    <p>{{ item }}</p>
@endfor

Using alternative Jinja2 syntax:

{% for item in items %}
    <p>{{ item }}</p>
{% endfor %}

Include statement

An include statement is useful for including other templates.

Line Statements:

@include 'components/errors.html'

<form action="/">

</form>

Using alternative Jinja2 syntax:

{% include 'components/errors.html' %}

<form action="/">

</form>

Any place you have repeating code you can break out and put it into an include template. These templates will have access to all variables in the current template.

Extends

This is useful for having a child template extend a parent template. There can only be 1 extends per template:

Line Statements:

@extends 'components/base.html'

@block content
    <p> read below to find out what a block is </p>
@endblock

Using alternative Jinja2 syntax:

{% extends 'components/base.html' %}

{% block content %}
    <p> read below to find out what a block is </p>
{% endblock %}

Blocks

Blocks are sections of code that can be used as placeholders for a parent template. These are only useful when used with the extends above. The "base.html" template is the parent template and contains blocks, which are defined in the child template "blocks.html".

Line Statements:

<!-- components/base.html -->
<html>
    <head>
        @block css 
        <!-- block named "css" defined in child template will be inserted here -->
        @endblock
    </head>

<body>
    <div class="container">
        @block content
        <!-- block named "content" defined in child template will be inserted here -->
        @endblock
    </div>

@block js
<!-- block named "js" defined in child template will be inserted here -->
@endblock

</body>
</html>
<!-- components/blocks.html -->
@extends 'components/base.html'

@block css
    <link rel=".." ..>
@endblock

@block content
    <p> This is content </p>
@endblock

@block js
    <script src=".." />
@endblock

Using alternative Jinja2 syntax:

<!-- components/base.html -->
<html>
    <head>
        {% block css %} 
        <!-- block named "css" defined in child template will be inserted here -->
        {% endblock %}
    </head>

<body>
    <div class="container">
        {% block content %} 
        <!-- block named "content" defined in child template will be inserted here -->
        {% endblock %}
    </div>

{% block js %}
<!-- block named "js" defined in child template will be inserted here -->
{% endblock %}

</body>
</html>
<!-- components/blocks.html -->
{% extends 'components/base.html' %}

{% block css %}
    <link rel=".." ..>
{% endblock %}

{% block content %}
    <p> This is content </p>
{% endblock %}

{% block js %}
    <script src=".." />
{% endblock %}

As you see blocks are fundamental and can be defined with Jinja2 and line statements. It allows you to structure your templates and have less repeating code.

The blocks defined in the child template will be passed to the parent template.

Introduction and Installation

The modern and developer centric Python web framework that strives for an actual batteries included developer tool with a lot of out of the box functionality with an extremely extendable architecture. Masonite is perfect for beginner developers getting into their first web applications as well as experienced devs that need to utilize the full potential of Masonite to get their applications done.

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. Use it for your next SaaS! Try it once and you’ll fall in love.

NOTE: Masonite 2.3 is no longer compatible with the masonite-cli tool. Please uninstall that by running pip uninstall masonite-cli. If you do not uninstall masonite-cli you will have command clashes

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.5+

  • Latest version of OpenSSL

  • Pip3

All commands of python and pip in this documentation is assuming they are pointing to the correct Python 3 versions. For example, anywhere you see the python command ran it is assuming that is a Python 3.5+ Python installation. If you are having issues with any installation steps just be sure the commands are for Python 3.5+ and not 2.7 or below.

Linux

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

Debian and Ubuntu based Linux distributions

terminal
$ sudo apt install python3-dev python3-pip libssl-dev build-essential python3-venv

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

terminal
$ sudo apt-get install python3.6-dev python3-pip libssl-dev build-essential python3-venv

Enterprise Linux based distributions (Fedora, CentOS, RHEL, ...)

terminal
# dnf install python-devel openssl-devel

Installation

Masonite excels at being simple to install and get going. If you are coming from previous versions of Masonite, the order of some of the installation steps have changed a bit.

Firstly, open a terminal and head to a directory you want to create your application in. You might want to create it in a programming directory for example:

$ cd ~/programming
$ mkdir myapp
$ cd myapp

If you are on windows you can just create a directory and open the directory in the Powershell.

Activating Our Virtual Environment (optional)

Although this step is technically optional, it is highly recommended. You can 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:

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

or if you are on Windows:

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

The pythoncommand here is utilizing Python 3. Your machine may run Python 2 (typically 2.7) by default for UNIX machines. 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

Installing Masonite

Now we can install Masonite. This will give us access to a craft command we can use to finish the install steps for us:

$ pip install masonite

Once Masonite installs you will now have access to the craft command line tool. Craft will become your best friend during your development. You will learn to love it very quickly :).

You can ensure Masonite and craft installed correctly by running:

$ craft

You should see a list of a few commands like install and new

Creating Our Project

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

terminal
$ craft

We are currently only interested in the craft new command. To create a new project just run:

terminal
$ craft new

This will also run craft install which will install our dependencies.

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.

Additional Commands

Now that Masonite installed fully we can check all the new commands we have available. There are many :).

$ craft

We should see many more commands now.

Running The Server

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

terminal
$ craft serve

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

Masonite uses romantic versioning instead of semantic versioning. Because of this, all minor releases (2.0.x) will contain bug fixes and fully backwards compatible feature releases. Be sure to always keep your application up to date with the latest minor release to get the full benefit of Masonite's romantic versioning.

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:

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:

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:

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:

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:

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:

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:

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!

Database Seeding

Introduction

You'll likely want to seed your database during development in order to get some dummy data into your database to start working fast.

Masonite uses Orator to generate seed files. This documentation will explain how to create the files itself.

Getting Started

We can simply create a new seeder by running:

This will create a new seeder inside the databases/seeds directory. This will also create a database_seeder.py file which will be where the root of all seeds should be.

The user_table_seeder should be where you simply abstract your seeds to.

Running Seeds

We can run:

This will run the database_seeder seed. We can also specify an individual seed

Which will run the user_table_seeder only.

Service Providers

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 by simply using a craft command:

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.

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 the boot method 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:

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

Provider Methods

Service providers have several methods we can use to help us bind objects into the container.

Commands

We can simply bind commands into the container:

Middleware

We can also bind http and route middleware into the container:

Notice that the route middleware accepts a dictionary and the http middleware accepts a list

Migrations

We can add directories that have migrations easily as well:

Routes

We can also add routes:

Assets

We can also add any directories that have asset files:

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.

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:

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.

This will update a dictionary that is linked to the current user.

Using Dictionaries

You can also set a dictionary as a session value and it will automatically JSON encode and decode as you set and get the key:

When you get the key from the session it will turn it back into a dictionary.

You can use it on flash as well:

Getting Data

Data can be pulled from the session:

Checking Data

Very often, you will want to see if a value exists in the session:

Getting all Data

You can get all data for the current user:

Flashing Data

Data can be inserted only for the next request. This is useful when using redirection and displaying a success message.

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:

It's important to note that changing the drivers will not change the session() function inside your templates (more about templates below).

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

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:

Then inside your working template you could add:

Then inside your controller you could do something like:

or as separate statements

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:

Or to reset only the flash data:

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.

Autoloading

Introduction

With Masonite 2, we can use the builtin autoloader in order to load classes into the container in a much simpler way.

Configuration

The configuration variable for autoloading is inside the config/application.py file which contains a list of directories:

Out of the box, Masonite will autoload all classes that are located in the app directory which unsurprisingly contains all of the application models.

How It Works

Masonite will go through each directory listed and convert it to a module. For example if given the directory of app/models it will convert that to app.models and fetch that module. It will use inspection to go through the entire module and extract all classes imported or defined.

If your code looks something like:

Then the autoloader will fetch three classes: the belongs_to class, the Model class and the User class. The autoloader will then check if the module of the classes fetched are actually apart of the module being autoloaded.

In other words the modules of the above classes are: orator.orm, config.database and app respectively. Remember that we are just autoloaded the app module so it will only bind the app.User class to the container with a binding of the class name: User and the actual object itself: <class app.User.User>.

All of this autoloading is done when the server is first started but before the WSGI server is ready to start accepting requests so there are no performance hits for this.

Usage

Since the app directory is autoloaded, and our User model is in that directory, the User model will be loaded into the container when the server starts.

All bindings into the Service Container will be the name of the object as the key and the actual object as the value. So the User model will be accessed like:

There is no reason to add models into the container unless your specific use case requires it but this is just an example.

Other Directories

You don't have to keep your models in the app directory. Feel free to move them anywhere but they will not be autoloaded outside the app directory by default. In order to autoload other directories we can add them to the AUTOLOAD variable.

For example if we have an app directory structure like:

Then we can edit our AUTOLOAD variable like so:

And then be able to do:

Being that the container is useful as an IOC container, another use case would be if a third party library needed some models to manipulate and then bind them back into the container. An example of this type of library would be one that needs to change the models methods in order to capture query operations and send them to a dashboard or a report.

Exceptions

The autoload class will raise a few exceptions so you should be aware of them in order to avoid confusion when these exceptions are raised.

InvalidAutoloadPath

This exception will be thrown if any of your autoload paths end with a forward slash like the first element in this list:

Notice the path is now app/ and not app. This will throw an exception when the server first starts.

AutoloadContainerOverwrite

This exception will be thrown when one of your classes are about to overwrite a container binding that is outside of your search path. The search path being the directories you specified in the AUTOLOAD constant.

For example, you may have a model called Request like so:

Without this exception, your application will overwrite the binding of the Masonite Request class.

When Masonite goes to autoload these classes, it will detect that the Request key has already been bound into the container (by Masonite itself). Masonite will then detect if that Request object in the container is within the search path. In other words it will check for a Request class inside the current module you are autoloading.

If the object is outside of the module you are autoloading then it will throw this exception. In this instance, it will throw an exception because the Request key in the container is the <class masonite.request.Request> class which is outside of the app module (and inside the masonite.request module).

If you find yourself hitting this exception then move the object outside of a directory being autoloaded and into a separate directory outside of the autoloader and then you can manually bind into the container with a different key, or simply rename the class to something else. When using models, you can rename the model to whatever you like and then specify a __table__ attribute to connect the model to the specific table.

Annotations

Although it is useful to get the model by the actual container key name, it might not be as practical or even the best way to fetch models from the container.

The recommended approach is to simply fetch the class itself by using annotations so you can adjust the variable name and ensure consistency throughout your application.

Autoload Class

You may also want to autoload classes yourself. This may be useful if building a package and needing to get all classes from a certain location or even all instances from a certain directory.

Class Instances

This might in useful in the Masonite scheduling feature to grab all the classes that are subclasses of the Task class.

Autoloading class instances could look something like:

This will fetch all the classes in the app/models directory that are instances of the Model class. The classes attribute contains a dictionary of all the classes it found.

Class Collecting

If you don't want to get classes that are instances of another class then we can simply collect classes:

Getting Only Application Classes

You may have noticed that this autoload class will actually capture all classes found. These classes include the classes that were imported as well.

The default autoload in the config file only loads application specific classes.

We can only get application specific classes by passing a parameter in:

This will check the namespace on the class found and make sure it is apart of the path that is being searched.

Instantiating Classes

The classes that the autoloader finds are just classes themselves which are uninstantiated. This is great for fetching models but won't be good for fetching other objects like commands. Commands need to be instantiated when they go into the container by design so they can be found by Masonite and cleo.

We can tell the autoloader to instantiate the class by passing another parameter. This will simply instantiate the object without passing any parameters in.

If your class needs parameters then you should collect the classes and instantiate each one individually.

You can see that now all the objects found are instantiated.

Responses

Controller Methods

Most of the responses you will work with simply involve returning various data types / classes / objects in the controller method. For example, you may be used to returning a view.render() object in the controller method. This will return a View instance which Masonite will extract out the rendered html template from it.

Below is a list of all the responses you can return

Strings

You can simply return a string which will output the string to the browser:

This will set headers and content lengths similiar to a normal HTML response.

Views

You can return an instance of a View object which Masonite will then pull the HTML information that Jinja has rendered. This is the normal process of returning your templates. You can do so by type hinting the view class and using the render method:

Notice you can also pass in a dictionary as a second argument which will pass those variables to your Jinja templates.

JSON (Dictionaries / Lists)

There are a few ways to return JSON responses. The easiest way is to simply return a dictionary like this:

This will return a response with the appropriate JSON related headers.

Similiarly you can return a list:

JSON (Models)

If you are working with models then its pretty easy to return a model as a JSON response by simply returning a model. This is useful when working with single records:

This will return a response like this:

JSON (Collections)

If you are working with collections you can return something similiar which will return a slightly different JSON response with several results:

Which will return a response like:

JSON (Pagination)

If you need to paginate a response you can return an instance of Paginator. You can do so easily by using the paginate() method:

The value you pass in to the paginate method is the page size or limit of results you want to return.

This will return a response like:

You can override the page size and page number by passing in the appropriate query inputs. You can change the page you are looking at by passing in a ?page= input and you can change the amount of results per page by using the ?page_size= input.

If you are building an API this might look like /api/users?page=2&page_size=5. This will return 5 results on page 2 for this endpoint.

Request Class (Redirections)

You can also return a few methods on the request class. These are mainly used for redirection.

For redirecting to a new route you can return the redirect() method:

Response Class

The response class is what Masonite uses internally but you can explicit use it if you find the need to. A need might include setting a response in a middleware or a service provider where Masonite does not handle all the response converting for you. It is typically used to condense a lot of redundant logic down throughout the framework like getting the response ready, status codes, content lengths and content types.

Previously this needed to be individually set but now the response object abstracts a lot of the logic. You will likely never need to encounter this object during normal development but it is documented here if you need to use it similarly to how we use it in core.

JSON Responses

We can set a JSON response by using the json() method. This simply requires a dictionary:

This will set the Content-Type, Content-Length, status code and the actual response for you.

Keep in mind this is the same thing as doing:

Since Masonite uses a middleware that abstracts this logic.

View and Text Response

The view() method either takes a View object or a string:

Setting Status Codes

Status codes can be set in the controller methods by 1 of 2 ways. The first way is to use the response object like above but set a status= parameter. Something like thihs:

The second way is to use a normal response but return a tuple: The above example might look something like this:

Redirecting

You can also use some very basic URL redirection using the response object:

Responsable Classes

Responsable classes are classes that are allowed to be returned in your controller methods. These classes simply need to inherit a Responsable class and then contain a get_response method.

Let's take a look at a simple hello world example:

This class can now be returned in a controller method

Masonite will check if the response is an instance of Responsable and run the get_response method. This will show "Hello world" to the browser. This is actually how Masonites view class and mail classes work so you can see how powerful this can be.

Mailables

You can also return mailables. This is great if you want to debug what your emails will look like before you send them. You can do so by simply returning the mailable method of the mail class:

This will now show what the email will look like.

Download Images and Files

Sometimes you will want to return an image or a file like a PDF file. You can do with Masonite pretty easily by using the Download class. Simply pass it path to a file and Masonite will take care of the rest like setting the correct headers and getting the file content

This will display the image or file in the browser. You can also force a download in 1 of 2 ways:

Lastly you can change the name of the image when it downloads:

Service Container

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:

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

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

Simple Binding

Sometimes you really don't care what the key is for the object you are binding. For example you may be binding a Markdown class into the container but really don't care what the key binding is called. This is a great reason to use simple binding which will set the key as the object class:

Make

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

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.

Singleton

You can bind singletons into the container. This will resolve the object at the time of binding. This will allow the same object to be used throughout the lifetime of the server.

Has

You can also check if a key exists in the container by using the has method:

You can also check if a key exists in the container by using the in keyword.

Collecting

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.

Collect By Key

We can easily collect all objects based on a key:

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:

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"

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

Collecting By Object

You can also collect all subclasses of an object. You may use this if you want to collect all instances of a specific class from the container:

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 parameter list of any object. Certain aspects of Masonite are resolved such as controller methods, middleware and drivers.

For example, we can type hint that we want to get the Request class and put it into our controller. All controller methods are resolved by the container. Masonite 2.1 only supports annotations resolving by default:

In this example, before the show method is called, Masonite will look at the parameters and look inside the container for the Request object.

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. capabilities Think of this as a get by value instead of a get by key like the earlier example.

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:

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 Parameters

This feature should not be used and you should instead use the more explicit form of resolving in the section above.

You can technically still resolve parameters with your container like you could in previous versions of Masonite. Resolving a parameter looked like this:

Although this was removed in 2.1+, you may still enable it on a per project basis. To enable it, go to your wsgi.py file and add this to the constructor of your App class towards the top of the file:

Your project will now resolve parameters as well. Resolving parameters looks for the key in the container instead of the class.

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.

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.

Resolving With Additional Parameters

Sometimes you may wish to resolve your code in addition to passing in variables within the same parameter list. For example you may want to have 3 parameters like this:

You can resolve and pass parameter at the same time by adding them to the resolve() method:

Masonite will go through each parameter list and resolve them, if it does not find the parameter it will pull it from the other parameters specified. These parameters can be in any order.

Using the container outside of Masonite flow

If you need to utilize a container outside the normal flow of Masonite like inside a command then you can import the container directly.

This would look something like:

Container Swapping

Sometimes when you resolve an object or class, you want a different value to be returned.

Using a value:

We can pass a simple value as the second parameter to the swap method which will be returned instead of the object being resolved. For example this is used currently when resolving the Mail class like this:

but the class definition for the Mail class here looks like this:

How does it know to resolve the smtp driver instead? It's because we added a container swap. Container swaps are simple, they take the object as the first parameter and either a value or a callable as the second.

For example we may want to mock the functionality above by doing something like this in the boot method of a Service Provider:

Notice that we specified which class should be returned whenever we resolve the Mail class. In this case we want to resolve the default driver specified in the projects configurations.

Using a callable

Instead of directly passing in a value as the second parameter we can pass in a callable instead. The callable MUST take 2 parameters. The first parameter will be the annotation we are trying to resolve and the second will be the container itself. Here is an example of how the above would work with a callable:

Notice that the second parameter is a callable object. This means that it will be called whenever we try to resolve the Mail class.

Remember: If the second parameter is a callable, it will be called. If it is a value, it will simply be returned instead of the resolving object.

Container Hooks

Sometimes we might want to run some code when things happen inside our container. For example we might want to run some arbitrary function about we resolve the Request object from the container or we might want to bind some values to a View class anytime we bind a Response to the container. This is excellent for testing purposes if we want to bind a user object to the request whenever it is resolved.

We have three options: on_bind, on_make, on_resolve. All we need for the first option is the key or object we want to bind the hook to, and the second option will be a function that takes two arguments. The first argument is the object in question and the second argument is the whole container.

The code might look something like this:

Notice that we create a function that accepts two values, the object we are dealing with and the container. Then whenever we run on_make, the function is ran.

We can also bind to specific objects instead of keys:

This then calls the same attribute but anytime the Request object itself is made from the container. Notice everything is the same except line 6 where we are using an object instead of a string.

We can do the same thing with the other options:

Strict and Overriding

By default, Masonite will not care if you override objects from the container. In other words you can do this:

Without issue. Notice we are binding twice to the same key. You can change this behavior by specifying 2 values in the constructor of the App class:

Override

If override is False, it will not override values in the container. It will simply ignore them if you are trying to bind twice to the same key. If override is True, which it is by default, you will be allowed to override keys in the container with new values by binding them.

Strict

Strict will throw an exception if you try binding a key to the container. So with override being False it simply ignored binding a key to the container that already exists, setting strict to True will actually throw an exception.

Remembering

Container remembering is an awesome feature where the container will remember the objects you passed in and instead of resolving the object over and over again which could possibly be expensive, it will pass in the previous objects that were passed in by storing and retrieving them from a separate remembering list the container builds.

Once an object is resolved for the first time, a new dictionary is built containing that object and it's dependencies. The second time that object is resolved, instead of inspecting the object to see which dependencies it should pass in, Masonite will inject the ones from the remembering dictionary.

This can speed up resolving your code by 10 - 15x. A significant speed improvement.

You can enable this on a per application basis by setting the remembering parameter to True on your container class. This can be found in your wsgi.py file:

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:

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

Extending a class method

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

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.

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:

Route Middleware

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:

Middleware Stacks

Route middleware have the unique option of also being stacks of middleware (or lists, really). So we can specify a middleware to have a list of middleware instead of one string based middleware:

Notice that we can use both lists and strings for middleware. As a list, all the middleware in the list will run when we call the auth middleware stack.

Default Middleware

There are several default middleware that come 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:

Verify Email Middleware

This middleware checks to see if the logged in user has verified their email. If they haven't it will redirect the user to a page reminding them to verify their email.

You can run this middleware on any route by specifying the key in the middleware method on your route:

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:

JsonResponseMiddleware

This middleware will convert dictionary responses into JSON responses

ResponseMiddleware

This middleware will take the response provided and add the necessary headers to the response

Middleware Parameters

You can pass parameters from your routes to your middleware in cases where a middleware should act differently depending on your route.

You can do this with a : symbol next to your route middleware name and then pass in those parameters to the before and after middleware methods.

For example, we may be creating a middleware for request throttling and in our routes we have something like this:

notice the throttle:2,100 syntax. The 2 and the 100 will then be passed into the before and after methods of your middleware:

Request Parameters

Similiar to the way we can pass values to a middleware using the : splice we can also use the @ in the value to pass the value of the parameter.

For example, we may create a route and a middleware like this

If we go to a route like /dashboard/152/settings then the value of 152 will be passed to the middleware before and after methods.

Creating Middleware

Again, middleware should live inside the app/http/middleware folder and should look something like:

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:

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:

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:

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:

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.

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, migrations 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. You also don't need to build for one use case or the other. You can easily build a Python package that can be used specifically for any Python package but also create a way it can wire directly into Masonite using Service Providers. We'll talk more about this later.

About Packages

There are several key functions that Masonite uses in order to create applications. These include primarily: routes, controllers, views, migrations, 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 can also use the publish command which will look something like craft publish YourServiceProvider. There really isn't much of a difference in terms of functionality but the install command will require you to manually copy things to where they need to go and the built in publish command takes care of some of these things for you.

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

Just go to the repo and download a zip of the file. It's not beneficial to simply git clone it since you will eventually need to create your own repository and cloning the repo will still track the existing starter-package repo.

Once downloaded you can unzip it to wherever you want on your machine. From there you should create a virtual environment and run the tests:

Create Your Virtual Environment

Activate Your Virtual Environment

Activating your virtual environment on Mac and Linux is simple:

or if you are on Windows:

Install Requirements

Now you can install some development pypi packages to help you in your package development journey like flake8 and pytest.

The starter package comes with a make file to help you get started faster. Just run:

This will install the craft CLI tool as well as some other requirement packages.

Run the tests

Now you can run the tests to make sure everything is working properly:

You should see all the basic setup tests passing. Now you can start your TDD flow or start building tests around your package.

The testing suite is the full Masonite testing suite so be sure to read the Testing docs.

Building an Install Command

First we will walk through how to create a simple install command. This will show you how to move things manually into the correct locations. After, we will look into using the publish command which will automate some of these things. If you want to skip to that part you can scroll down a bit to the next section.

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.

Remember you have access to craft commands so you can do something like:

You'll need to move your command inside the src/package directory but it will prevent you from having to write a lot of boiler plate while developing your package.

Just move these generated files into the src/package/commands and src/package/providers directories. You may have to create these directories if they don't exist.

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:

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. When you run migrations you will now see something like this:

Notice how we are now migrating several directories at a time rather than only from a single directory. If this is the approach you want your package to take then this is the best way to do it.

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

There are a few package helpers you can use to integrate your package into a Masonite application. 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.

Let's create a basic config file and then have the install command copy it over when they run the command. Let's create a simple file in src/package/snippets/configs/services.py

To help with this, Masonite has put a variable called package_directory here. Our handle method inside our install command should look something like:

Now when you run the install command it will create or append the config to the config/services.py file.

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:

If you make changes you'll need to uninstall and reinstall the package buy running something like:

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

Uploading to PyPi

So we have our package installable as well as tested and integrated nicely into an existing Masonite app. Now it's time to upload it to PYPI so everyone can install it.

We made it really easy to do this with the make commands.

.pypirc File

If you have uploaded PYPI packages before you'll know you need a file in your home directory.

If you do not have one then you go create one using another make command:

This will move the file to your home directory. If you are using windows you may need to move this file manually.

Make sure you go to your setup.py file and change all the package configurations. There are lots of comments next to each option so if you are unsure what it does then just give it a read.

Once you have your setup.py file looking correct you can run the make command:

This will install twine if it doesn't exist, build the package, upload it to PYPI and delete the artifacts.

Easy, right?

Consuming a package.

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

Then add your Service Provider to the PROVIDERS list:

and then run:

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.

Publishing

Masonite has the concept of publishing packages. This allows you to manage the integration with your package and Masonite in a more seamless way. Publishing allows you to add things like routes, views, migrations and commands easily into any Masonite app and it is all handled through your service provider

The goal is to have a developer run:

This should be the name of your provider class.

and have all your assets moved into the new Masonite application.

Publishing Files

You can create or append any files you need to in a developers masonite application. This can be used for any files to include commands, routes, config files etc.

For example let's say you have a directory in your package like:

Inside our service provider we can do this:

Notice our command path is 1 directory back inside the commands directory. We then combine the directory with the RuleCommand.py file and tell Masonite to put it inside the app/commands/RuleCommand.py module inside the users directory.

The user of your package will now have a new command in their application!

Publishing Migrations

You can take any migrations in your package and send them to the Masonite applications migration directory. This is useful if you want to have some developers edit your custom migrations before they migrate them.

For example let's say you have a directory in your package like:

The migrations like user_migration.py should be full migration files.

Then you can have a service provider like this:

This will create a new migration in the users directory.

Publishing Tags

You can also add tags to each of these migrations as well. For example if you have 2 sets of migrations you can do this instead:

Now a user can only either publish migrations or commands by adding a --tag option

This will ignore the commands publishing and only publish the migrations

Read more about the IOC container in the documentation.

Read more about helper functions in the documentation.

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 continue on.

You can read more about routes in the documentation

Let's create the BlogController in the next step:

Be sure to learn more about the .

and head over to and fill out the form. You can use whatever name and email you like but for this purpose we will use:

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.

We won't go into much more detail here about different types of relationships but to learn more, read the documentation.

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 now used request: 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.

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 .

Be sure to read the documentation to learn more about validators.

Read more about Service Providers under the documentation.

Jobs will be put inside the app/jobs directory. See the documentation for more information.

You'll need to read the documentation in the documentation for futher info on how to setup this feature.

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.

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.

Views use for it's template rendering. You can read about Jinja2 at the .

This section requires knowledge of and how the works. Be sure to read those documentation articles.

Learn more about session in the documentation.

If you are more of a visual learner you can watch Masonite related tutorial videos at

Be sure to join the for help or guidance.

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

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.

Read more about creating seed classes with the .

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/providers.py file, you will find a PROVIDERS list which contains all the Service Providers involved in building the framework.

Masonite has a which allows you to add objects into the container and have them auto resolved in controllers and other classes. This is an excellent feature and what makes Masonite so powerful. The most obvious way to load classes into the container is through creating a and interacting with the container from there.

If this seems like a strange syntax to you, be sure to read the section of the documentation.

There are several different ways for redirecting like redirecting to a named route or redirecting back to the previous route. For a full list of request redirection methods read the docs.

Read more about this in the documentation.

The best way to create a package is to download the .

It's great (and convenient) to add craft commands to a project so developers can use your package more efficiently. You can head over to 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.

Service Container
Helper Functions
Installation
Routing
Part 2 - Creating Our First Controller
Service Container
http://localhost:8000/register
Database Migrations here
Database Migrations
ORM
read their documentation
Static Files
Service Container
Service Container
Service Container
Requests
Service Providers
Service Container
Middleware
Cleo Documentation
Validation
Service Provider
Queues and Jobs
Queues and Jobs
Creating Packages
Encryption
Service Container
Service Container
Jinja2
official documentation here
Service Providers
Service Container
Session
MasoniteCasts.com
Slack Channel
The Craft Command
Routing
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))
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))
from masonite.driver import BaseMailDriver

class MailMaildrillDriver(BaseMailDriver):
    pass
import requests
from masonite.drivers 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})
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):
        self.app.bind('Request', Request(self.app.make('Environ')))
        self.app.bind('Route', Route(self.app.make('Environ')))
def show(self, Mail)
    Mail.driver('mandrill') # fetches MailMandrillDriver from container
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'
    }
}
craft seed User
craft seed:run
craft seed:run User
$ craft provider DashboardProvider
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'))
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: User):
        print(user)
def register(self):
    self.commands(Command1(), Command2())
def register(self):
    self.http_middleware([Middleware, Here])
    self.route_middleware({'middleware': Here})
def register(self):
    self.migrations('directory/1', 'directory/2')
def register(self):
    self.routes([
        get(...),
    ])
def register(self):
    self.assets({'/directory': 'alias/'})
def show(self, request: Request):
    print(Session) # Session class
def show(self, request: Request):
    request.session.set('key', 'value')
def show(self, request: Request):
    request.session.set('key', {'key', 'value'})
def show(self, request: Request):
    request.session.flash('key', {'key', 'value'})
def show(self, request: Request):
    request.session.get('key') # Returns 'value'
def show(self, request: Request):
    request.session.has('key') # Returns True
def show(self, request: Request):
    request.session.all() # Returns {'key': 'value'}
def show(self, request: Request):
    request.session.flash('success', 'Your action is successful')
from masonite.managers import SessionManager
def show(self, session: SessionManager):
    session.driver('cookie').set('key', 'value')
{% if session().has('success') %}
    <div class="alert alert-success">
        {{ session().get('success') }}
    </div>
{% endif %}
{% 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>

{% elif session().has('danger') %}

    <div class="alert alert-danger">
        {{ session().get('danger') }}
    </div>

{% endif %}
{% include 'helpers/messages.html' %}
def show(self):
    return request().redirect('/dashboard') \
        .session.flash('success', 'Action Successful!')
def show(self, request: Request):
    request.session.flash('success', 'Action Successful!')

    return request().redirect('/dashboard')
def show(self, request: Request):
    request.session.reset()
def show(self, request: Request):
    request.session.reset(flash_only=True)
AUTOLOAD = [
    'app',
]
from orator.orm import belongs_to
from config.database import Model

class User(Model):
    pass
app/http/controllers/YourController.py
from app.User import User

def show(self, user: User):
    user.find(1)
app/
  http/
  providers/
  models/
    Blog.py
    Author.py
  User.py
AUTOLOAD = [
    'app',
    'app/models'
]
app/http/controllers/YourController.py
from app.User import User
from app.Blog import Blog
from app.Author import Author

def show(self, user: User, blog: Blog, author: Author):
    user.find(1)
    blog.find(1)
    author.find(1)
AUTOLOAD = [
    'app/',
    'app/models'
]
app/
  http/
  providers/
  User.py
  Request.py
bootstrap/
...
from app.User import User

def show(self, author: User):
    author.find(1)
from masonite.autoload import Autoload
from orator.orm import Model

classes = Autoload().instances(['app/models'], Model)
# returns {'Model': <config.database.Model>, 'User': <class app.models.User.User>, ...}
from masonite.autoload import Autoload
from orator.orm import Model

classes = Autoload().collect(['app/models'])
# returns {'Model': <config.database.Model>, 'User': <class app.models.User.User>, ...}
from masonite.autoload import Autoload
from orator.orm import Model

classes = Autoload().collect(['app/models'], only_app=True)
# returns {'User': <class app.models.User.User>, ...}
from masonite.autoload import Autoload
from orator.orm import Model

classes = Autoload().collect(['app/models'], only_app=True, instantiate=True)
# returns {'User': <app.models.User.User at 0x10e36d780>, ...}
def show(self):
    return 'string here'
from masonite.view import View

def show(self, view: View):
    return view.render('your/template', {'key': 'value'})
def show(self):
    return {'key': 'value'}
def show(self):
    return [1,2,3,4]
from app.User import User
# ...

def show(self):
    return User.find(1)
{
    "id": 1,
    "name": "Brett Huber",
    "email": "hubbardtimothy@gmail.com",
    "password": "...",
    "remember_token": "...",
    "verified_at": null,
    "created_at": "2019-08-24T01:26:42.675467+00:00",
    "updated_at": "2019-08-24T01:26:42.675467+00:00",
}
from app.User import User
# ...

def show(self):
    return User.all()
[
    {
        "id": 1,
        "name": "Brett Huber",
        "email": "hubbardtimothy@gmail.com",
        "password": "...",
        ...
    },
    {
        "id": 2,
        "name": "Jack Baird",
        "email": "phelpsrebecca@stanley.info",
        "password": "...",
        ...
    },
    ...
    }
]
from app.User import User
# ...

def show(self):
    return User.paginate(10)
{
    "total": 55,
    "count": 10,
    "per_page": 10,
    "current_page": 1,
    "last_page": 6,
    "from": 1,
    "to": 10,
    "data": [
        {
            "id": 1,
            "name": "Brett Huber",
            "email": "hubbardtimothy@gmail.com",
            "password": "...",
            ...
        },
        {
            ...
        }
from masonite.request import Request
# ...

def show(self, request: Request):
    return request.redirect('/some/route')
from masonite.response import Response

def show(self, response: Response):
    return response.json({'key': 'value'})
def show(self):
    return {'key': 'value'}
from masonite.response import Response
from masonite.view import View

def show(self, response: Response, view: View):
    return response.view('hello world')

def show(self, response: Response, view: View):
    return response.view(view.render('some.template'))
from masonite.response import Response
from masonite.view import View

def show(self, response: Response, view: View):
    return response.view('hello world', status=401)
from masonite.response import Response
from masonite.view import View

def show(self, response: Response, view: View):
    return 'hello world', 401
from masonite.response import Response

def show(self, response: Response):
    return response.redirect('/some/route')
from masonite.response import Responsable

class HelloWorld(Responsable):

    def get_response(self):
        return 'hello world'
from some.place import HelloWorld

def show(self):
    return HelloWorld()
from app.mailables import WelcomeEmail

def show(self, mail: Mail):
    return mail.mailable(WelcomeEmail())
from masonite.response import Download

def show(self):
    return Download('path/to/file.png')
from masonite.response import Download

def show(self):
    return Download('path/to/file.png').force()
    return Download('path/to/file.png', force=True)
from masonite.response import Download

def show(self):
    return Download('path/to/file.png').force()
    return Download('path/to/file.png', name="new-file-name.jpg", force=True)
from masonite.provider import ServiceProvider
from app.User import User

class UserModelProvider(ServiceProvider):

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

    def boot(self):
        pass
>>> app.providers
{'User': <class app.User.User>}
def show(self, request: Request):
    request.app() # will return the service container
from masonite.provider import ServiceProvider
from app.User import User


class UserModelProvider(ServiceProvider):

    def register(self):
        self.app.simple(User)

    def boot(self):
        pass
>>> from app.User import User
>>> app.bind('User', User)
>>> app.make('User')
<class app.User.User>
from masonite.provider import ServiceProvider
from app.helpers import SomeClass


class UserModelProvider(ServiceProvider):

    def register(self):
        self.app.singleton('SomeClass', SomeClass)

    def boot(self):
        pass
app.has('Request')
'Request' in app
app.collect('*ExceptionHook')
app.collect('Sentry*')
app.collect('Sentry*Hook')
from cleo import Command
...
app.collect(Command)
# Returns {'FirstCommand': <class ...>, 'AnotherCommand': ...}
def show(self, request: Request):
    request.user()
from masonite.contracts import UploadContract

def show(self, upload: UploadContract)
    upload # <class masonite.drivers.UploadDiskDriver>
from masonite.request import Request

def show(self, request: Request):
    request.user()
container = App(resolve_parameters=True)
from masonite.request import Request
from masonite.view import View

def randomFunction(view: View):
    print(view)

def show(self, request: Request):
    request.app().resolve(randomFunction) # Will print the View object
from masonite.request import Request
from masonite import Mail

def send_email(request: Request, mail: Mail, email):
    pass
app.resolve(send_email, 'user@email.com')
from wsgi import container
from masonite import Queue

class SomeCommand:

    def handle(self):
        queue = container.make(Queue)
        queue.push(..)
from masonite import Mail

def show(self, mail: Mail):
    mail #== <masonite.drivers.MailSmtpDriver>
class Mail:
    pass
from masonite import Mail

def boot(self, mail: MailManager):
    self.app.swap(Mail, manager.driver(self.app.make('MailConfig').DRIVER))
from masonite import Mail
from somewhere import NewObject
...

def mail_callback(obj, container):
    return NewObject
...

def boot(self):
    self.app.swap(Mail, mail_callback)
from masonite.request import Request

def attribute_on_make(request_obj, container):
    request_obj.attribute = 'some value'

...

container = App()

# sets the hook
container.on_make('Request', attribute_on_make)
container.bind('Request', Request)

# runs the attribute_on_make function
request = container.make('Request')
request.attribute # 'some value'
from masonite.request import Request

...

# sets the hook
container.on_make(Request, attribute_on_make)
container.bind('Request', Request)

# runs the attribute_on_make function
request = container.make('Request')
request.attribute # 'some value'
container.on_bind(Request, attribute_on_make)
container.on_make(Request, attribute_on_make)
container.on_resolve(Request, attribute_on_make)
app.bind('number', 1)
app.bind('number', 2)
container = App(strict=True, override=False)
container = App(remembering = True)
def is_authenticated(self):
    return self

def show(self, request: Request):

    request.extend(is_authenticated)

    print(request.is_authenticated()) # returns the Request class
def is_authenticated(self):
    return self

def show(self, request: Request):

    request.extend(is_authenticated)

    print(request.is_authenticated()) # returns the Request class
class Authentication:

    def is_authenticated(self):
        return self

def show(self, request: Request):

    request.extend(Authentication.is_authenticated)

    print(request.is_authenticated()) # returns the Request class
class Authentication:

    def is_authenticated(self):
        return self

    def login(self):
        return self

def show(self, request: Request):

    request.extend(Authentication)

    print(request.is_authenticated()) # returns the Request class
    print(request.login()) # returns the Request class
from app.http.middleware.DashboardMiddleware import DashboardMiddleware

HTTP_MIDDLEWARE = [
    DashboardMiddleware,
]
from app.http.middleware.RouteMiddleware import RouteMiddleware

ROUTE_MIDDLEWARE = {
    'auth': RouteMiddleware,
}
from app.http.middleware.AdminMiddleware import AdminMiddleware
from app.http.middleware.AuthMiddleware import AuthMiddleware
from app.http.middleware.VerifyMiddleware import VerifyMiddleware

ROUTE_MIDDLEWARE = {
    'admin': AdminMiddleware,
    'auth': [
        AuthMiddleware,
        VerifyMiddleware,
    ]
}
routes/web.py
from masonite.routes import Get
...

ROUTES = [
    ...
    Get('/dashboard', 'DashboardController@show').middleware('auth')
]
routes/web.py
from masonite.routes import Get
...

ROUTES = [
    ...
    Get('/dashboard', 'DashboardController@show').middleware('verified')
]
app/http/controllers/YourController.py
def show(self):
    return request().user() # Returns the user or None
Get('/feeds', 'FeedController').middleware('throttle:2,100')
class ThrottleMiddleware:

    def before(self, minutes, requests):
        # throttle requests

    def after(self, minutes, requests):
        # throttle requests
Get('/dashboard/@user_id/settings', 'FeedController').middleware('permission:@user_id')
class AuthenticationMiddleware:
    """Middleware class
    """

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

    def before(self):
        pass

    def after(self):
        pass
class AuthenticationMiddleware:
    """Middleware class which loads the current user into the request
    """

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

    def before(self):
        if not self.request.user():
            self.request.redirect_to('login')

    def after(self):
        pass
from app.http.middleware.DashboardMiddleware import DashboardMiddleware
HTTP_MIDDLEWARE = [
    DashboardMiddleware,
]
from app.http.middleware.RouteMiddleware import RouteMiddleware

ROUTE_MIDDLEWARE = {
    'auth': RouteMiddleware,
}
Get().route('/dashboard', 'DashboardController@show').name('dashboard').middleware('auth')
Get().route('/login', 'LoginController@show').name('login')
$ python3 -m venv venv
terminal
$ source venv/bin/activate
terminal
$ ./venv/Scripts/activate
$ make init
$ python -m pytest
$ craft command Install
$ craft provider Package
import os

from masonite.provider import ServiceProvider

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

class PackageProvider(ServiceProvider):

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

Migrating: /Users/joseph/Programming/packages/starter/src/package/providers/../migrations 

[OK] Migrated 2019_12_31_041847_create_package_table
"""Config File."""

DRIVER = 'service1'
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

    package:install
    """

    def handle(self):
        create_or_append_config(
            os.path.join(
                package_directory,
                '../snippets/configs/services.py'
            )
        )
$ pip install .
$ pip uninstall your-package && pip install .
make pypirc
$ make publish
$ pip install super-awesome-package
from package.providers.InstallProvider import InstallProvider

PROVIDERS = [
    ...
    # New Provider
    InstallProvider,
]
$ craft package:install
$ craft publish YourProvider
validation/
  providers/
    ValidationProvider.py
  commands/
    RuleCommand.py
setup.py
import os

class ValidationProvider(ServiceProvider):

    wsgi = False

    def register(self):
        pass

    def boot(self):
        command_path = os.path.join(os.path.dirname(__file__), '../commands')

        self.publishes({
            os.path.join(command_path, 'RuleCommand.py'): 'app/commands/RuleCommand.py'
        })
validation/
  providers/
    ValidationProvider.py
  migrations/
    user_migration.py
    team_migration.py
setup.py
import os

def boot(self):
    migration_path = os.path.join(os.path.dirname(__file__), '../migrations')

    self.publishes_migrations([
        os.path.join(migration_path, 'user_migration.py'),
        os.path.join(migration_path, 'team_migration.py'),
    ])
import os

def boot(self):
    migration_path = os.path.join(os.path.dirname(__file__), '../migrations')
    command_path = os.path.join(os.path.dirname(__file__), '../commands')

    self.publishes({
        os.path.join(command_path, 'RuleCommand.py'): 'app/commands/RuleCommand.py'
    }, tag="commands")

    self.publishes_migrations([
        os.path.join(migration_path, 'user_migration.py'),
        os.path.join(migration_path, 'team_migration.py'),
    ], tag="migrations")
$ craft publish ValidationProvider --tag migrations

Status Codes

Introduction

Status codes are a crucial part of any application. They allow your users to identify exactly what went wrong with your site without showing too much information. By default, Masonite will show generic 404 pages when a url is missed.

When APP_DEBUG is True in your .env file, an exception view is shown to help you debug your application. When it is False then it will show a generic 500 error page.

This behavior is through the StatusCodeProvider in your PROVIDERS list. In addition to this behavior we can also show our own error pages.

How It Works

Masonite will first look for the error that is being thrown in your resources/templates/errors directory and render that template. If one does not exist then it will return a generic view from the Masonite package itself.

Usage

For example if a 404 Not Found error will be thrown then it will first check in resources/templates/errors/404.html and render that template. This is the same behavior for 500 Server Not Found errors and other errors thrown.

Environments

Introduction

Environments in Masonite are defined in .env files and contain all your secret environment variables that should not be pushed into source control. You can have multiple environment files that are loaded when the server first starts. We'll walk through how to configure your environment variables in this documentation.

Never load any of your .env files into source control. .env and .env.* are in the .gitignore file by default so you should not worry about accidentally pushing these files into source control.

Getting Started

Masonite comes with a LoadEnvironment class that is called in the bootstrap/start.py file. This file in imported into the wsgi.py file which is where the execution of the environment actually happens because of the import.

You likely won't have to use this class since this class handles most use cases by default but we will go over how the class itself works.

In bootstrap/start.py you will see a code that looks something like:

bootstrap/start.py
from masonite.environment import LoadEnvironment
...
LoadEnvironment()

This class instantiation does a few things:

The first thing is it loads the .env file located in the base of your application into the Python environment. If you installed Masonite using craft install then Masonite automatically create this .env file for you based on the .env-example file. If you have installed Masonite but do not see this .env file then you can create it manually and copy and paste the contents of .env-example.

The next thing it will do is look for an APP_ENV variable inside your .env file it just loaded and then look for an environment with that value.

For example, this variable:

.env
...
APP_ENV=local
...

Will load additionally load the .env.local environment file.

This may be useful to have more global environment variables that can be shared across your team like Stripe, Mailgun, or application keys and then have more developer specific values like database connections, Mailtrap or different storage drivers for development.

Loading Additional Environments

In addition to loading the .env file and the additional environment file defined in your .env file, you can load a third environment by specifying it in the constructor:

.env
...
APP_ENV=local
...
bootstrap/start.py
from masonite.environment import LoadEnvironment
...
LoadEnvironment('development')

This will load the .env file, the .env.local file and the .env.development environment file.

Loading Only A Single Environment

If you don't want to load an additional environment and instead want to load only 1 single environment then you can pass in the only parameter.

bootstrap/start.py
from masonite.environment import LoadEnvironment
...
LoadEnvironment(only='development')

This will load only the .env.development environment file.

Getting Environment Variables

Environment variables should be set on a project per project basis inside your .env file. When the server starts, it will load all of those environment variables into the current global environment. You can fetch these environment variables 1 of 2 ways:

os.getenv

You can obviously get them in the normal Python way by doing something like:

import os

os.getenv('DB_PORT') #== '5432' (string)

Notice that the above example is a string. We typically need the data type to be casted to the respective type. For example we need 5432 to be an integer and need True to be a boolean.

masonite.env

We can use the env() function in order to accomplish this which takes the place of os.getenv(). This looks like:

from masonite import env

env('DB_PORT', 'default') #== 5432 (int)

If the value is a numeric then it will cast it to an integer. Below are the examples of what this function will cast:

Value

Casts to (type)

5432

5432 (int)

true

True (bool)

True

True (bool)

false

False (bool)

False

False (bool)

smtp

smtp (string)

If you do not wish to cast the value then pass in false as the third parameter:

from masonite import env
​
env('DB_PORT', 'default', cast=False) #== '5432' (string)

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:

from masonite import Broadcast

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

We can change the driver on the fly as well:

from masonite import Broadcast

def show(self, broadcast: 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:

from masonite import Broadcast

def show(self, broadcast: 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:

from masonite import Broadcast

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

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

from masonite import Broadcast

def show(self, broadcast: 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:

from masonite import Broadcast

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

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

Changing Drivers

You can also swap drivers on the fly:

from masonite import Broadcast

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

or you can explicitly specify the class:

from masonite.drivers import BroadcastAblyDriver

from masonite import Broadcast

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

Events

Introduction

Masonite Events is a simple to use integration for subscribing to events. These events can be placed throughout your application and triggered when various actions occur. For example you may want to do several actions when a user signs up like:

  • Send an email

  • Subscribe to MailChimp

  • Update a section of your database

These actions can all be executed with a single line of code in you controller once you have setup your listeners

Getting Started

Installation

First we'll need to install the package using pip:

$ pip install masonite-events

Service Provider

Once installed we'll need to add the provider to our providers list:

config/providers.py
from events.providers import EventProvider
...

PROVIDERS = [
    ...
    EventProvider,
    ...
]

Commands

This Service Provider will add a new event:listener command we can use to create new events. We'll walk through step by step on how to create one below.

Creating an Event Listener

Masonite Events allows you to subscribe to arbitrary event actions with various event listeners. In this article we will walk through the steps on setting up listeners for a new user subscribing to our application.

We can create our event listener by executing the following command:

$ craft event:listener SubscribeUser

This will create a new event in the app/events directory that looks like this:

""" A SubscribeUser Event """
from events import Event

class SubscribeUser(Event):
    """ SubscribeUser Event Class """

    subscribe = []

    def __init__(self):
        """ Event Class Constructor """
        pass

    def handle(self):
        """ Event Handle Method """
        pass

About the Listener class

This is a very simple class. We have the __init__ method which is resolved by the container and we have a handle method, also resolved by the container.

This means that we can use syntax like:

from masonite.request import Request
...
def __init__(self, request: Request):
    """ Event Class Constructor """
    self.request = request
...

Subscribe Attribute

The subscribe attribute can be used as a shorthand later which we will see but it should be a list of events we want to subscribe to:

""" A SubscribeUser Event """
from events import Event
from package.action import SomeAction

​
class SubscribeUser(Event):
    """ SubscribeUser Event Class """
​
    subscribe = ['user.subscribed', SomeAction]
​
    def __init__(self):
        """ Event Class Constructor """
        pass
​
    def handle(self):
        """ Event Handle Method """
        pass

Subscribing to events

We can listen to events simply by passing the listener into one of your applications Service Provider's boot methods. Preferably this Service Provider should have wsgi=False so that you are not continuously subscribing the same listener on every request.

You likely have a Service Provider whose wsgi attribute is false but let's create a new one:

$ craft provider ListenerProvider

Make sure we set the wsgi attribute to False:

''' A ListenerProvider Service Provider '''
from masonite.provider import ServiceProvider

class ListenerProvider(ServiceProvider):

    wsgi = False

    def register(self):
        pass

    def boot(self):
        pass

Now we can import our listener and add it to our boot method:

''' A ListenerProvider Service Provider '''
from masonite.provider import ServiceProvider
from app.events.SubscribeUser.SubscribeUser
from events import Event

class ListenerProvider(ServiceProvider):

    wsgi = False

    def register(self):
        pass

    def boot(self, event: Event):
        event.subscribe(SubsribeUser)

This is the recommended approach over the more manual approach found below but both options are available if you find a need for one over the other.

Since we have a subscribe attribute on our listener, we can simply pass the action into the subscribe method. This will subscribe our listener to the SomeAction and the user.subscribed action that we specified in the subscribe attribute of our listener class.

Manually listening to events

If we don't specify the actions in the subscribe attribute, we can manually subscribe them using the listen method in our Service Provider's boot method:

''' A ListenerProvider Service Provider '''
from masonite.provider import ServiceProvider
from app.events.SubscribeUser.SubscribeUser
from events import Event

class ListenerProvider(ServiceProvider):

    wsgi = False

    def register(self):
        pass

    def boot(self, event: Event):
        event.listen('user.subscribed', [SubsribeUser])

Ensure that the second parameter in the listen method is a list; even if it has only a single value

Firing events

Now that we have events that are being listened to, we can start firing events. There are two ways to fire events. We can do both in any part of our application but we will go over how to do so in a controller method.

Fire Method

from events import Event
...
def show(self, event: Event):
    event.fire('user.subscribed')

Event Helper Method

Masonite Events also comes with a new builtin helper method:

def show(self):
    event('user.subscribed')

Both of these methods will fire all our listeners that are listening to the user.subscribed event action.

Class Events

As noted briefly above, we can subscribe to classes as events:

from package.action import SomeAction

def show(self):
    event(SomeAction)

This will go through the same steps as an event subscribed with a string above.

Wildcard Events

We can also fire events using an * wildcard action:

def show(self):
    event('user.*')

This will fire events such as user.subscribed, user.created, user.deleted.

We can also fire events with an asterisk in the front:

def show(self):
    event('*.created')

This will fire events such as user.created, dashboard.created and manager.created.

We can also fire events with a wildcard in the middle:

def show(self):
    event('user.*.created')

This will fire events such as user.manager.created, user.employee.created and user.friend.created.

Passing Arguments

Sometimes you will want to pass an argument from your controller (or wherever you are calling your code) to your event's handle method. In this case you can simply pass keyword arguments to your fire method like so:

def show(self, event: Event):
    event.fire('user.subscribed', to='user@email.com', active='True')

and you can fetch these values in your handle method using the argument method:

class SubscribeUser(Event):
    """ SubscribeUser Event Class """
​
    subscribe = ['user.subscribed', SomeAction]
​
    def __init__(self):
        """ Event Class Constructor """
        pass
​
    def handle(self):
        """ Event Handle Method """
        self.argument('to') # user@email.com
        self.argument('active') # True

Testing

Introduction

Masonite testing is very simple. You can test very complex parts of your code with ease by just extending your class with a Masonite unit test class.

Although Masonite uses pytest to run tests, Masonite's test suite is based on unittest. So you will use unittest syntax but run the tests with Pytest. Because of this, all syntax will be in camelCase instead of PEP 8 under_score. Just know that all TestCase method calls used during testing is in camelCase form to maintain unittest standards.

Normal tests should still be underscore and start with test_ though like this:

def test_user_can_login(self):
    pass

Configuration

First, create a new test class in a testing directory. There is a craft command you can run to create tests for you so just run:

$ craft test User

This will create a user test for us which we can work on. You can drag this test in any subdirectory you like.

This command will create a basic test like the one below:

tests/test\user.py
"""Example Testcase."""

from masonite.testing import TestCase


class TestUser(TestCase):

    transactions = True

    def setUp(self):
        super().setUp()

    def setUpFactories(self):
        pass

That's it! You're ready to start testing. Read on to learn how to start building your test cases.

Environments

Most times you want to develop and test on different databases. Maybe you develop on a local MySQL database but your tests should run in a SQLlite database.

You can create a .env.testing file and put all database configs in that. When Pytest runs it will additionally load and override any additional environment variables.

Your .env.testing file may look like this:

DB_CONNECTION=sqlite
DB_HOST=127.0.0.1
DB_DATABASE=masonite.db
DB_LOG=False

STRIPE_CLIENT=test_sk-9uxaxixjsxjsin
STRIPE_SECRET=test_sk-suhxs87cen88h7

Feel free to load any testing environment variables in here. By default they will not be commited.

Available Assertion Methods

Here is a list of methods that can be used for assetions.

All methods that begin with assert can be chained together to run through many assertions. All other method will return some kind of boolean or value which you can use to do your own assertions.

assertContains(value)

assertHasAmount(amount)

assertHeaderIs(key, value)

assertNotFound()

assertNotHasAmount(amount)

assertPathIs(value)

assertHasJson(key, value)

assertParameterIs(parameter, value)

isNamed(name)

assertJsonContains(key, value)

assertIsStatus(code)

hasMiddleware(*middleware)

assertCount(number)

assertHasHeader(name)

assertNotHasHeader(name)

hasController(name)

contains(value)

ok()

canView()

hasJson(key, value)

count(number)

amount(number)

isGet()

isPost()

isPut()

isPatch()

isDelete()

hasSession(key, value)

parameterIs()

headerIs()

Calling Routes

We have a few options for testing our routes.

Testing If a Route Exists:

To check if a route exists, we can simple use either get or post:

tests/test_unit.py
def test_route_exists(self):
    self.assertTrue(self.get('/testing'))
    self.assertTrue(self.post('/testing'))

Getting the Request and Response

The request and responses of a test are gotten by accessing the request and response attributes. The response attribute will be a string representation of your route:

def test_request_and_response(self):
    request = self.get('/testing').request # <masonite.request.Request>
    response = self.get('/testing').response # HTML code

Method options

You can choose anyone of the normal request methods:

def test_route_exists(self):
    self.get('/testing')
    self.post('/testing')
    self.put('/testing')
    self.patch('/testing')
    self.delete('/testing')

JSON Requests

You can use a standard JSON request and specify whichever option you need using the json() method:

def test_route_exists(self):
    self.json('POST', '/testing', {'user': 'Joe'})

Testing If Route Has The Correct Name

tests/test_unit.py
def test_route_has_the_correct_name(self):
    self.assertTrue(self.get('/testing').isNamed('testing.route'))

Testing If A Route Has The Correct Middleware

tests/test_unit.py
def test_route_has_route_middleware(self):
    assert self.get('/testing').hasMiddleware('auth', 'owner')

Testing If A Route Contains A String

This can be used to see if the template returned a specific value

tests/test_unit.py
def test_view_contains(self):
    assert self.get('/login').contains('Login Here')

You can also use:

self.get('/login').assertContains('Login Here')

Checking 200 Status Code

You can easily check if the response is ok by using the ok method:

tests/test_unit.py
def test_view_is_ok(self):
    assert self.get('/testing').ok()

CSRF Protection

By default, all calls to your routes with the above methods will be without CSRF protection. The testing code will allow you to bypass that protection.

This is very useful since you don't need to worry about setting CSRF tokens on every request but you may want to enable this protection. You can do so by calling the withCsrf() method on your test.

def test_csrf(self):
    self.withCsrf()

    self.post('/unit/test/json', {'test': 'testing'})

This will enable it on a specific test but you may want to enable it on all your tests. You can do this by adding the method to your setUp() method:

def setUp(self):
    super().setUp()
    self.withCsrf()

def test_csrf(self):
    self.post('/unit/test/json', {'test': 'testing'})

Exception Handling

As you have noticed, Masonite has exception handling which it uses to display useful information during development.

This is an issue during testing because we wan't to be able to see more useful testing related issues. Because of this, testing will disable Masonite's default exception handling and you will see more useful exceptions during testing. If you want to use Masonite's built in exception handling then you can enable it by running:

def setUp(self):
    super().setUp()
    self.withExceptionHandling()

def test_csrf(self):
    self.post('/unit/test/json', {'test': 'testing'})

Getting Output

You can get the output by using the capture output easily by calling the captureOutput method on your unit test:

def test_get_output(self):
    with self.captureOutput() as o:
        print('hello world!')

    self.assertEqual(o.getvalue().rstrip(), 'hello world!')

Testing JSON

A lot of times you will want to build tests around your API's. There are quite a few methods for testing your endpoints

Testing the count

You can test to make sure your endpoint returns a specific amount of something. Like returning 5 articles:

def test_has_articles(self):
    self.assertTrue(
        self.json('GET', '/api/articles').count(5)
    )

You can also use assertCount(5):

def test_has_articles(self):
    self.json('GET', '/api/articles').assertCount(5)

You can also use amount which is just an alias for count:

def test_has_articles(self):
    self.assertTrue(
        self.json('GET', '/api/articles').amount(5)
    )

    self.json('GET', '/api/articles').assertHasAmount(5)
    self.json('GET', '/api/articles').assertNotHasAmount(10)

Checking Amount of Specific Key

You can also check if a specific JSON key has a specific amount. For example:

"""
{
    "name": "Joe",
    "tags": ['python', 'framework'],
    "age": 25
}
"""

def test_has_several_tags(self):
    self.assertTrue(
        self.json('GET', '/api/user').hasAmount('tags', 2)
    )

Testing Specific Responses

Sometimes you will want to check if your endpoint returns some specific JSON values:

"""
{
    "name": "Joe",
    "title": "creator",
    "age": 25
}
"""

def test_has_age(self):
    self.assertTrue(
        self.json('GET', '/api/user').hasJson('age', 25)
    )

You can also specify a dictionary of values as well and will check if each value inside the response:

"""
{
    "name": "Joe",
    "title": "creator",
    "age": 25
}
"""

def test_has_age(self):
    self.assertTrue(
        self.json('GET', '/api/user').hasJson({
            "name": "Joe",
            "age": 25
        })
    )

You do not have to specify all of the elements. Just the ones you want to check for.

You can alo use:

self.assertJsonHas('key', 'value')

Asserting values inside a collection

You can also assert values inside a list of responses:

"""
[
    {
    "name": "Joe",
    "title": "creator",
    "age": 25
    },
    {
    "name": "Bob",
    "title": "Co-Founder",
    "age": 26
    }
]
"""

def test_bob_in_result(self):
    self.json('GET', '/api/user').assertJsonContains('name', 'Bob')

Dot Notation

You can also use dot notation for multi dimensional endpoints:

"""
{
    "profile": {
        "name": "Joe",
        "title": "creator",
        "age": 25
    }
}
"""

def test_has_name(self):
    self.assertTrue(
        self.json('GET', '/api/user').hasJson('profile.name', 'Joe')
    )

Converting to a Dictionary

Sometimes you don't want to use dot notation and may choose to convert directly to a dictionary and assert values on that. This is also good for debugging so you can print the dictionary to these terminal. You can do this easily:

"""
{
    "profile": {
        "name": "Joe",
        "title": "creator",
        "age": 25
    }
}
"""

def test_has_name(self):
    dictionary = self.json('GET', '/api/user').asDictonary()
    self.assertEqual(dictionary['profile']['name'], 'Joe')

Testing Parameters

You can test if a specific parameter contains a specific value. For example if you want to see if the parameter id is equal to 5:

def test_has_name(self):
    # Route is: /dashboard/user/@id
    self.assertTrue(
        self.get('GET', '/dashboard/user/5').parameterIs('id', '5')
    )

    self.get('GET', '/dashboard/user/5').assertParameterIs('id', '5')

Testing Headers

You can test if a specific header contains a specific value. For example if you want to see if the header Content-Type is equal to text/html:

def test_has_name(self):
    # Route is: /dashboard/user/@id
    self.assertTrue(
        self.get('GET', '/dashboard/user/5').headerIs('Content-Type', 'text/html')
    )

    self.get('GET', '/dashboard/user/5').assertHeaderIs('Content-Type', 'text/html')

Testing Status

You can use the isStatus and assertIsStatus methods to assert status checks:

self.assertTrue(
        self.get('GET', '/dashboard/user/5').isStatus(200)
)

self.get('GET', '/dashboard/user/5').assertIsStatus(200)

You can also easily assert 404 methods:

self.get('GET', '/dashboard/not/exists').assertNotFound()

This is the same as asserting a 404 status code.

Subdomains

By default, Masonite turns off subdomains since this can cause issues when deploying to a PaaS that deploys to a subdomain like sunny-land-176892.herokuapp.com for example.

To activate subdomains in your tests you will have to use the withSubdomains() method. You can then set the host in the wsgi attribute.

def test_subdomains(self):
    self.withSubdomains().get('/view', wsgi={
            'HTTP_HOST': 'subb.domain.com'
        }).assertIsStatus(404)

Testing the Database

Databases

By default, to prevent messing with running databases, database test cases are set to only run on the sqlite database. You can disable this by setting the sqlite attribute to False.

from masonite.testing import TestCase

class TestUser(TestCase):

    """Start and rollback transactions for this test
    """
    transactions = True
    sqlite = False

    def setUp(self):
        super().setUp()

This will allow you to use whatever database driver you need.

Transactions and Refreshing

By default, all your tests will run inside a transaction so any data you create will only exist within the lifecycle of the test. Once the test completes, your database is rolled back to its previous state. This is a perfect way to prevent test data from clogging up your database.

Although this is good for most use cases, you may want to actually migrate and refresh the entire migration stack. In this case you can set the refreshes_database attribute to True.

from masonite.testing import TestCase

class TestUser(TestCase):

    """Start and rollback transactions for this test
    """
    transactions = False
    refreshes_database = True

    def setUp(self):
        super().setUp()

Now this will migrate and refresh the database.

Beware that this will destroy any database information you have.

Factories

Factories are simply ways to seed some dummy data into your database. You can create a factory by making a method that accepts a faker argument and using that to seed data.

Masonite has a convenient method you can use that will run once the test first boots up called setUpFactories. This will run once and only once and not between every test.

Let's create the factory as well as use the setUpFactories method to run them now.

Below is how to create 100 users:

from masonite.testing import TestCase
from app.User import User

class TestUser(TestCase):

    """Start and rollback transactions for this test
    """
    transactions = True

    def setUp(self):
        super().setUp()

    def setUpFactories(self):
        self.make(User, self.user_factory, 100)

    def user_factory(self, faker):
        return {
            'name': faker.name(),
            'email': faker.email(),
            'password': '$2b$12$WMgb5Re1NqUr.uSRfQmPQeeGWudk/8/aNbVMpD1dR.Et83vfL8WAu',  
            # == 'secret'
        }

    def test_creates_users(self):
        pass

You don't need to build factories though. This can be used to simply create new records:

from masonite.testing import TestCase
from app.User import User

class TestUser(TestCase):

    """Start and rollback transactions for this test
    """
    transactions = True

    def setUp(self):
        super().setUp()

    def setUpFactories(self):
        User.create({
            'name': 'Joe',
            'email': 'user@example.com',
            'password': '$2b$12$WMgb5Re1NqUr.uSRfQmPQeeGWudk/8/aNbVMpD1dR.Et83vfL8WAu',  # == 'secret'
        })

    def test_creates_users(self):
        pass

Users

We can load users into the route and check if they can view the route. This is good to see if your middleware is acting good against various users. This can be done with the acting_as() method.

tests/test_unit.py
from app.User import User
...

    def setUpFactories(self):
        User.create({
            'name': 'Joe',
            'email': 'user@example.com',
            'password': '$2b$12$WMgb5Re1NqUr.uSRfQmPQeeGWudk/8/aNbVMpD1dR.Et83vfL8WAu',  # == 'secret'
        })

    def test_user_can_see_dashboard(self):
        self.assertTrue(
            self.actingAs(User.find(1)).get('/dashboard').ok()
        )

Passing in Data

Maybe you need to check a post request and pass in some input data like submitting a form. You can do this by passing in a dictionary as the second value to either the get or post method:

def test_user_can_see_dashboard(self):
    self.assertTrue(
        self.actingAs(User.find(1)).post('/dashboard', {
            'name': 'Joe',
            'active': 1
        })
    )

The same can be applied to the get method except it will be in the form of query parameters.

Asserting Values

Instead of doing model calls to check if values exist in the database, you can use a simple assertDatabaseHas assertion:

def test_create_article(self):
    self.assertTrue(
        self.post('/articles', {
            'name': 'Masonite is great',
            'author_id': 1
        }).assertDatabaseHas('articles.name', 'Masonite is great')
    )

You can also do the same thing with the opposite assertDatabaseNotHas assertion:

def test_create_article(self):
    self.assertTrue(
        self.post('/articles', {
            'name': 'Masonite is great',
            'author_id': 1
        }).assertDatabaseNotHas('articles.name', 'Masonite is bad')
    )

Test Example

To complete our test, let's check if the user is actually created:

from masonite.testing import TestCase
from app.User import User

class TestUser(TestCase):

    """Start and rollback transactions for this test
    """
    transactions = True

    def setUp(self):
        super().setUp()

    def setUpFactories(self):
        User.create({
            'name': 'Joe',
            'email': 'user@example.com',
            # == 'secret'
            'password': '$2b$12$WMgb5Re1NqUr.uSRfQmPQeeGWudk/8/aNbVMpD1dR.Et83vfL8WAu',  
        })

    def test_creates_users(self):
        self.assertTrue(User.find(1))

Thats it! This test will now check that the user is created properly

Running Tests

You can run tests by running:

$ python -m pytest

CSRF Protection

Introduction

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.

Getting Started

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 }} tag to our form like so:

<form action="/dashboard" method="POST">
    {{ csrf_field }}

    <input type="text" name="first_name">
</form>

This will add a hidden field that looks like:

<input type="hidden" name="__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 }} then you will receive a InvalidCsrfException 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.

You can get also get the token that is generated. This is useful for JS frontends where you need to pass a CSRF token to the backend for an AJAX call

<p> Token: {{ csrf_token }} </p>

AJAX / Vue / Axios

For ajax calls, the best way to pass CSRF tokens is by setting the token inside a parent template inside a meta tag like this:

<meta name="csrf-token" content="{{ csrf_token }}">

And then you can fetch the token and put it wherever you need:

token = document.head.querySelector('meta[name="csrf-token"]')

You can then pass the token via the X-CSRF-TOKEN header instead of the __token input for ease of use.

Exempting Routes

Not all routes may require CSRF protection such as OAuth authentication or various webhooks. 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 and no checks will be made against this route. Use this sparingly as CSRF protection is crucial to application security but you may find that not all routes need it.

Exempting Multiple Routes

You can also use * wildcards for exempting several routes under the same prefix. For example you may find yourself needing to do this:

class CsrfMiddleware:
    """Verify CSRF Token Middleware
    """

    exempt = [
        '/api/document/reject-reason',
        '/api/document/@id/reject',
        '/api/document/@id/approve',
        '/api/document/@id/process/@user',
    ]

    ...

This can get a bit repetitive so you may specify a wildcard instead:

class CsrfMiddleware:
    """Verify CSRF Token Middleware
    """

    exempt = [
        '/api/document/*',
    ]

    ...

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.

Class Based Drivers

You can also explicitly declare the driver as a class:

from masonite.drivers import UploadS3Driver

DRIVER = UploadS3Driver

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:

from masonite import Upload

def upload(self, upload: 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 name. We could use that to input into our database if we want. All file uploads will convert the filename into a random 25 character string.

upload.driver('disk').store(request.input('file_upload'))
#== '838nd92920sjsn928snaj92gj.png'

Lastly, we may can specify a filename directly using the filename keyword argument:

upload.driver('disk').store(request.input('file_upload'), filename="username.profile")
#== username.profile.png

Accepting Specific Files

By default, Masonite only allows uploads to accept images for security reasons but you can specify any file type you want to accept by specifying the filetype in the accept method before calling the store method.

upload.accept('yml', 'zip').store('some.yml')

You can also just accept all file types as well:

upload.accept('*').store('some.yml')

Uploading Files

You can upload files directly by passing in a open() file:

from masonite import Upload

def upload(self, upload: Upload):
    upload.driver('disk').store(open('some/file.txt'))

This will upload a file directly from the file system to wherever it needs to upload to.

Locations

You can also specify the location you want to upload to. This will default to location specified in the config file but we can change it on the fly:

upload.driver('disk').store(request.input('file_upload'), location='storage/profiles')

Dot Notation

You can use dot notation to search your driver locations. Take this configuration for example:

DRIVERS = {
    'disk': {
        'location': {
            'uploads': 'storage/uploads',
            'profiles': 'storage/users/profiles',
    }
}

and you can use dot notation:

upload.driver('disk').store(request.input('file_upload'), location='disk.profiles')

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:

from masonite import Upload

def upload(self, upload: Upload):
    upload.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.

Changing Drivers

You can also swap drivers on the fly:

from masonite import Upload

def upload(self, upload: Upload):
    upload.driver('s3').store(request.input('file_upload'))

or you can explicitly specify the class:

from masonite.drivers import UploadS3Driver
from masonite import Upload

def upload(self, upload: Upload):
    upload.driver(UploadS3Driver).store(request.input('file_upload'))

Authentication

Authentication

Introduction

Masonite comes with some authentication out of the box. Nothing is too opinionated to be restrictive so Masonite leaves it really customizable but gives you a great starting point.

The concept of authentication is a "Guard" concept. Guards are simply authentication logic for different scenarios. For example if you are building an API you might have an api guard but if you are building for a normal web app you might use a web guard. You can put different routes behind different guards to give you an ultimate level of control.

Configuration

There is a single config/auth.py configuration file which you can use to set the authentication behavior of your Masonite project. The default configuration looks something like:

AUTH = {
    'defaults': {
        'guard': env('AUTH_GUARD', 'web')
    },
    'guards': {
        'web': {
            'driver': 'cookie',
            'model': User,
            'drivers': { # 'cookie', 'jwt'
                'jwt': {
                    'reauthentication': True,
                    'lifetime': '5 minutes'
                }
            }
        },
    }
}

You can set the default guard to use in the defaults key. From there you can set various different settings based on which guard you need inside the guards key. Each guard takes a few different settings like the driver the guard will use, the model, and then driver settings here.

If you wish to change the authentication model, to a Company model for example, feel free to do in this configuration file.

This would look something like:

from app.Company import Company

AUTH = {
    'defaults': {
        'guard': env('AUTH_GUARD', 'web')
    },
    'guards': {
        'web': {
            'driver': 'cookie',
            'model': Company,
            'drivers': { # 'cookie', 'jwt'
                'jwt': {
                    'reauthentication': True,
                    'lifetime': '5 minutes'
                }
            }
        },
    }
}

Cookie Driver

The cookie driver will set a token as a cookie and then fetch the user from the database on every request. For most applications this is fine although you are making an additional query per request just to fetch the user.

This is the most basic authentication driver.

JWT Driver

The JWT driver will store an encrypted JWT token inside a cookie with all the authenticated user information. Then when the authenticated user goes to the page, the JWT token is decrypted and fills in the data on the user model without having to recall the database. Use this option for high traffic sites that you don't want making that extra database call on every request.

There are also 2 options you can set as well. The first option is how long until the jwt token expires. By default this is 5 minutes but you can extend it out longer:

    'jwt': {
        'reauthentication': True,
        'lifetime': '5 minutes'
    }

The second option is whether or not the user should reauthenticate with the database after their token has expired. If set to False, the token will simply continue to refill the user model and set a new token all without touching the database. If set to True it will cancel out the token and reauthenticate with the database to create a new token.

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 when a user logs in.

Authentication Column

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:

class User(Model):

    __fillable__ = ['name', 'email', 'password']

    __auth__ = 'name'

Multiple Authentication Columns

Sometimes your application will be able to either login by email OR by username. You can do this by specifying the __auth__ attribute as a list of columns:

class User(Model):

    __fillable__ = ['name', 'email', 'password']

    __auth__ = ['name', 'email']

Now you can do something like "Please enter Username or Email".

Password Column

By default, Masonite will use the password column to authenticate as the password. Some applications may have this changed. Your specific application may be authenticating with a token column for example.

You can change the password column by speciyfing the __password__ attribute to the column name:

class User(Model):

    __fillable__ = ['name', 'email', 'password']

    __auth__ = ['name']
    __password__ = 'token'

Authenticating a Model

If you want to authenticate a model, you can use the Auth class 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:

from masonite.auth import Auth

def show(self, request: Request, auth: Auth):
    auth.login('user@email.com', 'password')

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 Column

You may change the column to be authenticated by simply changing the column value of the __auth__ class attribute. This will look something like:

class User(Model):

    __fillable__ = ['name', 'email', 'password']

    __auth__ = 'email'

This will look inside the email column now and check that column and password. The authentication column is email by default.

Changing The Guard

The above examples will use the default guard. You can change the guard on the fly by doing:

from masonite.auth import Auth

def show(self, request: Request, auth: Auth):
    auth.guard('api').login('user@email.com', 'password')

This will now use the api guard (if one existed).

Changing the Guard in Routes

Masonite ships with a guard middleware which allows you to switch the guard based on the route. For example if you had an API route you might want to use an api guard you have created:

Get('/api/users').middleware('guard:api')

Or you may want to use route groups:

RouteGroup([
    Get('/api/users'),
    Get('/api/settings'),
    Get('/api/posts'),
], middleware=('guard:api',))

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:

$ craft auth

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?

Retrieving the Authenticated User

Masonite ships with a LoadUserMiddleware 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 LoadUserMiddleware middleware you can retrieve the current user using:

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

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

from masonite.auth import Auth

def show(self, request: Request, auth: Auth):
    auth.user()

Checking if the User is Authenticated

If you would like to simply check if the user is authenticated, request.user() or auth.user() will return False if the user is not authenticated. This will look like:

def show(self, request: Request):
    if request.user():
        user_email = request.user().email

Logging In

You can easily log users into your application using the Auth class:

from masonite.auth import Auth

def show(self, request: Request, auth: Auth):
    auth.login(
        request.input('username'),
        request.input('password')
    )

Note that the username you supply needs to be in whatever format the __auth__ attribute is on your model. If the email address is the "username", then the user will need to supply their email address.

Login By ID

If you need more direct control internally, you can login by the models ID:

from masonite.auth import Auth

def show(self):
    auth.login_by_id(1)

You are now logged in as the user with the ID of 1.

Login Once

If you only want to login "once", maybe for just authenticating an action or verifying the user can supply the correct credentials, you can login without saving any cookies to the browser:

from masonite.auth import Auth

def show(self):
    auth.once().login_by_id(1)

You can do the same for the normal login method as well:

from masonite.auth import Auth

def show(self, request: Request, auth: Auth):
    auth.once().login(
        request.input('username'),
        request.input('password')
    )

Registering a User

You can also easily register a user using the register() method on the Auth class:

from masonite.auth import Auth

def show(self, auth: Auth):
    auth.register({
        'name': 'Joe',
        'email': 'joe@email.com',
        'password': 'secret'
    })

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:

Get().route('/dashboard', 'DashboardController@show').middleware('auth')

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

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:

auth.logout()

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:

def logout(self, request: Request, auth: Auth):
    auth.logout()
    return request.redirect('/login')

Verifying A User's Email

If you wish to require a user to verify their email address and automatically send them an email, you can extend the User model.

from masonite.auth import MustVerifyEmail

class User(Model, MustVerifyEmail):

    __fillable__ = ['name', 'email', 'password']

    __auth__ = 'name'

When a user registers this will automatically send them an email asking them to confirm their email address.

Redirecting Unverified User's

You can use the VerifyEmailMiddleware class to redirect an unverified user.

You can use this middleware in your routes file like so:

Get().route('/dashboard', 'DashboardController@show').middleware('verified')

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.

Service Providers
Orator documentation
Service Container
Service Container
Service Provider
Service Container
Masonite Starter Package Repo
Creating Commands
Creating Commands
Service Container
Resolve
Request Redirection

Compiling Assets

Compiling Assets

Introduction

Previously Masonite used to use the libsass package for handling SASS and LESS but Masonite has now moved over to using NPM for provide better support out of the box. More specifically, Masonite uses Laravel Mix which provides a really simple way to handle asset compiling even greater than simple SASS and LESS. You don't need to be an expert in either Laravel Mix or NPM to compile assets, though.

Getting Started

To get started we can simply run NPM install:

$ npm install

This will install everything you need to start compiling assets.

Configuration

The configuration settings will be made inside your webpack.mix.js file located in the root of your project.

You can see there is already an example config setup for you that looks like this:

mix.js('storage/static/js/app.js', 'storage/compiled/js')
    .sass('storage/static/sass/style.scss', 'storage/compiled/css');

This will move these 2 files, storage/static/js/app.js and storage/statis/sass/style.scss and compile them both into the storage/compiled directory.

Compiling

Now that we have our compiled assets configured we can now actually compile them.

You can do so by running:

$ npm run dev

This will compile the assets and put them in the directories you put in the configuration file.

You can also have NPM wait for changes and recompile when changes are detected. This is similiar to an auto reloading server. To do this just run:

$ npm run watch

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:

DRIVERS = {
    'disk': {
        'location': 'storage/cache/templates'
    }
}

Redis

Masonite also supports a Redis driver. Make sure the Redis server is running and:

$ pip install redis
DRIVER = 'redis'

DRIVERS = {
    'disk': {
        'location': 'storage/cache/templates'
    },
    'redis': {
        'host': os.getenv('REDIS_HOST', 'localhost'),
        'port': os.getenv('REDIS_PORT', '6379'),
        'password': os.getenv('REDIS_PASSWORD', '')
    }
}

Add the required environment keys to your .env file and you are good to go!

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:

from masonite import Cache

def show(self, cache: Cache):
    cache # returns the cache class

Storing

We can easily store items into the cache by doing:

from masonite import Cache

def show(self, cache: Cache):
    cache.store('key', 'value')

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:

from masonite import Cache

def show(self, cache: Cache):
    cache.store_for('key', 'value', 5, 'seconds')

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.

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:

from masonite import Cache

def show(self, cache: Cache):
    cache.get('key')

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:

from masonite import Cache

def show(self, cache: Cache):
    cache.is_valid('key')

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:

from masonite import Cache

def show(self, cache: Cache):
    cache.exists('key')

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.

from masonite import Cache

def show(self, cache: Cache):
    cache.update('key', 'value')

Deleting

You can delete a cache by key using:

from masonite import Cache

def show(self, cache: Cache):
    cache.delete('key')

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.

.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

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

SSL (optional)

You may need to use an ssl version of SMTP depending on the service you are using. You can specify to use SSL by setting that option in your smtp driver configuration in config/mail.py:

DRIVERS = {
    'smtp': {
        'host': env('MAIL_HOST', 'smtp.mailtrap.io'),
        'port': env('MAIL_PORT', '465'),
        'username': env('MAIL_USERNAME', 'username'),
        'password': env('MAIL_PASSWORD', 'password'),
        'ssl': True
    },

TLS (optional)

The SMTP driver supports a TLS option as well if your mail server requires TLS:

DRIVERS = {
    'smtp': {
        'host': env('MAIL_HOST', 'smtp.mailtrap.io'),
        'port': env('MAIL_PORT', '465'),
        'username': env('MAIL_USERNAME', 'username'),
        'password': env('MAIL_PASSWORD', 'password'),
        'tls': True
    },

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

Remember that it is safe 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:

.env
MAILGUN_SECRET=key-xx
MAILGUN_DOMAIN=sandboxXX.mailgun.org

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

config/mail.py
DRIVER = env('MAIL_DRIVER', 'smtp')

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

.env
MAIL_DRIVER=mailgun

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

config/mail.py
DRIVER = 'mailgun'

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.

config/mail.py
DRIVERS = {
    ...
    'mailgun': {
        'secret': env('MAILGUN_SECRET', 'key-XX'),
        'domain': env('MAILGUN_DOMAIN', 'sandboxXX.mailgun.org')
    }
}

Terminal Driver

The Terminal driver simply prints out your email message in the terminal. Makes testing and development super easy. To use the terminal driver you'll need to enter a few configuration settings.

.env
MAIL_DRIVER=terminal
MAIL_FROM_ADDRESS=admin@email.com
MAIL_FROM_NAME=Masonite
MAIL_HOST=
MAIL_PORT=
MAIL_USERNAME=
MAIL_PASSWORD=

Log Driver

The Log driver simply prints out your email message into a log file. To use the log driver you'll need to enter a few configuration settings.

.env
MAIL_DRIVER=log
MAIL_FROM_ADDRESS=admin@email.com
MAIL_FROM_NAME=Masonite
MAIL_HOST=
MAIL_PORT=
MAIL_USERNAME=
MAIL_PASSWORD=

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

config/mail.py
DRIVERS = {
    ...
    'log': {
        'file': env('LOG_FILE', 'mail.log'),
        'location': 'bootstrap/logs'
    }
}

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:

from masonite import Mail

def show(self, mail: Mail):
    print(mail) # returns the default mail driver

We can send an email like so:

from masonite import Mail

def show(self, mail: Mail):
    mail.to('hello@email.com').send('Welcome!')

You can also obviously specify a specific user:

from app.User import User
from masonite import Mail
...
def show(self, mail: Mail):
    mail.to(User.find(1).email).send('Welcome!')

Masonite also supports the following .to() formats:

from masonite import Mail

def show(self, mail: Mail):
    # Single email address
    mail.to('user1@email.com').send('Welcome!')

    # Email with name
    mail.to('Joe Mancuso <user1@email.com>').send('Welcome!')

    # List of emails
    mail.to(['user1@email.com', 'user2.email.com']).send('Welcome!')

    # List of emails with name
    mail.to([
        'Joe Mancuso <user1@email.com>', 
        'John Mancuso <user2@email.com>'
    ]).send('Welcome!')

Queuing Emails

you can easily queue the sending of emails by using the queue method before the send method like so:

from app.User import User
from masonite import Mail
...
def show(self, mail: Mail):
    mail.to(User.find(1).email).queue().send('Welcome!')

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:

from masonite.manager import MailManager

def show(self, manager: MailManager):
    manager.driver('mailgun') # now uses the Mailgun 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:

from app.jobs.SendWelcomeEmail import SendWelcomeEmail
from masonite import Queue

def show(self, queue: Queue):
    queue.push(SendWelcomeEmail)

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.

Changing the subject

We can also specify the subject:

mail.subject('Welcome!').to('hello@email.com').send('Welcome!')

Changing the Send From

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

mail.send_from('Admin@email.com').to('hello@email.com').send('Welcome!')

Changing the Mime Type

By default, Masonite will send HTML emails but you can specify either HTML or plain text emails simply:

mail.html('<h1>Hello</h1>').send()

or plain text:

mail.text('world').send()

You can also send both:

mail.html('<h1>Hello</h1>').text('world').send()

Templates

The most common place to put your email templates is inside resources/templates/mail.

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

mail.to('idmann509@gmail.com').template('mail/welcome').send()

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

Passing Data to Templates

You are also able to pass data into our mail templates. This data is passed in as a dictionary that contains a key which is the variable with the corresponding value. We can pass data to the function like so:

mail.to('idmann509@gmail.com').template('mail/welcome', {'name': 'Masonite User'}).send()

Mailable Classes

Mailable classes are really helpful classes you can use to abstract some of the logic of sending emails out.

You can make a mailable class by creating a class in your app/mailables directory. You can do so by running a craft command:

$ craft mailable WelcomeEmail

Now you can build a mailable class which you can use to later send. Let's build a welcome email:

from masonite.drivers import Mailable


class WelcomeEmail(Mailable):

    def __init__(self, to):
        self.to = to

    def build(self):
        return (
            self.subject('Welcome To My Application')
            .reply_to('service@example.com')
            .send_from('admin@example.com')
            .view('emails/welcome')
            .to(self.to)
        )

Once built you can then use it in anyway you need to:

from masonite import Mail
from app.mailables import WelcomeEmail

def show(self, mail: Mail):
    mail.mailable(WelcomeEmail('user@example.com')).send()

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 SentryServiceProvider

This will create a new Service Provider inside app/providers/SentryServiceProvider.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/providers.py file:

from app.providers.SentryServiceProvider import SentryServiceProvider
...
PROVIDERS = [
    # Framework Providers
    AppProvider,
...
    ViewProvider,

    # Optional Framework Providers
    SassProvider,
    MailProvider,
...
    # Application Providers
    SentryServiceProvider
...
]
...

Exception Handlers

You can build handlers to handle specific exceptions that are thrown by your application. For example if a TemplateNotFound exception is thrown then you can build a special exception handler to catch that and return a special view or a special debug screen.

Building an Exception Handler

Exception handlers are simple classes that have a handle method which accepts the exception thrown:

from masonite.request import Request

class TemplateNotFoundHandler:

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

    def handle(self, exception):
        pass

The constructor of all exception handlers are resolved by the container so you can hint any dependencies you need. Once the constructor is resolved then the handle method will be called.

Once the handle method is called then Masonite will continue with the rest of the WSGI logic which is just setting the status code, headers and returning a response.

Registering the Exception Handler

Now that we have our exception handler we will need to register it in the container using a special naming convention. The naming convention is: ExceptionNameOfErrorHandler. The name of the exception that we want to be catching is called TemplateNotFound so we will need to bind this into the container like so:

from masonite.provider import ServiceProvider
from somewhere import TemplateNotFoundHandler

class UserModelProvider(ServiceProvider):

    def register(self): 
        self.app.bind('ExceptionTemplateNotFoundHandler', TemplateNotFoundHandler)

    ...

Exception Listeners

While an exception handler will actually handle the incoming exception, exception listeners are a little different.

Masonite can have several listeners registered with the framework that will listen to specific (or all) exceptions and have that exception passed into it if one is raised. It will then perform any logic it needs until an exception handler finally handles the exception.

Creating a Listener

A listener is a simple class that requires a list of exceptions to listen for. Here is a simple boilerplate of an exception listener:

from masonite.request import Request
from masonite.listeners import BaseExceptionListener

class ExceptionListener(BaseExceptionListener):

    listens = [
        ZeroDivisionError
    ]

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

    def handle(self, exception, file, line):
        # Perform an action

Note that the __init__ method of a listener is resolved by the container so feel free to type hint anything you need there.

Finally it requires a handle method which takes 3 arguments. the exception that was thrown, the file that the exception was thrown in and the line the exception was thrown on.

You can either listen to any number of exceptions or all exceptions by passing in a * to the listens attribute:

class ExceptionListener(BaseExceptionListener):

    listens = ['*']

    # ...

A good use case for this would be Masonite Logging package which uses this to log any exceptions.

Registering Exception Listeners

You can register an exception listener directly to the container with any service provider. For easy use, you can use a simple bind to the container which will bind the class to the container with the name of the class as the key:

from some.place import LoggerExceptionListener

class YourProvider:

    wsgi = False

    def register(self):
        self.app.simple(LoggerExceptionListener)

Your listener will now run whenever an exception occurs that your listener is listening to.

Queues and Jobs

Queues and Jobs

Introduction

Almost all applications can make use of queues. Queues are a great way to make time intensive tasks seem immediate by sending the task into the background or into a message queue. 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.

Masonite uses pickle to serialize and deserialize Python objects when appropriate. Ensure that the objects you are serializing is free of any end user supplied code that could potentially serialize into a Python object during the deserialization portion.

All configuration settings by default are in the config/queue.py file. Out of the box, Masonite supports 3 drivers:

  • async

  • amqp

  • database

The async driver simply sends jobs into the background using multithreading. The amqp driver is used for any AMQP compatible message queues like RabbitMQ. If you do create a driver, consider making it available on PyPi so others can also install it. The database driver has a few additional features that the other drivers do not have if you need more fine-grained control

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 import Queueable

class SendWelcomeEmail(Queueable):

    def __init__(self):
        pass

    def handle(self):
        pass

Adding Jobs to the queue

We can run jobs by using the Queue class. Let's run this job from a controller method:

from app.jobs.SendWelcomeEmail import SendWelcomeEmail
from masonite import Queue

def show(self, queue: Queue):
    queue.push(SendWelcomeEmail)

That's it. This job will now send to the queue and run the handle method.

Resolving

Notice in the show method above that we passed in just the class object. We did not instantiate the class. In this case, Masonite will resolve the job constructor. All job constructors are able to be resolved by the container so we can simply pass anything we need as normal:

from masonite.queues import Queueable
from masonite.request import Request
from masonite import Mail

class SendWelcomeEmail(Queueable):

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

    def handle(self):
        pass

Instantiating

We can also instantiate the job as well if we need to pass in data from a controller method. This will not resolve the job's constructor at all:

from app.jobs.SendWelcomeEmail import SendWelcomeEmail
from masonite import Queue

def show(self, queue: Queue):
    var1 = 'value1'
    var2 = 'value2'

    queue.push(SendWelcomeEmail(var1, var2))

The constructor of our job class now will look like:

class SendWelcomeEmail(Queueable):

    def __init__(self, var1, var2):
        self.var1 = var1
        self.var2 = var2

Executing Jobs

Whenever jobs are executed, it simply executes the handle method. Because of this we can send our welcome email:

from masonite.queues import Queueable
from masonite.request import Request
from masonite import Mail

class SendWelcomeEmail(Queueable):

    def __init__(self, request: Request, mail: 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! 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
from masonite import Queue

def show(self, queue: Queue):
    queue.push(SendWelcomeEmail, TutorialEmail('val1', 'val2'))

Passing Variables Into Jobs

Most of the time you will want to resolve the constructor but pass in variables into the handle() method. This can be done by passing in an iterator into the args= keyword argument:

from masonite import Queue

def show(self, queue: Queue):
    queue.push(SendWelcomeEmail, args=['user@email.com'])

This will pass to your handle method:

from masonite.request import Request
from masonite import Mail
class SendWelcomeEmail(Queueable):

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

    def handle(self, email):
        email # =='user@email.com'

Passing Functions or Methods

You can also call any arbitrary function or method using the queue driver. All you need to do is pass the reference for it in the push method and pass any arguments you need in the args parameter like so:

def run_async(obj1, obj2):
    pass

def show(self, queue: Queue):
    obj1 = SomeObject()
    obj2 = AnotherObject()
    queue.push(run_async, args=(obj1, obj2))

This will then queue this function to be called later.

Note that you will not be able to get a response value back. Once it gets sent to the queue it will run at an arbitrary time later.

Async Driver

The async queue driver will allow you to send jobs into the background to run asynchronously. This does not need any third party services like the amqp driver below.

Change Modes

The async driver has 2 different modes: threading and multiprocess. The differences between the two is that threading uses several threads and multiprocess uses several processes. Which mode you should use depends on the type of jobs you are processing. You should research what is best depending on your use cases.

You can change the mode inside the config/queue.py file:

DRIVERS = {
    'async': {
        'mode': 'threading' # or 'multiprocess'
    },
}

Blocking

During development it may be hard to debug asyncronous tasks. If an exception is thrown it will be hard to catch that. It may appear that a job is never ran.

In order to combat this you can set the blocking setting in your config/queue.py file:

DRIVERS = {
    'async': {
        'mode': 'threading' # or 'multiprocess',
        'blocking': True
    },
}

Blocking bascially makes asyncronous tasks run syncronously. This will enable some reporting inside your terminal that looks something like:

GET Route: /categories
 Job Ran: <Future at 0x1032cef60 state=finished returned str> 
 Job Ran: <Future at 0x1032f1a90 state=finished returned str> 
 ...

This will also run tasks syncronously so you can find exceptions and issues in your jobs during development.

For production this should be set to False.

It may be good to set this setting equal to whatever your APP_DEBUG environment variable is:

from masonite import env

DRIVERS = {
    'async': {
        'mode': 'threading' # or 'multiprocess',
        'blocking': env('APP_DEBUG')
    },
}

This way it will always be blocking during development and automatically switch to unblocking during production.

AMQP Driver

The amqp driver can be used to communicate with RabbitMQ services.

Installing

In order to get started with this driver you will need to install RabbitMQ on your development machine (or production machine depending on where you are running Masonite).

Before you continue, make sure to install pika which is a Python RabbitMQ/AMQP client library.

pip install pika

Running RabbitMQ

Once you have RabbitMQ installed you can go ahead and run it. This looking something like this in the terminal if ran successfully:

$ rabbitmq-server

  ##  ##
  ##  ##      RabbitMQ 3.7.8. Copyright (C) 2007-2018 Pivotal Software, Inc.
  ##########  Licensed under the MPL.  See http://www.rabbitmq.com/
  ######  ##
  ##########  Logs: /usr/local/var/log/rabbitmq/rabbit@localhost.log
                    /usr/local/var/log/rabbitmq/rabbit@localhost_upgrade.log

              Starting broker...
 completed with 6 plugins.

Great! Now that RabbitMQ is up and running we can look at the Masonite part.

Now we will need to make sure our driver and driver configurations are specified correctly. Below are the default values which should connect to your current RabbitMQ configuration. This will be in your config/queue.py file

DRIVER = 'amqp'
...
DRIVERS = {
    'amqp': {
        'username': 'guest',
        'password': 'guest',
        'host': 'localhost',
        'port': '5672',
        'channel': 'default',
    }
}

If your rabbit MQ instance requires a vhost but doesn't have a port, we can add a vhost and set the port to none. vhost and port both have the option of being None. If you are developing locally then vhost should likely be left out all together. The setting below will most likely be used for your production settings:

DRIVER = 'amqp'
...
DRIVERS = {
    'amqp': {
        'username': 'guest',
        'vhost': '/',
        'password': 'guest',
        'host': 'localhost',
        'port': None,
        'channel': 'default',
    }
}

Database Driver

The database driver will store all jobs in a database table called queue_jobs and on fail, will store all failed jobs in a failed_jobs table if one exists. If the failed_jobs table does not exist then it will not store any failed jobs and any jobs that fail will be lost.

Migrations

In order to get these two queue table you can run the queue:table command with the flag on which table you would like:

This command will create the queue_jobs migration where you can store your jobs:

$ craft queue:table --jobs

This command will create the failed_jobs migration where you can store your failed jobs:

$ craft queue:table --failed

Once these migrations are created you can run the migrate command:

$ craft migrate

Delaying Jobs

Jobs can be easily delayed using the database driver. Other drivers currently do not have this ability. In order to delay a job you can use a string time using the wait keyword.

def show(self, queue: Queue):
    queue.push(SendWelcomeEmail, wait="10 minutes")

Starting The Worker

We can now start the worker using the queue:work command. It might be a good idea to run this command in a new terminal window since it will stay running until we close it.

$ craft queue:work

This will startup the worker and start listening for jobs to come in via your Masonite project.

You can also specify the driver you want to create the worker for by using the -d or --driver option

$ craft queue:work --driver amqp

You may also specify the channel as well. channel may mean different things to different drivers. For the amqp driver, the channel is which queue to listen to. For the database driver, the channel is the connection to find the queue_jobs and queue_failed tables.

$ craft queue:work --driver database --channel sqlite

Sending Jobs

That's it! send jobs like you normally would and it will process via RabbitMQ:

from app.jobs import SomeJob, AnotherJob
from masonite import Queue
...
def show(self, queue: Queue):
    # do your normal logic
    queue.push(SomeJob, AnotherJob(1,2))

you can also specify the channel to push to by running:

queue.push(SomeJob, AnotherJob(1,2), channel="high")

Failed Jobs

Sometimes your jobs will fail. This could be for many reasons such as an exception but Masonite will try to run the job 3 times in a row, waiting 1 second between jobs before finally calling the job failed.

If the object being passed into the queue is not a job (or a class that implements Queueable) then the job will not requeue. It will only ever attempt to run once.

Handling Failed Jobs

Each job can have a failed method which will be called when the job fails. You can do things like fix a parameter and requeue something, call other queues, send an email to your development team etc.

This will look something like:

from masonite.queues import Queueable
from masonite.request import Request
from masonite import Mail

class SendWelcomeEmail(Queueable):

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

    def failed(self, payload, error):
        self.mail.to('developer@company.com').send('The welcome email failed')

It's important to note that only classes that extend from the Queueable class will handle being failed. All other queued objects will simply die with no failed callback.

Notice that the failed method MUST take 2 parameters.

The first parameter is the payload which tried running which is a dictionary of information that looks like this:

payload == {
    'obj': <class app.jobs.SomeJob>,
    'args': ('some_variables',), 
    'callback': 'handle', 
    'created': '2019-02-08T18:49:59.588474-05:00', 
    'ran': 3
}

and the error may be something like division by zero.

Storing Failed Jobs

By default, when a job is failed it disappears and cannot be ran again since Masonite does not store this information.

If you wish to store failed jobs in order to run them again at a later date then you will need to create a queue table. Masonite makes this very easy.

First you will need to run:

$ craft queue:table

Which will create a new migration inside databases/migrations. Then you can will migrate it:

$ craft migrate

Now whenever a failed job occurs it will store the information inside this new table.

Running Failed Jobs

You can run all the failed jobs by running

$ craft queue:work --failed

This will get all the jobs from the database and send them back into the queue. If they fail again then they will be added back into this database table.

Specifying Failed Jobs

You can modify the settings above by specifying it directly on the job. For example you may want to specify that the job reruns 5 times instead of 3 times when it fails or that it should not rerun at all.

Specifying this on a job may look something like:

from masonite.request import Request
from masonite import Mail

class SendWelcomeEmail(Queueable):

    run_again_on_fail = False

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

    def handle(self, email):
        ...

This will not try to rerun when the job fails.

You can specify how many times the job will rerun when it fails by specifying the run_times attribute:

from masonite.request import Request
from masonite import Mail

class SendWelcomeEmail(Queueable):

    run_times = 5

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

    def handle(self, email):
        ...

Task Scheduling

Introduction

Often your application will require some kind of recurring task that should happen at a specific time of the week, every minute, or every few months. These tasks can be things like:

  • Reading XML from a directory and importing it into a remote database

  • Checking if a customer is still a subscriber in Stripe and updating your local database

  • Cleaning your database of unneeded data every minute or so

  • Send an API call to a service in order to fire certain events

Or anything in between. There are lots of use cases for simple tasks to be ran during certain parts of the day or even "offline" hours when your employees are gone.

Getting Started

First we will need to install the scheduler feature. We can simply pip install it:

terminal
$ pip install masonite-scheduler
config/providers.py
...
from masonite.scheduler.providers import ScheduleProvider

PROVIDERS = [
    AppProvider,
    ...
    ...
    ScheduleProvider, # Here
]

This provider will add several new features to Masonite. The first is that it will add two new commands.

The first command that will be added is the craft schedule:run command which will run all the scheduled tasks that need to run (which we will create in a bit).

The second command is a craft task command which will create a new task under the app/tasks directory.

Creating a Task

Now that we added the Service Provider, we can start creating tasks. Let's create a super basic task that prints "Hi". First let's create the task itself:

$ craft task SayHi

This will create a file under app/tasks/SayHi.py

from scheduler.Task import Task

class SayHi(Task):

    def __init__(self):
        pass

    def handle(self):
        pass

This will be the simple boilerplate for our tasks.

Container Autoloading

In order for tasks to be discovered they need to be inside the container. Once inside the container, we collect them, see if they need to run and then decide to execute them or not.

...
AUTOLOAD = [
    'app',
    'app/tasks'
]
...

This will find all the tasks in the app/tasks directory and load them into the container for you with the key binding being the name of the class.

Loading Jobs Manually

You don't need to use the autoloader. You can also schedule jobs manually. To do this you'll need to use a Service Provider and bind.

If you are loading jobs manually it is useful to inherit the CanSchedule class. This simply gives you a self.call() method you can use to more easily schedule your jobs and commands.

You can do so in the register method of the Service Provider:

from masonite.scheduler import CanSchedule
from app.jobs.YourJob import YourJob

class YourServiceProvider(ServiceProvider, CanSchedule):

    def register(self):
       self.schedule(YourJob()).every('3 days')

You can use these methods to schedule: every('5 minutes'), every_minute(), every_15_minutes(), every_30_minutes(), every_45_minutes(), daily(), hourly(), weekly(), monthly().

Scheduling Commands

You can schedule commands in a similiar way.

If you are loading jobs manually it is useful to inherit the CanSchedule class. This simply gives you a self.call() method you can use to more easily schedule your jobs and commands.

from masonite.scheduler import CanSchedule
from app.jobs.YourJob import YourJob

class YourServiceProvider(Serv):

    def register(self):
        self.call('your:command --flag').daily().at('9:00')

Making The Task

Now that our task is able to be added to the container automatically, let's start building the class.

Constructors

Firstly, the constructor of all tasks are resolved by the container. You can fetch anything from the container that doesn't need the WSGI server to be running (which is pretty much everything). So we can fetch things like the Upload, Mail, Broadcast and Request objects. This will look something like:

from scheduler.Task import Task

from masonite.request import Request

class SayHi(Task):

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

    def handle(self):
        pass

Handle Method

The handle method is where the logic of the task should live. This is where you should put the logic of the task that should be running recurrently.

We can do something like fire an API call here:

from scheduler.Task import Task
import requests

class SayHi(Task):

    def __init__(self):
        pass

    def handle(self):
        requests.post('http://url.com/api/store')

When To Run

The awesomeness of recurring tasks is telling the task when it should run. There are a few options we can go over here:

A complete task could look something like:

from scheduler.Task import Task
import requests

class SayHi(Task):
    run_every = '3 days'
    run_at = '17:00'

    def __init__(self):
        pass

    def handle(self):
        requests.post('http://url.com/api/store')

This task will fire that API call every 3 days at 5pm.

All possible options are False by default. The options here are:

Attribute

Options

Example

run_every

Either a singular or plural version of the accepted time units: minutes, hours, days, months

run_every = '1 day'

run_at

The time in military time ("17:00" for 5pm)

run_at = '17:00'

run_every_hour

Boolean on whether to run every hour or not: True, False

run_every_hour = True

run_every_minute

Boolean on whether to run every minute or not: True, False

run_every_minute = True

twice_daily

A tuple on the hours of the day the task should run. Also in military time. (1, 13) will run at 1am and 1pm.

twice_daily = (1, 13)

If the time on the task is days or months then you can also specify a run_at attribute which will set the time of day it should should. By default, all tasks will run at midnight if days is set and midnight on the first of the month when months is set. We can specify which time of day using the run_at attribute along side the run_every attribute. This option will be ignored if run_every is minutes or hours.

Timezones

You can also set timezones on individual tasks by setting a timezone attribute on the task:

from scheduler.Task import Task
import requests

class SayHi(Task):
    run_every = '3 days'
    run_at = '17:00'
    timezone = 'America/New_York'

    def __init__(self):
        pass

    def handle(self):
        requests.post('http://url.com/api/store')

Caveats

This feature is designed to run without having to spin up a seperate server command and therefore has some caveats so be sure to read this section to get a full understanding on how this feature works.

When it Runs

Since the scheduler doesn't actually know when the server starts, it doesn't know from what day to start the count down. In order to get around this, Masonite calculates the current day using a modulus operator to see if the modulus of the tasks time and the current time are 0.

For example, if the task above is to be ran (every 3 days) in May then the task will be ran at midnight on May 3rd, May 6th, May 9th, May 12th etc etc. So it's important to note that if the task is created on May 11th and should be ran every 3 days, then it will run the next day on the 12th and then 3 days after that.

Running The Tasks

After we add the directory to the AUTOLOAD list, we can run the schedule:run command which will find the command and execute it.

$ craft schedule:run

Masonite will fetch all tasks from the container by finding all subclasses of scheduler.tasks.Task, check if they should run and then either execute it or not execute it.

Even though we ran the task, we should not see any output. Let's change the task a bit by printing "Hi" and setting it to run every minute:

from scheduler.Task import Task


class SayHi(Task):

    run_every = '1 minute'

    def __init__(self):
        pass

    def handle(self):
        print('Hi!')

Now let's run the command again:

$ craft schedule:run

We should now see "Hi!" output to the terminal window.

Running a Specific Task

You may also run a specific task by running the schedule:run command with a --task flag. The flag value is the container binding (usually the task class name):

 craft schedule:run --task SayHi

Or you can give your task a name explicitly:

from scheduler.Task import Task


class SayHi(Task):

    run_every = '1 minute'
    name = 'hey'

    def __init__(self):
        pass

    def handle(self):
        print('Hi!')

and then run the command by name

 craft schedule:run --task hey

Cron Jobs

Setting up Cron Jobs are for UNIX based machines like Mac and Linux only. Windows has a similar schedule called Task Scheduler which is similar but will require different instructions in setting it up.

Although the command above is useful, it is not very practical in a production setup. In production, we should setup a cron job to run that command every minute so Masonite can decide on what jobs need to be ran.

We'll show you an example cron job and then we will walk through how to build it.

PATH=/Users/Masonite/Programming/project_name/venv/bin:/Library/Frameworks/Python.framework/Versions/3.6/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Frameworks/Python.framework/Versions/3.6/bin
* * * * * cd /Users/Masonite/Programming/project_name && source venv/bin/activate && craft schedule:run

Getting The Path

When a cron job runs, it will typically run commands with a /bin/sh command instead of the usual /bin/bash. Because of this, craft may not be found on the machine so we need to tell the cron job the PATH that should be loaded in. We can simply find the PATH by going to our project directory and running:

$ env

Which will show an output of something like:

...
__CF_USER_TEXT_ENCODING=0x1F5:0x0:0x0
PATH=/Library/Frameworks/Python.framework/Versions/3.6/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Frameworks/Python.framework/Versions/3.6/bin
PWD=/Users/Masonite/Programming/masonite
...

If you are using a virtual environment for development purposes then you need to run the env command inside your virtual environment.

We can then copy the PATH and put it in the cron job.

To enter into cron, just run:

$ env EDITOR=nano crontab -e

and paste the PATH we just copied. Once we do that our cron should look like:

PATH=/Users/Masonite/Programming/masonitetesting/venv/bin:/Library/Frameworks/Python.framework/Versions/3.6/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Frameworks/Python.framework/Versions/3.6/bin

Exit out of nano. Now we just need to setup the actual cron job:

Setting The Cron Task

Now we just need to setup the cron task itself. This could be as easy as copying it and pasting it into the nano editor again. You may need to change a few things though.

The first * * * * * part is a must and bascially means "run this every minute by default". The next part is the location of your application which is dependant on where you installed your Masonite application.

The next part is dependant on your setup. If you have a virtual environment then you need to activate it by appending && source venv/bin/activate to the cron task. If you are not running in a virtual environment then you can leave that part out.

Lastly we need to run the schedule command so we can append && craft schedule:run

Great! Now we have a cron job that will run the craft command every minute. Masonite will decide which classes need to be executed.

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')

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 compromised) which can read all cookies on your website and send them to the hacker.

Other frameworks use cryptographic signing which attaches 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 this 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:

$ craft key

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:

$ craft key --store

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:

from masonite.auth import Sign

sign = Sign()

signed = sign.sign('value') # PSJDUudbs87SB....

sign.unsign(signed) # 'value'

By default, Sign() uses the encryption key in your config/application.py file but you could also pass in your own key.

from masonite.auth import Sign

encryption_key = b'SJS(839dhs...'

sign = Sign(encryption_key)

signed = sign.sign('value') # PSJDUudbs87SB....

sign.unsign(signed) # 'value'
from masonite.auth import Sign
from cryptography.fernet import Fernet

encryption_key = Fernet.generate_key()

sign = Sign(encryption_key)

signed = sign.sign('value') # PSJDUudbs87SB....

sign.unsign(signed) # 'value'

Just remember to store the key you generated or you will not be able to decrypt any values that you encrypted in the first place.

Using bcrypt

Bcrypt is very easy to use and 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 using the password helper function:

from masonite.helpers import password

encrypted_password = password('secret')

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

User.create(
    name=request.input('name'),
    password=password,
    email=request.input('email'),
)

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:

bcrypt.checkpw(bytes('password', 'utf-8'), bytes(model.password, 'utf-8'))

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.

Validation

Validation

Introduction

There are a lot of times when you need to validate incoming input either from a form or from an incoming json request. It is wise to have some form of backend validation as it will allow you to build more secure applications. Masonite provides an extremely flexible and fluent way to validate this data.

Validations are based on rules where you can pass in a key or a list of keys to the rule. The validation will then use all the rules and apply them to the dictionary to validate.

Validating The Request

Incoming form or JSON data can be validated very simply. All you need to do is import the Validator class, resolve it, and use the necessary rule methods.

This whole snippet will look like this in your controller method:

from masonite.validation import Validator

def show(self, request: Request, validate: Validator):
    """
    Incoming Input: {
        'user': 'username123',
        'email': 'user@example.com',
        'terms': 'on'
    }
    """
    errors = request.validate(

        validate.required(['user', 'email']),
        validate.accepted('terms')

    )

    if errors:
        request.session.flash('errors', errors)
        return request.back()

This validating will read like "user and email are required and the terms must be accepted" (more on available rules and what they mean in a bit)

Note you can either pass in a single value or a list of values

Creating a Rule

Sometimes you may cross a time where you need to create a new rule that isn't available in Masonite or there is such a niche use case that you need to build a rule for.

In this case you can create a new rule.

Rule Command

You can easily create a new rule boiler plate by running:

$ craft rule equals_masonite

There is no particular reason that rules are lowercase class names. The main reason it is improves readability when you end up using it as a method if you choose to register the rule with the validation class like you will see below.

This will create a boiler plate rule inside app/rules/equals_masonite.py that looks like:

class equals_masonite(BaseValidation):
    """A rule_name validation class
    """

    def passes(self, attribute, key, dictionary):
        """The passing criteria for this rule.

        ...
        """
        return attribute

    def message(self, key):
        """A message to show when this rule fails

        ...
        """
        return '{} is required'.format(key)

    def negated_message(self, key):
        """A message to show when this rule is negated using a negation rule like 'isnt()'

        ...
        """
        return '{} is not required'.format(key)

Constructing our Rule

Our rule class needs 3 methods that you see when you run the rule command, a passes, message and negated_message methods.

Passes Method

The passes method needs to return some kind of boolean value for the use case in which this rule passes.

For example if you need to make a rule that a value always equals Masonite then you can make the method look like this:

def passes(self, attribute, key, dictionary):
    """The passing criteria for this rule.

    ...
    """
    return attribute == 'Masonite'

When validating a dictionary like this:

{
  'name': 'Masonite'
}

then

  • the attribute will be the value (Masonite)

  • the key will be the dictionary key (name)

  • the dictionary will be the full dictionary in case you need to do any additional checks.

Message method

The message method needs to return a string used as the error message. If you are making the rule above then our rule may so far look something like:

def passes(self, attribute, key, dictionary):
    """The passing criteria for this rule.

    ...
    """
    return attribute == 'Masonite'

def message(self, key):
    return '{} must be equal to Masonite'.format(key)

Negated Message

The negated message method needs to return a message when this rule is negated. This will basically be a negated statement of the message method:

def passes(self, attribute, key, dictionary):
    """The passing criteria for this rule.

    ...
    """
    return attribute == 'Masonite'

def message(self, key):
    return '{} must be equal to Masonite'.format(key)

def negated_message(self, key):
    return '{} must not be equal to Masonite'.format(key)

Registering our Rule

Now the rule is created we can use it in 1 of 2 ways.

Importing our rule

We can either import directly into our controller method:

from masonite.validation import Validator
from app.rules.equals_masonite import equals_masonite

def show(self, request: Request, validate: Validator):
    """
    Incoming Input: {
        'user': 'username123',
        'company': 'Masonite'
    }
    """
    valid = request.validate(

        validate.required(['user', 'company']),
        equals_masonite('company')

    )

or we can register our rule and use it with the Validator class as normal.

Register the rule

In any service provider's boot method (preferably a provider where wsgi=False to prevent it from running on every request) we can register our rule with the validator class.

If you don't have a provider yet we can make one specifically for adding custom rules:

$ craft provider RuleProvider

Then inside this rule provider's boot method we can resolve and register our rule. This will look like:

from app.rules.equals_masonite import equals_masonite
from masonite.validation import Validator

class RuleProvider(ServiceProvider):
    """Provides Services To The Service Container
    """

    wsgi = False

    ...

    def boot(self, validator: Validator):
        """Boots services required by the container
        """

        validator.register(equals_masonite)

Now instead of importing the rule we can just use it as normal:

from masonite.validation import Validator

def show(self, request: Request, validate: Validator):
    """
    Incoming Input: {
        'user': 'username123',
        'company': 'Masonite'
    }
    """
    valid = request.validate(

        validate.required(['user', 'company']),
        validate.equals_masonite('company')

    )

notice we called the method as if it was apart of the validator class this whole time.

Registering rules is especially useful when creating packages for Masonite that contain new rules.

Using The Validator Class

In addition to validating the request class we can also use the validator class directly. This is useful if you need to validate your own dictionary:

from masonite.validation import Validator

def show(self, validator: Validator):
    """
    Incoming Input: {
        'user': 'username123',
        'company': 'Masonite'
    }
    """
    valid = validator.validate({
        'user': 'username123',
        'company': 'Masonite'
    },
        validate.required(['user', 'company']),
        validate.equals_masonite('company')
    )

Just put the dictionary as the first argument and then each rule being its own argument.

Using The Decorator

Masonite validation has a convenient decorator you can use on your controller methods. This will prevent the controller method being hit all together if validation isn't correct:

from masonite.validation.decorators import validate
from masonite.validation import required

@validate(required('name'))
def show(self, view: View):
  return view.render(..)

This will return a JSON response. You can also choose where to redirect back to:

from masonite.validation.decorators import validate
from masonite.validation import required

@validate(required('name'), redirect='/login')
def show(self, view: View):
  return view.render(..)

As well as redirect back to where you came from (if you use the {{ back() }} template helper)

from masonite.validation.decorators import validate
from masonite.validation import required

@validate(required('name'), back=True)
def show(self, view: View):
  return view.render(..)

Both of these redirections will redirect with errors and input. So you can use the {{ old() }} template helper to get previous input.

Rule Enclosures

Rule enclosures are self contained classes with rules. You can use these to help reuse your validation logic. For example if you see you are using the same rules often you can use an enclosure to always keep them together and reuse them throughout your code base.

Rule Enclosure Command

You can create a rule enclosure by running:

$ craft rule:enclosure AcceptedTerms

You will then see a file generated like this inside app/rules:

from masonite.validation import RuleEnclosure

...

class AcceptedTerms(RuleEnclosure):

    def rules(self):
        """ ... """
        return [
            # Rules go here
        ]

Creating the Rule Enclosure

You can then fill the list with rules:

from masonite.validation import required, accepted

class LoginForm(RuleEnclosure):

    def rules(self):
        """ ... """
        return [
            required(['email', 'terms']),
            accepted('terms')
        ]

You can then use the rule enclosure like this:

from app.rules.LoginForm import AcceptedTerms

def show(self, request: Request):
    """
    Incoming Input: {
        'user': 'username123',
        'email': 'user@example.com',
        'terms': 'on'
    }
    """
    errors = request.validate(AcceptedTerms)

    if errors:
        request.session.flash('errors', errors)
        return request.back()

You can also use this in addition to other rules:

from app.rules.LoginForm import AcceptedTerms
from masonite.validations import email

def show(self, request: Request):
    """
    Incoming Input: {
        'user': 'username123',
        'email': 'user@example.com',
        'terms': 'on'
    }
    """
    errors = request.validate(
        AcceptedTerms,
        email('email')
    )

    if errors:
        request.session.flash('errors', errors)
        return request.back()

Message Bag

Working with errors may be a lot especially if you have a lot of errors which results in quite a big dictionary to work with.

Because of this, Masonite Validation comes with a MessageBag class which you can use to wrap your errors in. This will look like this:

from masonite.validation import MessageBag
# ...
def show(self, request: Request):
    errors = request.validate(
        email('email')
    )
    errors = MessageBag(errors)

Getting All Errors:

You can easily get all errors using the all() method:

errors = MessageBag(errors)
errors.all()
"""
{
  'email': ['Your email is required'],
  'name': ['Your name is required']
}
"""

Checking for any errors

errors = MessageBag(errors)
errors.any() #== True

Checking if the bag is Empty

This is just the opposite of the any() method.

errors = MessageBag(errors)
errors.empty() #== False

Checking For a Specific Error

errors = MessageBag(errors)
errors.has('email') #== True

Getting the first Key:

errors = MessageBag(errors)
errors.all()
"""
{
  'email': ['Your email is required'],
  'name': ['Your name is required']
}
"""
errors.first()
"""
{
  'email': ['Your email is required']
}
"""

Getting the Number of Errors:

errors = MessageBag(errors)
errors.count() #== 2

Converting to JSON

errors = MessageBag(errors)
errors.json()
"""
'{"email": ["Your email is required"],"name": ["Your name is required"]}'
"""

Get the Amount of Messages:

errors = MessageBag(errors)
errors.amount('email') #== 1

Get the Messages:

errors = MessageBag(errors)
errors.amount('email')
"""
['Your email is required']
"""

Get the Errors

errors = MessageBag(errors)
errors.errors()
"""
['email', 'name']
"""

Get all the Messages:

errors = MessageBag(errors)
errors.messages()
"""
['Your email is required', 'Your name is required']
"""

Merge a Dictionary

You can also merge an existing dictionary into the bag with the errors:

errors = MessageBag(errors)
errors.merge({'key': 'value'})

Template Helper

You can use the bag() template helper which will contain the list of errors. Inside an HTML template you can do something like this:

@if(bag().any())
    <div class="bg-yellow-200 text-yellow-800 px-4 py-2">
        <ul>
            @for message in bag().messages()
            <li>{{ message }}</li>
            @endfor
        </ul>
    </div>
@endif

This will give you all the errors inside each list.

Nested Validations

Sometimes you will need to check values that aren't on the top level of a dictionary like the examples shown here. In this case we can use dot notation to validate deeper dictionaries:

"""
{
  'domain': 'http://google.com',
  'email': 'user@example.com'
  'user': {
     'id': 1,
     'email': 'user@example.com',
     'status': {
         'active': 1,
         'banned': 0
     }
  }
}
"""
errors = request.validate(

    validate.required('user.email'),
    validate.truthy('user.status.active')

)

notice the dot notation here. Each . being a deeper level to the dictionary.

Nested Validations With Lists

Sometimes your validations will have lists and you will need to ensure that each element in the list validates. For example you want to make sure that a user passes in a list of names and ID's.

For this you can use the * asterisk to validate these:

"""
{
  'domain': 'http://google.com',
  'email': 'user@example.com'
  'user': {
     'id': 1,
     'email': 'user@example.com',
     'addresses': [{
         'id': 1, 'street': 'A Street',
         'id': 2, 'street': 'B Street'
     }]
  }
}
"""

Here is an example to make sure that street is a required field:

errors = request.validate(

    validate.required('user.addresses.*.street'),
    validate.integer('user.addresses.*.id'),

)

Custom Messages

All errors returned will be very generic. Most times you will need to specify some custom error that is more tailored to your user base.

Each rule has a messages keyword arg that can be used to specify your custom errors.

"""
{
  'terms': 'off',
  'active': 'on',
}
"""
validate.accepted(['terms', 'active'], messages = {
    'terms': 'You must check the terms box on the bottom',
    'active': 'Make sure you are active'
})

Now instead of returning the generic errors, the error message returned will be the one you supplied.

Leaving out a message will result in the generic one still being returned for that value.

Exceptions

By default, Masonite will not throw exceptions when it encounters failed validations. You can force Masonite to raise a ValueError when it hits a failed validation:

"""
{
  'domain': 'http://google.com',
  'email': 'user@example.com'
  'user': {
     'id': 1,
     'email': 'user@example.com',
     'status': {
         'active': 1,
         'banned': 0
     }
  }
}
"""
errors = request.validate(

    validate.required('user.email', raises=True),
    validate.truthy('user.status.active')

)

Now if the required rule fails it will throw a ValueError. You can catch the message like so:

try:
    errors = request.validate(

        validate.required('user.email', raises=True),
        validate.truthy('user.status.active')

    )
except ValueError as e:
    str(e) #== 'user.email is required'

Custom Exceptions

You can also specify which exceptions should be thrown with which key being checked by using a dictionary:

try:
    errors = request.validate(

        validate.required(['user.email', 'user.id'], raises={
            'user.id': AttributeError,
            'user.email': CustomException
        }),

    )
except AttributeError as e:
    str(e) #== 'user.id is required'
except CustomException as e:
    str(e) #== 'user.email is required'

All other rules within an explicit exception error will throw the ValueError.

String Validation

In addition to using the methods provided below, you can also use each one as a pipe delimitted string. For example these two validations are identical:

# Normal
errors = request.validate(
  validate.required(['email', 'username', 'password', 'bio']),
  validate.accepted('terms'),
  validate.length('bio', min=5, max=50),
  validate.strong('password')
)

# With Strings
errors = request.validate({
  'email': 'required',
  'username': 'required',
  'password': 'required|strong',
  'bio': 'required|length:5..50'
  'terms': 'accepted'
})

These rules are identical so use whichever feels more comfortable.

Available Rules

Accepted

The accepted rule is most useful when seeing if a checkbox has been checked. When a checkbox is submitted it usually has the value of on so this rule will check to make sure the value is either on, 1, or yes.

"""
{
  'terms': 'on'
}
"""
validate.accepted('terms')

Active_domain

This is used to verify that the domain being passed in is a DNS resolvable domain name. You can also do this for email addresses as well. The preferred search is domain.com but Masonite will strip out http://, https:// and www automatically for you.

"""
{
  'domain': 'http://google.com',
  'email': 'user@example.com'
}
"""
validate.active_domain(['domain', 'email'])

After_today

Used to make sure the date is a date after today. In this example, this will work for any day that is 2019-10-21 or later.

"""
{
  'date': '2019-10-20', # Or date in the future
}
"""
validate.after_today('date')

You may also pass in a timezone for this rule:

"""
{
  'date': '2019-10-20', # Or date in the future
}
"""
validate.after_today('date', tz='America/New_York')

Before_today

Used to make sure the date is a date before today. In this example, this will work for any day that is 2019-10-19 or earlier.

"""
{
  'date': '2019-10-20', # Or date in the past
}
"""
validate.before_today('date')

You may also pass in a timezone for this rule:

"""
{
  'date': '2019-10-20', # Or date in the past
}
"""
validate.before_today('date', tz='America/New_York')

Confirmed

This rule is used to make sure a key is "confirmed". This is simply a key_confirmation representation of the key.

For example, if you need to confirm a password you would set the password confirmation to password_confirmation.

"""
{
  'password': 'secret',
  'password_confirmation': 'secret'
}
"""
validate.confirmed('password')

Contains

This is used to make sure a value exists inside an iterable (like a list or string). You may want to check if the string contains the value Masonite for example:

"""
{
  'description': 'Masonite is an amazing framework'
}
"""
validate.contains('description', 'Masonite')

Date

"""
{
  'date': '1975-05-21T22:00:00'
}
"""
validate.date('date')

Different

"""
{
  'first_name': 'Sam',
  'last_name': 'Gamji'
}
"""
validate.different('first_name', 'last_name')

Distinct

Used to check that an array value contains distinct items.

"""
{
  'users': ['mark', 'joe', 'joe']
}
"""
validate.distinct('users')  # would fail
"""
{
  'users': [
       {
            'id': 1,
            'name': 'joe'
       },
       {
            'id': 2,
            'name': 'mark'
       },
  ]
}
"""
validate.distinct('users.*.id')  # would pass

Does_not

Used for running a set of rules when a set of rules does not match. Has a then() method as well. Can be seen as the opposite of when.

"""
{
  'age': 15,
  'email': 'user@email.com',
  'terms': 'on'
}
"""
validate.does_not(
    validate.exists('user')
).then(
    validate.accepted('terms'),
)

Email

This is useful for verifying that a value is a valid email address

"""
{
  'domain': 'http://google.com',
  'email': 'user@example.com'
}
"""
validate.email('email')

Equals

Used to make sure a dictionary value is equal to a specific value

"""
{
  'age': 25
}
"""
validate.equals('age', 25)

Exists

Checks to see if a key exists in the dictionary.

"""
{
  'email': 'user@example.com',
  'terms': 'on'
  'age': 18
}
"""
validate.exists('terms')

This is good when used with the when rule:

"""
{
  'email': 'user@example.com',
  'terms': 'on'
  'age': 18
}
"""
validate.when(
    validate.exists('terms')
).then(
    validate.greater_than('age', 18)
)

File

Used to make sure that value is a valid file.

"""
{
  'document': '/my/doc.pdf'
}
"""
validate.file('document')

Additionally you can check file size, with different file size formats:

validate.file('document', 1024) # check valid file and max size is 1 Kilobyte (1024 bytes)
validate.file('document', '1K') # check valid file and max size is 1 Kilobyte (1024 bytes), 1k or 1KB also works
validate.file('document', '15M') # check valid file and max size is 15 Megabytes

Finally file type can be checked through a MIME types list:

validate.file('document', mimes=['jpg', 'png'])

You can combine all those file checks at once:

validate.file('document', mimes=['pdf', 'txt'], size='4MB')

Greater_than

This is used to make sure a value is greater than a specific value

"""
{
  'age': 25
}
"""
validate.greater_than('age', 18)

Image

Used to make sure that value is a valid image.

"""
{
  'avatar': '/my/picture.png'
}
"""
validate.image('avatar')

Valid image types are defined by all MIME types starting with image/. For more details you can check mimetypes Python package which gives known MIME types with mimetypes.types_map.

Additionally you can check image size as with basic file validator

validate.image('avatar', size="2MB")

In_range

Used when you need to check if an integer is within a given range of numbers

"""
{
  'attendees': 54
}
"""
validate.in_range('attendees', min=24, max=64)

Ip

You can also check if the input is a valid IPv4 address:

"""
{
  'address': '78.281.291.8'
}
"""
validate.ip('address')

Is_future

Checks to see the date and time passed is in the future. This will pass even if the datetime is 5 minutes in the future.

"""
{
  'date': '2019-10-20', # Or date in the future
}
"""
validate.is_future('date')

You may also pass in a timezone for this rule:

"""
{
  'date': '2019-10-20', # Or date in the future
}
"""
validate.is_future('date', tz='America/New_York')

Is_list

Used to make sure the value is a list (a Python list instance)

"""
{
  'tags': [1,3,7]
}
"""
validate.is_list('tags')

* notation can also be used

"""
{
  'name': 'Joe',
  'discounts_ref': [1,2,3]
}
"""
validate.is_list('discounts_ref.*')

Is_in

Used to make sure if a value is in a specific value

"""
{
  'age': 5
}
"""
validate.is_in('age', [2,4,5])

notice how 5 is in the list

Is_past

Checks to see the date and time passed is in the past. This will pass even if the datetime is 5 minutes in the past.

"""
{
  'date': '2019-10-20', # Or date in the future
}
"""
validate.is_past('date')

You may also pass in a timezone for this rule:

"""
{
  'date': '2019-10-20', # Or date in the future
}
"""
validate.is_past('date', tz='America/New_York')

Isnt

This will negate all rules. So if you need to get the opposite of any of these rules you will add them as rules inside this rule.

For example to get the opposite if is_in you will do:

"""
{
  'age': 5
}
"""
validate.isnt(
  validate.is_in('age', [2,4,5])
)

This will produce an error because age it is looking to make sure age is not in the list now.

Json

Used to make sure a given value is actually a JSON object

"""
{
  'user': 1,
  'payload': '[{"email": "user@email.com"}]'
}
"""
validate.json('payload')

Length

Used to make sure a string is of a certain length

"""
{
  'user': 1,
  'description': 'this is a long description'
}
"""
validate.length('description', min=5, max=35)

Less_than

This is used to make sure a value is less than a specific value

"""
{
  'age': 25
}
"""
validate.less_than('age', 18)

Matches

Used to make sure the value matches another field value

"""
{
  'user1': {
    'role': 'admin'
  },
  'user2': {
    'role': 'admin'
  }
}
"""
validate.matches('user1.role', 'user2.role')

None

Used to make sure the value is None

"""
{
  'age': 25,
  'active': None
}
"""
validate.none('active')

Numeric

Used to make sure a value is a numeric value

"""
{
  'age': 25,
  'active': None
}
"""
validate.numeric('age')

One_of

Sometimes you will want only one of several fields to be required. At least one of them need to be required.

"""
{
  'user': 'Joe',
  'email': 'user@example.com,
  'phone': '123-456-7890'
}
"""
validate.one_of(['user', 'accepted', 'location'])

This will pass because at least 1 value has been found: user.

Phone

You can also use the phone validator to validate the most common phone number formats:

"""
{
  'phone': '876-827-9271'
}
"""
validate.phone('phone', pattern='123-456-7890')

The available patterns are:

  • 123-456-7890

  • (123)456-7890

Postal Code

Every country has their own postal code formats. We added regular expressions for over 130 countries which you can specify by using a comma separated string of country codes:

"""
{
  'zip': '123456'
}
"""
validate.postal_code('zip', "US,IN,GB")

Please look up the "alpha-2 code" for available country formats.

Regex

Sometimes you want to do more complex validations on some fields. This rule allows to validate against a regular expression directly. In the following example we check that username value is a valid user name (without special characters and between 3 and 16 characters).

"""
{
  'username': 'masonite_user_1'
}
"""
validate.regex('username', pattern='^[a-z0-9_-]{3,16}$'))

Required

"""
{
  'age': 25,
  'email': 'user@email.com',
  'first_name': ''
}
"""
validate.required(['age', 'email'])
validate.required('first_name')  # would fail
validate.required('last_name')  # would fail

Required If

Used to make sure that value is present and not empty only if an other field has a given value.

"""
{
  'first_name': 'Sam',
  'last_name': 'Gamji'
}
"""
validate.required_if('first_name', 'last_name', 'Gamji')

Required With

Used to make sure that value is present and not empty onlyf if any of the other specified fields are present.

"""
{
  'first_name': 'Sam',
  'last_name': 'Gamji'
  'email': 'samgamji@lotr.com'
}
"""
validate.required_with('email', ['last_name', 'nick_name'])

String

Used to make sure the value is a string

"""
{
  'age': 25,
  'email': 'user@email.com'
}
"""
validate.string('email')

Strong

The strong rule is used to make sure a string has a certain amount of characters required to be considered a "strong" string.

This is really useful for passwords when you want to make sure a password has at least 8 characters, have at least 2 uppercase letters and at least 2 special characters.

"""
{
  'email': 'user@email.com'
  'password': 'SeCreT!!'
}
"""
validate.strong('password', length=8, special=2, uppercase=3)

Timezone

You can also validate that a value passed in a valid timezone

"""
{
  'timezone': 'America/New_York'
}
"""
validate.timezone('timezone')

Truthy

Used to make sure a value is a truthy value. This is anything that would pass in a simple if statement.

"""
{
  'active': 1,
  'email': 'user@email.com'
}
"""
validate.truthy('active')

Uuid

"""
{
  'doc_id': 'c1d38bb1-139e-4099-8a20-61a2a0c9b996'
}
"""
# check value is a valid UUID4
validate.uuid('doc_id')
# check value is a valid UUID3
validate.uuid('doc_id', 3)  # or '3'

Video

Used to make sure that value is a valid video file.

"""
{
  'document': '/my/movie.mp4'
}
"""
validate.video('document')

Valid video types are defined by all MIME types starting with video/. For more details you can check mimetypes Python package which gives known MIME types with mimetypes.types_map.

Additionally you can check video size as with basic file validator

validate.video('document', size="2MB")

When

Conditional rules. This is used when you want to run a specific set of rules only if a first set of rules succeeds.

For example if you want to make terms be accepted ONLY if the user is under 18

"""
{
  'age': 15,
  'email': 'user@email.com',
  'terms': 'on'
}
"""
validate.when(
    validate.less_than('age', 18)
).then(
    validate.required('terms'),
    validate.accepted('terms')
)

View Composers, Sharing and Filters

Introduction

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

from masonite.request import Request

def show(self, request: Request):
    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.

View Sharing

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

from masonite.view import View
...

def boot(self, view: View)
    view.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.

from masonite.request import Request
from masonite.view import View

class ViewComposer(ServiceProvider):

    wsgi = False

    def register(self):
        pass

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

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

from app.providers.ViewComposer import ViewComposer

PROVIDERS = [
    # Framework Providers
    ...
    ViewProvider,
    HelpersProvider,
    ...

    # Third Party Providers
    ...

    # Application Providers
    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, view: View, request: Request):
    view.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, view: View, request: Request):
    view.composer(['dashboard', 'dashboard/user'], {'request': request})

Lastly, we can compose a dictionary for all templates:

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

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

View Filters

Jinja2 allows adding filters to your views. Before we explain how to add filters to all of your templates, let's explain exactly what a view filter is.

What is a Filter?

Filters can be attached to view variables in order to change and modify them. For example you may have a variable that you want to turn into a slug and have something like:

{{ variable|slug }}

In Python, this slug filter is simply a function that takes the variable as an argument and would look like a simple function like this:

def slug(variable):
    return variable.replace(' ', '-')

That's it! It's important to note that the variable it is filtering is always passed as the first argument and all other parameters are passed in after so we could do something like:

{{ variable|slug('-') }}

and then our function would look like:

def slug(variable, replace_with):
    return variable.replace(' ', replace_with)

Adding Filters

We can add filters simply using the filter method on the ViewClass class. This will look something like:

from masonite.view import View

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

    wsgi = False

    ...

    def boot(self, view: View):
        view.filter('slug', self.slug)

    @staticmethod
    def slug(item):
        return item.replace(' ', '-')

That's it! Adding filters is that easy!

View Tests

View tests are simply custom boolean expressions that can be used in your templates. We may want to run boolean tests on specific objects to assert that they pass a test. For example we may want to test if a user is an owner of a company like this:

<div>
    {% if user is a_company_owner %}
        hey boss
    {% else %}
        you are an employee
    {% endif %}
</div>

In order to do this we need to add a test on the View class. We can do this in a Service Provider. The Service Provider you choose should preferably have a wsgi=False attribute so the test isn't added on every single request which could potentially slow down the application.

The code is simple and looks something like this:

from masonite.view import View
...

def a_company_owner(user):
    # Returns True or False
    return user.owner == 1

class SomeProvider:
    wsgi = False

    ...

    def boot(self, view: View):
                  # template alias
        view.test('a_company_owner', a_company_owner)

That's it! Now we can use the a_company_owner in our templates just like the first code snippet above!

Notice that we only supplied the function and we did not instantiate anything. The function or object we supply needs to have 1 parameter which is the object or string we are testing.

Adding Extensions

from masonite.view import View

class SomeProvider:
    wsgi = False

    ...

    def boot(self, view: View):
        view.add_extension('pypugjs.ext.jinja.PyPugJSExtension')

This will add the extension to the view class.

Remember to place this in a service provider where wsgi=False as this will prevent the extension being added on every request.

Selenium Testing

Introduction

Selenium tests are browser tests. With normal unit testing you are usually testing sections of your code like JSON endpoints or seeing a header on a page.

With Selenium testing, you are able to test more advanced features that may require JS handling like what happens when a user clicks a button or submits or a form.

With selenium testing, you will build a bunch of actions for your tests and a browser will open and actually go step by step according to your instructions.

This package comes shipped with all the required selenium drivers so there is no need for any external selenium server installations.

Getting Started

Installation

First we will need to install the package:

$ pip install masonite-selenium

Creating The TestCase

This package extends the current testing framework so you will add the TestCase to your current unit tests.

Let's build a new TestCase and test the homepage that shows when Masonite is first installed:

$ craft test HomePage

This will generate a new test that looks like this:

from masonite.testing import TestCase


class TestHomePage(TestCase):

    """All tests by default will run inside of a database transaction."""
    transactions = True

    def setUp(self):
        """Anytime you override the setUp method you must call the setUp method
        on the parent class like below.
        """
        super().setUp()

    def setUpFactories(self):
        """This runs when the test class first starts up.
        This does not run before every test case. Use this method to
        set your database up.
        """
        pass

Building The TestCase

In order to build the selenium test we need to import the class and add it to our TestCase.

from masonite.testing import TestCase
from masonite.testing.selenium import SeleniumTestCase


class TestHomePage(TestCase, SeleniumTestCase):

    """All tests by default will run inside of a database transaction."""
    transactions = True

    def setUp(self):
        """Anytime you override the setUp method you must call the setUp method
        on the parent class like below.
        """
        super().setUp()

We now have all methods available to us to start building our selenium tests.

Using a Browser

First before we build our TestCase, we need to specify which browser we want. The current 2 options are: chrome and firefox.

You can specify which browser to use using the useBrowser() method inside the setUp() method.

from masonite.testing import TestCase
from masonite.testing.selenium import SeleniumTestCase


class TestHomePage(TestCase, SeleniumTestCase):

    """All tests by default will run inside of a database transaction."""
    transactions = True

    def setUp(self):
        """Anytime you override the setUp method you must call the setUp method
        on the parent class like below.
        """
        super().setUp()
        self.useBrowser('chrome', version='74')
        # self.useBrowser('firefox')

If you are using the chrome driver you can optionally specify which version to run.

Headless

You can optionally specify if you want to run your browsers in a headless state. This means that the browser will not actually open to run tests but will run in the background. This will not effect your tests but is just a preference and usually your tests will run faster.

self.useBrowser('chrome', version='74', headless=True)

Building The Test

Here is a basic example on building a test for the installed homepage:

def test_can_see_homepage(self):
    (self.visit('/')
        .assertSee('Masonite 2.2'))

we can then run the test by running:

$ python -m pytest

Method Chaining

You can chain all methods together to build up and mock user actions. An example test might look like this:

def test_can_login_to_pypi(self):
    (self.visit('https://pypi.org/')
        .clickLink('Log in')
        .assertSee('Log in to PyPI')
        .text('#username', 'josephmancuso')
        .text('#password', 'secret')
        .submit().assertCantSee('Your projects'))

Selectors

When finding a selector you can use a few symbols to help navigate the page

Take this form for example:

<form action="/submit">
    <input id="username" type="text" name="username">
    <input id="password" type="password" name="password">

    <button type="submit">Submit</button>
</form>

Selecting by ID

You can select an ID by using the # symbol:

(self.visit('/form')
    .text('#username', 'user')
    .text('#password', 'secret')
    .submit())

Selecting by Name

You can select by the name by simply passing in the name value. This will default to the name attribute:

(self.visit('/form')
    .text('username', 'user')
    .text('password', 'secret')
    .submit())

Selecting by Class

(self.visit('/form')
    .text('.username', 'user')
    .text('.password', 'secret')
    .submit())

Selecting by a Unique Attribute

The issue with selecting by a normal selector like an ID or a name is that these could change. This is why you are able to select with a unique attribute name.

You may change your form a bit to do something like this instead:

<form action="/submit">
    <input selenium="username" type="text" name="username">
    <input selenium="password" type="password" name="password">

    <button type="submit">Submit</button>
</form>

You can then tell Masonite what the name of your unique attribute is:

from masonite.testing import TestCase
from masonite.testing.selenium import SeleniumTestCase


class TestHomePage(TestCase, SeleniumTestCase):

    """All tests by default will run inside of a database transaction."""
    transactions = True
    unique_attribute = 'selenium'

    def setUp(self):
        """Anytime you override the setUp method you must call the setUp method
        on the parent class like below.
        """
        super().setUp()
        self.useBrowser('chrome', version='74')
        # self.useBrowser('firefox')

and finally you can select by that attribute using the @ symbol:

(self.visit('/form')
    .text('@username', 'user')
    .text('@password', 'secret')
    .submit())

Available Methods

Below are the available methods you can use to build your tests.

visit

This method will navigate to a URL

self.visit('/')

If the URL does not start with http then Masonite will prepend the APP_URL environment variable to the front. If this is running inside your Masonite application, you can change this value in your .env file.

assertTitleIs

This method will assert that the title is a specific value

(self.visit('https://www.python.org/')
    .assertTitleIs('Welcome'))

assertTitleIsNot

Opposite of assertTitleIs.

assertUrlIs

This method will assert that the current URL is a specific value

(self.visit('https://www.python.org/')
    .assertUrlIs('https://www.python.org/'))

assertSee

Asserts that something is available on the page that the user can see

(self.visit('https://www.python.org/')
    .assertSee('Python'))

assertCanSee

Just an alias for assertSee.

assertCantSee

Opposite of assertCanSee. Used to assert that text is not on the page.

text

Types text into a text box.

(self.visit('https://www.python.org/')
    .text('#username', 'user123')
    .text('#password', 'pass123'))

selectBox

You can choose an option in a select box by its value:

(self.visit('https://www.python.org/')
    .selectBox('#fruit', 'apple'))

check

This will check a checkbox

(self.visit('https://www.python.org/')
    .check('#checkbox'))

resize

Resizes the window based on a width and heigh parameter

(self.visit('https://www.python.org/')
    .resize(800, 600)) # width, height

mazimize

Maximizes the window

(self.visit('https://www.python.org/')
    .maximize())

minimize

Minimizes the window

(self.visit('https://www.python.org/')
    .minimize())

refresh

Refreshes the window

(self.visit('https://www.python.org/')
    .refresh())

back

Navigates backwards

(self.visit('https://www.python.org/')
    .back())

forward

Navigates forwards

(self.visit('https://www.python.org/')
    .forward())

link

Clicks a link on a page

(self.visit('https://www.python.org/')
    .link('#login'))

clickLink

Alias for link.

submit

Submits the current form the last entered element is in

(self.visit('https://www.python.org/')
    .text('#username', 'user123')
    .text('#password', 'pass123')
    .submit())

You can also submit another form by entering a selector

(self.visit('https://www.python.org/')
    .text('#username', 'user123')
    .text('#password', 'pass123')
    .submit('#another-form'))

click

Clicks an element

(self.visit('https://www.python.org/')
    .click('#button'))

close

Closes the browser

(self.visit('https://www.python.org/')
    .close())

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 UploadGoogleDriver 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:

Contracts

In order to ensure that all drivers follow the same structure, we can use Contracts. Contracts are essentially interfaces if you are coming from other programming languages. They make sure that the classes they inherit into have the minimum methods necessary in order to be accepted as a driver. If it does not meet the minumum methods necessary then you will keep hitting exceptions every time you tried to run your code.

For our specific driver we should inherit from the UploadContract:

Coding Our Driver

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.

Headers

Headers

Introduction

Masonite allows you to easily add security headers to your application. Masonite adds some sensible defaults but you can modify them as you need.

Configuration

All you need to do is add the middleware to your HTTP_MIDDLEWARE constant in your config/middleware.py file:

This will add these default headers for your server:

Overriding Headers

If you want to change or add any headers, you just need to specify them in your config/middleware.py file and this middleware will automatically pick them up. For example you can change the X-Frame-Options header like this:

This will then change your headers to:

Notice the change in the new header we changed.

CORS

You may also choose to use CORS for your application for advanced security measures. Using CORS is very similar to the secure headers above.

This middleware needs to be at the TOP of the HTTP_MIDDLEWARE stack so the request will not be rejected inside the other middleware.

To get started just import the CorsProvider class into your config/providers.py file and add it to your PROVIDERS list:

Then inside your config/middleware.py file you can put your CORS headers as a dictionary. Here is a list of sensible defaults:

Now if you go to a browser you will see these headers being sent as a response from your server.

Releases

Releases

Introduction

Masonite takes security seriously and wants full transparency with security vulnerabilities so we document each security release in detail and how to patch or fix it.

2.1.2

Issue

There were 2 issues involved with this release. The first is that input data was not being properly sanitized so there was an XSS vulnerability if the developer returned this directly back from a controller.

Another issue was that there were no filters on what could be uploaded with the upload feature. The disk driver would allow any file types to be uploaded to include exe, jar files, app files etc.

There was no reported exploitation of this. These were both proactively caught through analyzing code and possible vulnerabilities.

Fix

The fix for the input issue was simply to just escape the input before it is stored in the request dictionary. We used the html core module that ships with Python and created a helper function to be used elsewhere.

The fix to the second issue of file types was to limit all uploads to images unless explicitly stated otherwise through the use of the accept method.

Patch

The patch for this is to simply upgrade to 2.1.3 and explicitly state which file types you need to upload if they are not images like so:

Those choices to accept those files should be limited and up to the developer building the application sparingly.

2.3.24, 3.0.4

Session Based CSRF tokens

The security community recently discovered several vulnerabilities from the CSRF features of Masonite. The issue was that Masonite used session based CSRF tokens. Meaning a CSRF token was set in the cookie and then was used for each request. This provides an attacker to use size based sub channels to leak the token 1 character at a time. This is known as a BREACH attack.

The solution to this was to change the CSRF tokens to be changed on every request. Previously there was an attribute set on the CSRF middleware of every_request. This came disabled by default but in the security patch, this attribute is ignored and all csrf tokens will regenerate on every request.

Timing Attacks

Masonite was checking if the csrf token matched the token that was sent in the request. This is fine but we were using a comparison operator like ==. Python will check each character one at a time and return False once a character does not match. An attacker is able to send an arbitrary string and measure down to the nano seconds on how long the comparison takes.

The solution to this is to use a constant time comparison check so that comparing 2 strings always takes the amount of time. This was accomplished using hmac.compare_digest.

Cookie Tossing

Cookie tossing is when an attacker is able to obtain access to a subdomain and set cookies on the parent domain. They are then able to set the CSRF token to whatever they want which will match the CSRF token that is submitted.

The solution to this is now Masonite sets a session ID which is checked against the CSRF token and the users session ID which the attacker would not be able to guess. The session ID is unique to each user and changes every 5 minutes.

Cookie Overflow

Browsers can only store a certain amount of cookies before a browser starts deleting old cookies and replace them with the attackers cookies.

The solution to this is now Masonite sets a session ID which is checked against the CSRF token and the users session ID which the attacker would not be able to guess. The session ID is unique to each user and changes every 5 minutes.

Checking Unsafe HTTP Methods

We were previously only checking CSRF tokens on POST requests which allowed attackers the ability to change the request method in a form to bypass the CSRF protections.

The solution is to check the CSRF token for other unsafe request methods like POST, PUT, PATCH and DELETE

Replay Attacks

If session based CSRF tokens are used, an attacker has the ability to reuse the CSRF token it was able to get to attempt more than 1 attack. This assumes the CSRF token was able to be leaked in the first place.

The solution to this is Masonite now has per request CSRF tokens

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.

If your driver needs additional methods that can be used that are now inside a contract then your documentation should have that caveat listed in a somewhat obvious manner. This means that by the developer using that new method, they will not be able to switch to other drivers freely without hitting exceptions or having to manually use the methods used by the driver.

Therefore it is advisable to not code additional methods on your drivers and just keep to the methods provided by the base class and contract.

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:

Now this class will constantly throw exceptions until it overrides all the required methods in the class.

Resolving Contracts

It is useful if you want to "code to an interface and not an implementation." This type of programming paradigm allows your code to be very maintainable because you can simply swap out classes in the container that have the same contract.

For example, Masonite has specific manager contracts depending on the type of driver you are trying to resolve. If we are trying to get the manager for the upload drivers, we can resolve that manager via the corresponding upload manager:

Notice this simply returns the specific upload manager used for uploading. Now the upload manager is not a "concrete" implementation but is very swappable. You can load any instance of the the UploadManagerContract in the container and Masonite will fetch it for you.

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

BroadcastManagerContract

CacheContract

CacheManagerContract

MailContract

MailManagerContract

QueueContract

QueueManagerContract

UploadContract

UploadManagerContract

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 and responsible for managing a specific set of drivers. These managers are 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 manager. The config attribute is the configuration file you want which via the key in the container and the driver_prefix is the drivers you want to manage. In this case it is the Task{X}Driver. 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 our new manager:

Container Swapping

Although the above code works fine it might be more useful to add a container swap in our Service Provider so we can resolve an arbitrary shorthand class which will return our correct driver. We can do this in our boot method:

Now we can resolve this Task class which will return the correct driver since we specified a driver swap:

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.

Configuration

Make sure you put all your database your credentials and driver into the .env file. This file is in the .gitignore file for you so make sure that this file never gets submitted to source control.

MySQL

If you are using MySQL you can install:

or:

Postgres

Postgres databases only have a single option for a package:

SQLite

If you are using SQLite configure your .env file with the following options

Your sqlite database will be created on project root directory.

Getting Started

Before you start using the ORM you will receive an exception saying that you need to install one of the required packages for your database driver.

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:

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:

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.

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:

Inside our with statement we can start adding columns.

Lets go ahead and add some columns that can be used for a blog table.

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:

This will set up our column index to be ready for a foreign key. We can easily specify a foreign key by then typing

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:

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:

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:

Masonite API

Introduction

Installation

Install the PyPi package:

Add the Service Provider to your provider list in config/providers.py:

Adding The Guard

Masonite API comes with an api guard which you can use to handle fetching users as you would in a normal web app using the request.user() method or auth() function.

You can add this config to your config/auth.py:

Creating a Resource

We can create API Resources by building them wherever you want to. In this documentation we will put them in app/resources. We just need to create a simple resource class which inherits from api.resources.Resource.

You should also specify a model by importing it and specifying the model attribute:

Lastly for simplicity sake, we can specify a serializer. This will take any Orator models or Collection we return and serialize them into a dictionary which will be picked up via the JsonResponseMiddleware.

Awesome! Now we are ready to go!

Specifying our routes

Our resource has several routes that it will build based on the information we provided so let's import it into our web.py file so it will build the routes for us. We can also specify the base route which will be used for all routes.

This will build a route list that looks like:

We can also specify the routes that we want to create by setting the methods attribute:

This will only build those routes dependent on the methods specified:

Adding Middleware

You can easily add middleware to routes by specifying them using the middleware method:

Or of course you can add the middleware to a group:

Using The Guard Middleware

Overriding Methods

You may want to override some methods that are used internally by the API endpoint to return the necessary data.

The methods are: create, index, show, update, delete.

You can check the repo on how these methods are used and how you should modify them but it's pretty straight forward. The show method is used to show all the results are is what is returned when the endpoint is something like /api/user.

Overriding a method will look something like:

This will not only return all the results where active is 1. Keep in mind as well that these methods are resolved via the container so we can use dependency injection:

The index method is ran when getting all records with: POST /api/user.

Removing model attributes

Currently our response may look something like:

You might not want to display all the model attributes like id, email or password. We can choose to remove these with the without class attribute:

Now our response will look like:

Yes, it's that easy.

Authentication

For any API package to be awesome, it really needs to have powerful and simple authentication.

JWT Authentication

We can specify our authentication for our specific endpoint by inheriting from a class:

Now our endpoint is being JWT Authentication. If we hit our endpoint now in the browser by sending a GET request to http://localhost:8000/api/user. we will see:

JWT Tokens

We can easily create an endpoint for giving out and refreshing API tokens by adding some routes to our web.py:

Now we can make a POST request to http://localhost:8000/token with your username and password which will give us back a JWT token.

Retrieve a JWT Token

POST http://localhost:8000/token

Use this endpoint to retrieve new tokens using a username and password. The username and password will default to your regular authentication model located in config/auth.py.

Query Parameters

we can now use this token to make our calls by using that new token. This token is good for 5 minutes and it will be required to refresh the token once expired.

Refreshing Tokens

Once our JWT token expires, we need to refresh it by sending our old expired token to

Refresh JWT Tokens

POST http://localhost:8000/token/refresh

Path Parameters

Permission Scopes

You can also specify any permission scopes you need. Most of the time some API endpoints will need to have more restrictive scopes than others. We can specify whatever scopes we need to with the scopes attribute as well as inheriting another class.

Now all API endpoint will need to have the correct permission scopes. Hitting this API endpoint now will result in:

We can request the scopes we need by spending a POST request back to the endpoint with a scopes input. The scopes should be comma separated. The request should look like:

This will generate a new token with the correct permission scopes.

Filter Scopes

Filter scopes is an extension of the scopes above. It will filter the data based on the scope level. This is useful if you want a specific scope to have more permission than other scopes.

To do this we can extend our resource with the FilterScopes class:

Now when you make this request it will return the columns in accordance with the user scopes.

Creating Authentication Classes

Authentication classes are extremely simple classes. They just need to inherit the BaseAuthentication class and contain 2 methods: authenticate and get_token.

Authenticate

This method is resolved via the container. it is important to note that if the authenticate is successful, it should not return anything. Masonite will only look for exceptions thrown in this method and then correlate an error response to it.

For example if we want to return an error because the token was not found, we can raise that exception:

Which will result in an error response:

Get Token

This method is used to return a dictionary which is the decrypted version of the token. So however your authentication class should decrypt the token, it needs to do so in this method. This all depends on how the token was encrypted in the first place. This may look something like:

Creating Serializers

Serializers are simple classes with a single serialize method on them. The serialize method takes a single parameter which is the response returned from one of the create, index, show, update, delete methods.

For example if we return something like a model instance:

We will receive this output into our serialize method:

which we can then serialize how we need to and return it. Here is an example of a JSON serializer:

notice we take the response and then convert that response into a dictionary depending on the response type.

Once we convert to a dictionary here, the JSONResponseMiddleware will pick that dictionary up and return a JSON response.

Builtin Methods

We have access to a few builtin methods from anywhere in our resource.

Getting the token

The token can be in several different forms coming into the request. It can be either a JWT token or a regular token or some other form of token. Either way it needs to be "unencrypted" in order for it to be used and since the authentication class is responsible for un-encrypting it, it is the responsibility of the authentication class to get the token.

This method is only available when inheriting from an authentication class like JWTAuthentication which requires this method and should return the un-encrypted version of the token

Fetching the token

There are a few locations the token can be. The two main locations are in the query string itself (/endpoint?token=123..) or inside a header (the HTTP_AUTHORIZATION header). We can get the token regardless of where it is with the fetch_token() method:

Mike

Feel free to change which directories the files get compiled to. For more information on additional configuration values take a look at the

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.

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.

It would be wise to read about and ensure your specific application is protected against any avenues of attack.

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.

You can find the .

and then add the to our PROVIDERS list in config/providers.py:

All tasks should inherit the scheduler.task.Task class. This adds some needed methods to the task itself but also allows a way to fetch all the tasks from the .

Make sure you read about collecting objects from the container by reading the section of the documentation.

There are two ways to get classes into the container. The first is to manually by creating a . You can read the documentation on creating if you don't know how to do that.

The other way is to them. Starting with Masonite 2.0, You can autoload entire directories which will find classes in that directory and load them into the container. This can be done by adding the directory your tasks are located in to the AUTOLOAD config variable inside config/application.py:

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.

You can read the .

This feature uses for this kind of encryption. Because of this, we can generate keys using Fernet.

You can see a .

This is used to verify that the value is a valid date. module is used to verify validity. It supports the RFC 3339 format, most ISO 8601 formats and some other common formats.

Used to check that value is different from another field value. It is the opposite of validation rule.

For image or video file type validation prefer the direct and validation rules.

Used to make sure the value is actually available in the dictionary and not null. This will add errors if the key is not present. To check only the presence of the value in the dictionary use .

Used to check that a value is a valid UUID. The UUID version (according to ) standard can optionally be verified (1,3,4 or 5). The default version 4.

Make sure that you add filters in a that has wsgi=False set. This prevents filters from being added on every single request which is not needed.

Jinja2 has the concept of extensions and you can easily add them to your project in a similar way as previous implementations above which is in a :

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 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.

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.

is a package designed to make it dead simple to add externally facing API's with various types of authentication and permission scopes. There is a new concept called "API Resources" which you will use to build your specific endpoint. In this documentation we will walk through how to make a User Resource so we can walk through the various moving parts.

Great! If we specify a token by hitting then we will see a different error:

Name
Type
Description
Name
Type
Description
Laravel Mix Documentation
Service Container
Creating a Mail Driver
Queues and Jobs
Sentry
Sentry
Sentry
Sentry.io
Sentry.io
Sentry.io
pickle exploitations
Service Container
installation guide for RabbitMQ here
Service Provider
Autoload
About Drivers
bcrypt documentation here
pyca/cryptography
Pendulum
RFC 4122
Service Provider
Service Provider
container by collecting them
Service Container
Collecting
Service Provider
Service Providers
bind them into the container
list of available rules here
matches
image
video
exists
from masonite.contracts import UploadContract

class UploadDiskDriver(UploadContract):
    pass
from masonite.contracts import UploadContract

class UploadDiskDriver(UploadContract):
    pass
from masonite import Upload

def show(self, upload: Upload):
    upload.store(request().input('file'))
from masonite.contracts import UploadContract

class UploadDiskDriver(UploadContract):

    def store(self, fileitem, location=None):
        pass
from masonite.contracts import UploadContract
from config import storage, application

class UploadDiskDriver(UploadContract):

    def __init__(self, cls: SomeClass):
        self.cls = cls
        self.config = storage
        self.appconfig = application

    def store(self, fileitem, location=None):
        pass
from masonite.contracts import UploadContract
from config import storage, application

class UploadDiskDriver(UploadContract):

    def __init__(self):
        self.config = storage
        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 import UploadManager
from masonite.drivers 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, manager: UploadManager):
        self.app.bind('Upload', manager.driver(storage.DRIVER))
from masonite.middleware import SecureHeadersMiddleware

HTTP_MIDDLEWARE = [
    ...
    SecureHeadersMiddleware,
]
'Strict-Transport-Security': 'max-age=63072000; includeSubdomains'
'X-Frame-Options': 'SAMEORIGIN'
'X-XSS-Protection': '1; mode=block'
'X-Content-Type-Options': 'nosniff'
'Referrer-Policy': 'no-referrer, strict-origin-when-cross-origin'
'Cache-control': 'no-cache, no-store, must-revalidate'
'Pragma': 'no-cache'
config/middleware.py
SECURE_HEADERS = {
   'X-Frame-Options' : 'deny'
}
'Strict-Transport-Security': 'max-age=63072000; includeSubdomains'
'X-Frame-Options': 'deny'
'X-XSS-Protection': '1; mode=block'
'X-Content-Type-Options': 'nosniff'
'Referrer-Policy': 'no-referrer, strict-origin-when-cross-origin'
'Cache-control': 'no-cache, no-store, must-revalidate'
'Pragma': 'no-cache'
from masonite.providers import CorsProvider
...
PROVIDERS = [
    AppProvider,
    CorsProvider,
    ...,
]
from masonite.middleware import CorsMiddleware
...
HTTP_MIDDLEWARE = [
    ...,
]

ROUTE_MIDDLEWARE = [
    ...,
]

...

CORS = {
    'Access-Control-Allow-Origin': "*",
    "Access-Control-Allow-Methods": "DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT",
    "Access-Control-Allow-Headers": "Content-Type, Accept, X-Requested-With",
    "Access-Control-Max-Age": "3600",
    "Access-Control-Allow-Credentials": "true"
}
def show(self, upload: Upload):
    upload.accept('exe', 'jar').store('...')
from masonite.contracts import UploadContract

class UploadGoogleDriver(UploadContract):
    pass
from masonite.contracts import UploadManagerContract

def show(self, upload: UploadManagerContract):
    upload.store(..)
    upload.driver('s3').store(..)
from masonite.managers import Manager

class TaskManager(Manager):
    pass
from masonite.managers 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 import TaskTodoDriver
from masonite.managers 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, manager: TaskManager):
        self.app.bind('Task', manager.driver(task.DRIVER))
from masonite.managers import TaskManager

def show(self, manager: TaskManager):
    manager.driver('todo').method_here()
class Task:
    pass

..
    def boot(self, manager: TaskManager):
        self.app.bind('Task', manager.driver(task.DRIVER))
        self.app.swap(Task, manager.driver(task.DRIVER))
from somewhere import Task

def show(self, task: Task):
    task.method_here()
$ pip install mysqlclient
$ pip install PyMySQL
$ pip install psycopg2
.env
...
DB_CONNECTION=sqlite
DB_DATABASE=masonite
DB_LOG=True
...
$ craft migration name_of_migration_here --table dashboard
$ craft migration name_of_migration_here --create dashboard
$ craft migration create_blogs_table --create blogs
def up(self):
        """
        Run the migrations.
        """
        with self.schema.create('blogs') as table:
            table.increments('id')
            table.timestamps()
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()
table.integer('user_id').unsigned()
table.foreign('user_id').references('id').on('users')
$ craft migration change_default_status --table dashboard
table.integer('status').nullable().default(0).change()
table.drop_foreign('posts_user_id_foreign')
table.rename_column('user_id', 'author_id')
table.foreign('author_id').references('id').on('users')
$ pip install masonite-api
from masonite.api.providers import ApiProvider

PROVIDERS = [
    #..

    # Third Party Providers
    ApiProvider,
]
    'guards': {
        'web': {
            # ..
            # Normal config here
            # ..
        },
        'api': {
            'driver': 'jwt',
            'model': User,
        },
    }
from masonite.api.resources import Resource

class UserResource(Resource):
    pass
from masonite.api.resources import Resource
from app.User import User

class UserResource(Resource):
    model = User
from masonite.api.resources import Resource
from app.User import User
from masonite.api.serializers import JSONSerializer

class UserResource(Resource, JSONSerializer):
    model = User
...
from app.resources.UserResource import UserResource

ROUTES = [
    ...
    UserResource('/api/users').routes(),
    ...
]
========  =============  =======  ========  ============
Method    Path           Name     Domain    Middleware
========  =============  =======  ========  ============
POST      /api/user
GET       /api/user
GET       /api/user/@id
PATCH/PUT /api/user/@id
DELETE    /api/user/@id
========  =============  =======  ========  ============
from masonite.api.resources import Resource
from app.User import User
from masonite.api.serializers import JSONSerializer

class UserResource(Resource, JSONSerializer):
    model = User
    methods = ['create', 'index', 'show']
========  =============  =======  ========  ============
Method    Path           Name     Domain    Middleware
========  =============  =======  ========  ============
POST      /api/user
GET       /api/user
GET       /api/user/@id
========  =============  =======  ========  ============
...
from app.resources.UserResource import UserResource

ROUTES = [
    ...
    UserResource('/api/users').middleware('auth', 'managers').routes(),
    ...
]
...
from app.resources.UserResource import UserResource
from masonite.routes import RouteGroup

ROUTES = [
    ...
    RouteGroup([
         UserResource('/api/users').routes(),
    ], middleware=('auth', 'managers'))
    ...
]
from masonite.api.resources import Resource
from masonite.request import Request
from app.User import User
from masonite.api.serializers import JSONSerializer

class UserResource(Resource, JSONSerializer):
    model = User
    methods = ['create', 'index', 'show']

    def show(self, request: Request):
        return self.model.find(request.param('id'))
from masonite.api.resources import Resource
from app.User import User
from masonite.api.serializers import JSONSerializer
from masonite.request import Request

class UserResource(Resource, JSONSerializer):
    model = User
    methods = ['create', 'index', 'show']

    def show(self, request: Request):
        return self.model.where('active', self.request.input('active')).get()

    def index(self):
        return self.model.all()
{
    "id": 1,
    "name": "username",
    "email": "email@email.com",
    "password": "12345",
    "remember_token": null,
    "created_at": "2018-09-23T07:33:30.118068",
    "updated_at": "2018-09-23T11:47:48.962105",
    "customer_id": null,
    "plan_id": null,
    "is_active": null
}
from masonite.api.resources import Resource
from app.User import User
from masonite.api.serializers import JSONSerializer
from masonite.request import Request

class UserResource(Resource, JSONSerializer):
    model = User
    methods = ['create', 'index', 'show']
    without = ['id', 'email', 'password']
{
    "name": "username",
    "remember_token": null,
    "created_at": "2018-09-23T07:33:30.118068",
    "updated_at": "2018-09-23T11:47:48.962105",
    "customer_id": null,
    "plan_id": null,
    "is_active": null
}
from masonite.api.resources import Resource
from app.User import User
from masonite.api.serializers import JSONSerializer
from masonite.api.authentication import JWTAuthentication

class UserResource(Resource, JSONSerializer, JWTAuthentication):
    model = User
    methods = ['create', 'index', 'show']
{
    "error": "no API token found"
}
{
    "error": "token is invalid"
}
...
from app.resources.UserResource import UserResource
from masonite.api.routes import JWTRoutes

ROUTES = [
    ...
    UserResource('/api/user').routes(),
    JWTRoutes('/token'),
    ...
]

username

string

The username to authenticate using your authentication model

password

string

The password to authenticate using your authentication model

{
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3N1ZWQiOiIyMDE4LTA5LTIzVDE1OjA1OjM4LjE1MTQyMC0wNDowMCIsImV4cGlyZXMiOiIyMDE4LTA5LTIzVDE1OjEwOjM4LjE1MTY4Mi0wNDowMCIsInNjb3BlcyI6ZmFsc2V9.7BnD7Ro1oK2WdJOU4hsBY3KK0tojBszZAaazQ6MMK-4"
}

token

string

The expired JWT token

{
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3N1ZWQiOiIyMDE4LTA5LTIzVDE1OjA1OjM4LjE1MTQyMC0wNDowMCIsImV4cGlyZXMiOiIyMDE4LTA5LTIzVDE1OjEwOjM4LjE1MTY4Mi0wNDowMCIsInNjb3BlcyI6ZmFsc2V9.7BnD7Ro1oK2WdJOU4hsBY3KK0tojBszZAaazQ6MMK-4"
}
from masonite.api.resources import Resource
from app.User import User
from masonite.api.serializers import JSONSerializer
from masonite.api.authentication import JWTAuthentication, PermissionScopes

class UserResource(Resource, JSONSerializer, JWTAuthentication, PermissionScopes):
    model = User
    methods = ['create', 'index', 'show']
    scopes = ['user:read']
{
    "error": "Permission scope denied. Requires scopes: user:read"
}
http://localhost:8000/token?scopes=user:read,user:create
from masonite.api.filters import FilterScopes

class UserResource(..., ..., FilterScopes):
    model = User
    methods = ['create', 'index', 'show']
    scopes = ['user:read']
    filter_scopes = {
        'user:read': ['name', 'email'],
        'user:manager': ['id', 'name', 'email', 'active', 'password']
    }
from masonite.api.authentication import BaseAuthentication

class JWTAuthentication(BaseAuthentication):

    def authenticate(self, request: Request):
        """Authenticate using a JWT token
        """

        pass

    def get_token(self):
        """Returns the decrypted string as a dictionary. This method needs to be overwritten on each authentication class.

        Returns:
            dict -- Should always return a dictionary
        """

        pass
...
from masonite.api.exceptions import NoApiTokenFound
...

    def authenticate(self, request: Request):
        """Authenticate using a JWT token
        """

        if not request.input('token'):
            raise NoApiTokenFound
{
    "error": "no API token found"
}
from masonite.api.exceptions import InvalidToken
...
        def get_token(self):
        """Returns the decrypted string as a dictionary. This method needs to be overwritten on each authentication class.

        Returns:
            dict -- Should always return a dictionary
        """
        try:
            return jwt.decode(self.fetch_token(), KEY, algorithms=['HS256'])
        except jwt.DecodeError:
            raise InvalidToken
def index(self):
    return self.model.find(1)
def serialize(self, response):
    response # == self.model.find(1)
import json
from orator.support.collection import Collection
from orator import Model

class JSONSerializer:

    def serialize(self, response):
        """Serialize the model into JSON
        """

        if isinstance(response, Collection):
            return response.serialize()
        elif isinstance(response, Model):
            return response.to_dict()

        return response
def index(self):
    token = self.get_token()
def index(self):
    token = self.fetch_token()

Optimization

Introduction

Masonite can handle most of the application that will be built with it. Python is clearly not the fastest language so if it is brute speed to manage 100,000 simultaneous connections than no application written in Python will suit your needs. Those types of application will typically require some Node.js server and asynchronous tasks on multiple servers but if you are going with a Python framework than it is likely you will not need that much juice.

WSGI Server

Out of the box, Masonite is designed to run as a great development environment. There are a few trade off's that need to be made in order to stably run Masonite on all operating systems. Masonite defaults to running as the WSGI server with Waitress. This was chosen because waitress is a WSGI server that runs on both Windows and Mac and is powerful enough to run most application during development.

As your application is entering the deployment phase you should look away from running the server with Waitress and look to more production ready options such as uWSGI and Gunicorn. Gunicorn has been tested to run faster than uWSGI both using their default options but uWSGI may be able to be tweaked in a way that is faster dependent on your application.

Because Masonite is simply a WSGI application, any deployment tutorials you research should simply be how to setup a WSGI application on the platform you are trying to deploy to.

So remember, Waitress is great for development but a better WSGI server like Gunicorn should be used when deploying to production.

Masonite Logging

Introduction

Logging is a pretty crucial part of any application. The Masonite Logging package allows you to see errors your application is throwing as well as allow you to log your own messages in several different alert levels.

Masonite Logging currently contains the ability to log to a file, syslog and slack.

Installation

To get the Masonite Logging package up and running on your machine you must first install the package:

$ pip install masonite-logging

And then add the Service Provider to your application:

from masonite.logging.providers import LoggingProvider
# ...
PROVIDERS = [
    # ..

    # Third Party Providers
    LoggingProvider,
    #..
]

You can then publish the provider which will create your config/logging.py configuration file:

$ craft publish LoggingProvider

Logging Exceptions

The Masonite Logging package will automatically register an exception listener with your Masonite application and log any exceptions your application encounters with the corresponding channel.

Channels

The Masonite Logging package uses the concept of channels and drivers. A channel internally is used to create and pass various information to instantiate the driver. At a higher level, you will mainly be working with channels.

Out of the box there are several different channels: single, stack, daily, slack, syslog and terminal. Each channel will handle logging messages in it's own way.

Single Channel

The single channel will put all information in a "single" file. This file can be specified in your config/logging.py file in the path option:

'single': {
    # ...
    'path': 'storage/logs/single.log'
},

Daily Channel

The daily channel is similiar to the single channel except this will create a new log file based on the current day. So this will create log files like 10-23-2019.log and 10-24-2019.log.

The path to set here needs to be a directory instead of a path to a file:

'single': {
    # ...
    'path': 'storage/logs'
},

Slack Channel

The Slack channel will send messages directly to your Slack channel so you or your team can act quickly and be alerted directly of any messages logged.

You'll need to generate a Slack application token. You can add it to your .env file:

SLACK_TOKEN=xoxp-35992....

You then need to set a few options in your config file if you need to change any default settings like the user, the icon emoji,

'slack': {
    # ...
    'channel': '#bot',
    'emoji': ':warning:',
    'username': 'Logging Bot',
    'token': env('SLACK_TOKEN', None),
    # ...
}

These options are up to you.

Terminal Channel

The terminal channel will simply output errors to the terminal. This is handy for debugging or in addition to other channels when using the stack channel.

Stack Channel

The stack channel is useful when you need to combine several channels together. Maybe you want it to log to both a daily channel file as well as send a message to your slack group. You can do this easily by specifying the channels within your stack channel options.

'stack': {
    # ...
    'channels': ['daily', 'slack']
},

You can have as many channels as you want here.

Syslog

The syslog channel will tie directly into your system level logging software. Each operating system has its own type of system monitoring.

You'll need to tie into your system socket path. This can be different per operating system and machine so find yours and put that socket path in the config options:

'syslog': {
    # ...
    'path': '/var/run/syslog',
}

Log Levels

Log levels are a hierarchy of log levels that you can specify on your channels. The hierarchy in order of most important to least important are:

emergency
alert
critical
error
warning
notice
info
debug

Each channel can have its own minimum log level. The log message will only continue if it is greater than or equal to the minimum log level on that channel.

For example, if we have a configuration like this on the daily channel:

'daily': {
    'level': 'info',
    'path': 'storage/logs'
},

This will only send messages to this channel if the log level is notice or above. It will ignore all debug level log messages.

Writing Log Messages

You can of course write your own log messages. You can resolve the logger class from the container and use a method equal to the log level you want to write the message for.

from masonite.logging import Logger

def show(self, logger: Logger):
    logger.debug('message')
    logger.info('message')
    logger.notice('message')
    logger.warning('message')
    # ...

Write to a specific channel

You can easily switch channels by using the channel method:

from masonite.logging import Logger

def show(self, logger: Logger):
    logger.channel('slack').debug('message')
    # ...

Timezones

By default, Masonite Logging will record all times in the UTC timezone but in the event you want to switch timezones, you can do so in your configuration file:

CHANNELS = {
    'timezone': 'America/New_York',
    'single': {
        # ...
    },

All timestamps associated with logging will now use the correct timezone.

Drivers

Introduction

Masonite comes with several drivers that all work great out of the box and in development. Some drivers are better than others in production and some cannot be used in production because of how servers are setup.

Upload Drivers

For example, the caching or upload drivers should not store anything on the server itself if you are deploying onto an ephemeral deployment platform like Heroku. In these cases you should use a caching driver like Redis and an upload driver like Amazon S3.

Session Driver

The session driver has two options out of the box: memory and cookie. The cookie driver will store session data such as success messages, user data or anything else you store with Session.store().

The memory driver will store all session data in a giant dictionary while the server is running and store all data under the IP address. The data will completely lost when the server stops running. The memory driver is great for development and instances where you need to test features without always deleting cookie data.

You can imagine that if you have the memory driver set and 10,000 users then thats 10,000 dictionary keys each containing several values of session data.

For production, this setting should be set to cookie to maximize server performance and server resources.

Hash ID's

Introduction

The Hash ID Masonite essential feature is design for you to easily be able to screen your ID's in your URL's while being able to automatically decode them before they reach your controller method.

For example we can encode a value like 10 and get back l9avmeG. When we pass this value into our endpoint like /dashboard/user/l9avmeG, we get back the correct value in our controller:

from masonite.request import Request

class SomeController:
    
    # Route: /dashboard/user/@id
    # URL: /dashboard/user/l9avmeG
    def show(self, request: Request):
        request.input('id') #== 10

This is very useful for hiding primary ID's and this package makes it very simple to do so.

Installation

This package requires:

  • Masonite 2.0+

You can install the required dependencies for the Hash ID feature by running:

$ pip install masonite-essentials[hashids]

Configuration

Once you have it installed you can then add a middleware and a Service Provider

Middleware

You can add the middleware in your config/middleware.py file:

...
from masonite.contrib.essentials.middleware import HashIDMiddleware
...
HTTP_MIDDLEWARE = [
    ...
    MaintenanceModeMiddleware,
    HashIDMiddleware,
]

Service Provider

You also need to add the Service Provider as well:

from masonite.contrib.essentials.providers import HashIDProvider

PROVIDERS = [
    # Framework Providers
    AppProvider,
    SessionProvider,
    ..
    ..
    # Third Party Providers
    HashIDProvider,
    ..
    ..
]

Great. Once you do those 2 things you are ready to get started.

Usage

Views

In your views you can use the Hash ID template helper which was added when you added the Service Provider. We can use this like so:

{% for user in users %}
    <a href="/dashboard/user/{{ hashid(user.id) }}">User</a>
    <!-- Generates /dashboard/user/l9avmeG -->
{% endfor %}

Controllers

This feature is designed to be transparent. When the middleware method runs it will check all the request inputs and try to decode them. It will insert the decoded values back into the request input and ignore anything it could not correctly decode.

You can get the inputs like normal:

from masonite.request import Request

class SomeController:
    
    # Route: /dashboard/user/@id
    # URL: /dashboard/user/l9avmeG
    def show(self, request: Request):
        request.param('id') #== 10

Inputs

This also works for inputs so if you have an endpoint like /dashboard/user?id=l9avmeG then you can still get the correct value by using input().

from masonite.request import Request

class SomeController:
    
    # Route: /dashboard/user
    # URL: /dashboard/user?id=l9avmeG
    def show(self, request: Request):
        request.input('id') #== 10

Encoding

You can also encode values yourself by importing the essentials helper:

from masonite.request import Request
from masonite.contrib.essentials.helpers import hashid

class SomeController:
    def show(self):
        hashid(10) #== l9avmeG

Decoding

You have a few options for decoding. You can decode a normal string that you previously encoded by passing in the decode keyword.

from masonite.contrib.essentials.helpers import hashid
​
class SomeController:
    def show(self):
        hashid('l9avmeG', decode=True) #== l9avmeG

You can also pass in a dictionary of values to decode. This helper will try to decode each one and insert the correct value. It will skip the ones it cannot decode.

from masonite.contrib.essentials.helpers import hashid
​
class SomeController:
    def show(self):
        values = {'id': 'l9avmeG': 'name': 'Joe'}
        hashid(values, decode=True) #== {'id': '10': 'name': 'Joe'}

This is actually what is happening under the hood of the feature.

Masonite Billing

Requires

  • Masonite 2.0.0+

Installation

Installing Masonite Billing is simple. We just need a new configuration file, 2 new migrations and extend our User model.

Pip Install

$ pip install masonite-billing

Add the Service Provider

Simply add the Masonite Billing Service Provider to your providers list:

from billing.providers import BillingProvider
​
PROVIDERS = [
    ...
​
    # Third Party Providers
    BillingProvider,

    # Application Providers
    ...
]

This will add a new install:billing command to craft. Just run:

$ craft install:billing

This will create a new configuration file in config/billing.py

Configuration File

All billing information will be located in the config/billing.py file and has 2 important constants:

...
​
DRIVER = 'stripe'
​
...
​
DRIVERS = {
    'stripe': {
        'client': os.getenv('STRIPE_CLIENT'),
        'secret': os.getenv('STRIPE_SECRET'),
        'currency': 'usd',
    }
}

The DRIVER is the processor that Masonite billing will use and the DRIVERS constant is the configuration setting for the driver.

Although there is the DRIVER constant, Masonite Billing currently only supports Stripe. Other drivers like Braintree will be supported in later releases which should be backwards compatible.

Migrations

We'll need to create 2 new migrations: one to add columns to the users table and one migration to create a new subscriptions table. Just create these migration files with craft and copy and paste the migration into those files and migrate them.

Let's first add 2 new columns to our users table.

$ craft migration add_subscription_info_to_users --table users

Now just add this column to the migration file:

with self.schema.table('users') as table:
    table.string('customer_id').nullable()
    table.string('plan_id').nullable()

Now let's add a new subscriptions table.

$ craft migration create_subscriptions_table --create subscriptions
with self.schema.create('subscriptions') as table:
    table.increments('id')
    table.integer('user_id').unsigned()
    table.string('plan')
    table.string('plan_id')
    table.string('plan_name')
    table.timestamp('trial_ends_at').nullable()
    table.timestamp('ends_at').nullable()
    table.timestamps()

Now just migrate the new migrations:

$ craft migrate

Stripe Authentication Keys

Just add your Stripe public and secret keys to your .env file:

STRIPE_CLIENT=pk_Njsd993hdc...
STRIPE_SECRET=sk_sjs8yd78H8...

Billable Model

Masonite Billing consists of a model that should be inherited by whatever model you want to add subscription billing information to. In this example here, we will focus on adding the billing integration to our User model.

from config.database import Model
from billing.models.Billable import Billable
​
class User(Model, Billable):
    __fillable__ = ['name', 'email', 'password']
    __auth__ = 'email'

Once that is added we will now have a plethora of methods we can use to subscribe and charge our user.

Read more about how to handle subscription and payment information in the Usage documentation.

Getting Started

Below you will notice we are using a tok_amex token, you may use this token for testing purposes but this token in production should be the token returned when processing your stripe form.

It's also important to note that the subscription records in your database are never deleted but are updated instead. So canceling a subscription does not delete that subscription from your database but only sets when the subscription ended. This is good if you want to dump the data into an email campaign tool to try and get back any lost customers.

Subscribing to Plans

To subscribe a user to plans, we can use the subscribe method like so:

user.subscribe('masonite-test', 'tok_amex')

This method retuns a string of the subscription token such as sub_j8sy7dbdns8d7sd..

If you try to subscribe a user to a plan and the plan does not exist in Stripe then Masonite will throw a billing.exceptions.PlanNotFound exception.

Checking Subscriptions

If you want to check if the user is subscribed you have a few options:

You may check if the user is subscribed to any plan:

user.is_subscribed()

or you can check if the user is subscribed to a specific plan:

user.is_subscribed('masonite-test')

You may also check if a user was subscribed but their plan has expired:

user.was_subscribed()

or you can obviously check if the user was subscribed to a specific plan:

user.was_subscribed('masonite-test')

Getting The Plan Name

If you need to get the name of the plan the user is on you can do so using the plan() method:

user.plan() # returns Masonite Test

This will return the name of the plan in Stripe, not the plan ID. For example, our plan ID might be masonite-test but the plan name could be "Awesome Plan."

Trialing

If the plan you are subscribing a user to has a trial set inside Stripe then the user will be automatically enrolled in a trial for that time. We can check if the user is on a trial using the on_trial() method. In our examples here, the masonite-test plan has a 30 day free trial set inside Stripe.

For example:

user.subscribe('masonite-test') # plan has a 30 day trial
user.on_trial() # returns True

We may also want to skip the trial and charge the user immediately for the plan:

user.skip_trial().subscribe('masonite-test') # plan has a 30 day trial
user.on_trial() # returns False

We can also specify the amount of days the user should be on a trial for when subscribing them to a plan:

user.trial(days=4).subscribe('masonite-test') # plan now has a 4 day trial
user.on_trial() # returns True

Swapping Plans

We can swap subscription plans at anytime using the swap() method:

user.subscribe('masonite-test') # plan has a 30 day trial
user.is_subscribed('masonite-test') # returns True
user.on_trial() # returns True
​
user.swap('new-plan') # returns True
​
user.is_subscribed('masonite-test') # returns False
user.is_subscribed('new-plan') # returns True

Canceling

If you want to cancel a user's subscription we can use the cancel() method.

This will cancel the users plan but continue the subscription until the period ends.

user.subscribe('masonite-test')
user.is_subscribed() # returns True
user.cancel() # returns True
user.is_subscribed() # returns True

Notice here that the last is_subscribed() method still returned True even after we canceled. The user will continue the subscription until the period ends. For example if it is a 1 month subscription and the user canceled 1 week in, Masonite Billing will continue the subscription for the remaining 3 weeks.

If you wish to cancel the user immediately we can specify the now=Trueparameter:

user.subscribe('masonite-test')
user.is_subscribed() # returns True
user.cancel(now=True) # returns True
user.is_subscribed() # now returns False

The period between canceling a subscriptions and the period running out can be caught using the user.is_canceled() method:

user.subscribe('masonite-test')
user.cancel() # returns True
user.is_canceled() # returns True

Again this will only return true if the user has an active subscription but has chosen to cancel it. If the subscription is canceled immediately, this will return False.

user.subscribe('masonite-test')
user.cancel(now=True) # returns True
user.is_canceled() # returns False

Resuming Plans

If you don't cancel a plan immediately, there will be a period between when the user canceled and when the plan can be resumed. We can use the resume() method here:

user.subscribe('masonite-test')
​
user.cancel() # returns True
user.is_canceled() # returns True
​
user.resume() # returns True
user.is_canceled() # returns False

Updating Card Information

Sometimes a user will want to switch or update their card information. We can use the card() method and pass a token to it:

user.card('tok_amex') # updates the users card

Charging users

If you want to make one off transactions for customers you can do so easily using the charge() method:

You can charge a card by passing a token:

user.charge(999, token='tok_amex') # charges the users card

You can also charge a customer directly by leaving out the token which will charge whatever card the customer has on file.

user.charge(999) # charges the customer

You can also add a description and metadata (as a dictionary) for the charge:

user.charge(999, description='Charges for Flowers', metadata={'flower_type': 'tulips, roses'})

Coupons

You will first need to setup coupons in Stripe.

Once you setup a coupon you can use coupons on both charges and subscriptions:

user.coupon('black-friday').subscribe('masonite-plan')

You can also pass in an integer to deduct an amount:

user.coupon(500).charge(1000)

This will deduct 5 dollars frin the 10 dollars you are charging the user.

You can also make a coupon for a certain percentage reduction:

user.coupon(.25).charge(1000)

This will deduct 25 percent off.

Webhooks

Webhooks allow your application to interact directly with stripe when certain actions happen such as when a user's subscription has expired. You can use these webhooks to catch any events emitted.

Getting Started

Masonite Billing also allows you to tie into Stripe webhooks. If the subscription is cancelled in Stripe, Stripe will send your server a webhook and Masonite Billing will update the database accordingly.

Be sure to setup the correct url in the the Stripe dashboard inside your Webhook Settings. The url to setup should be http://your-domain.com/stripe/webhook or if you're testing with Ngrok it should be something like http://684b1285.ngrok.io/stripe/webhook.

If you go the Ngrok route be sure to read the Testing With Ngrok section

Webhook Controller

We can simply put the webhook controller in our routes/web.py file just like any other controller but it can point to the packages controller:

...
Post().route('/stripe/webhook', '/billing.controllers.WebhookController@handle')
...

This will send all Stripe traffic to the server and handle any hooks accordingly.

Testing With Ngrok

Most developers use Ngrok for testing incoming webhooks. Ngrok is a freemium HTTP tunneling service that allows extrernal requests to tunnel into your localhost and port environment. It's exellent for testing things like this as well as other things like OAuth.

If you use Ngrok you will get a subdomain like: http://684b1285.ngrok.io . Because this is a subdomain, Masonite needs to know which subdomain to support so you're route will have to be:

...
Post().domain('684b1285').route('/stripe/webhook', '/billing.controllers.WebhookController@handle')
...

in order to catch the Ngrok subdomain. This setup will allow you to send test webhooks from Stripe to your server.

You can read more about subdomains in the Subdomain Routing section of the Routing documentation.

Csrf Protection

External incoming API calls typically will not be able to be CSRF protected because they will not know the specific token connected to a request. We will need to put an exception to the /stripe/webhook route:

class CsrfMiddleware: 
  ...

  exempt = [
    '/stripe/webhook'
  ]

Creating Custom Hooks

Currently the webhook controller only handles when a subscription is canceled but can handle any hook you like. If you need to create a custom hook you can inherit from the WebhookController and add the needed controller methods:

from billing.controllers import WebhookController
​
class CustomWebhookController(WebhookController):
​
    def handle_resource_event(self):
        return 'Webhook Handled'

Routes

You'll also have to specify a new location of your controller which should now be located in your normal controllers directory:

...
Post().route('/stripe/webhook', 'CustomWebhookController@handle'),
...

Events

Events are specific types of webhooks that Stripe will send out when certain actions occur.

You can see a list of stripe events here: https://stripe.com/docs/api#event_types​

You'll notice that we have a handle_resource_event method. The WebhookController will take all incoming webhook events into the handle method and dispatch them to other methods.

How it does this is simply takes the event lets say the charge.dispute.created Stripe event (see the link above for a list of more events) and parses it into a string that looks like:

handle_charge_dispute_created

You'll notice we just replaced the . with _ and prefixed a handle to it. This looks like a method call. If we simply create a method now:

from billing.controllers import WebhookController
​
class CustomWebhookController(WebhookController):
​
    def handle_charge_dispute_created(self):
        return 'Webhook Handled'

This method will be called whenever we receive a webhook for that event. If no method is found for the incoming webhook then the server will return a Webhook Not Supported string which you may see while development testing your Stripe webhooks.

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 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:

$ pip install masonite-cli --user

or

$ pip install --user masonite-cli

I pip installed masonite-cli but running craft doesn't work?

If you ran:

$ pip install masonite-cli --user

and then run:

$ craft

and get something like:

-bash: craft: command not found

then try closing your terminal and reopening it. If that doesn't work then you may be running a pip version connecting to Python 2.7. Try uninstalling it and reinstalling it using pip3:

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

If that does not work then you may have to run sudo:

$ pip3 uninstall masonite-cli
$ sudo pip3 install 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 --upgrade requests

If that doesn't work we can just go back to the lower idna version:

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 and install it and you may need to close your terminal to get it work if you are using a UNIX machine.

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!

Deprecation

Introduction

This page contains all deprecated code that may be removed in future versions of Masonite. These helpers are available to use for the remainder of the 2.1 lifetime but will be removed in either the next version (2.2) or the version after.

About Deprecation

Some code inside Masonite becomes obsolete as new features become available. In order to make maintaining the codebase easier and clutter free, Masonite will start emitting deprecation warnings during minor releases of the current release.

For example if you upgrade from 2.1.20 to 2.1.21, you may start seeing deprecation warnings inside your console. You can safely ignore these warnings and your code will still work fine but may not work when upgrading to the next major version (for example from 2.1 to 2.2).

What to do

When you see these deprecation warnings, you will see a link as well that comes to this page. You should scroll down this page for examples on how to get your code up to date and not in a deprecated state. You should do this as soon as it becomes convenient for you to do so.

Deprecated Code

No code is scheduled for deprecation as of yet. This can change with future versions of Masonite

Service Container
About Managers
About Managers
Service Providers
About Drivers
Orators Schema Builder
Orator Schema Columns
Orator documentation
Masonite API
http://localhost:8000/api/user?token=1234
accepted
active_domain
after_today
before_today
confirmed
contains
date
different
distinct
does_not
email
equals
exists
file
greater_than
image
in_range
ip
is_future
is_list
is_in
is_past
isnt
json
length
less_than
matches
none
numeric
one_of
phone
postal_code
regex
required
required_if
required_with
string
strong
timezone
truthy
uuid
video
when

Creating a Blog

Preface

This section of the documentation will contain various tutorials. These are guides that are designed to take you from beginning to end on building various types of projects with Masonite. We may not explain things in much detail for each section as this part of the documentation is designed to just get you familiar with the inner workings of Masonite.

Since this section of the documentation is designed to just get you up and coding with Masonite, any further explanations that should be presented are inside various "hint blocks." Once you are done with the tutorial or simply want to learn more about a topic it is advised that you go through each hint block and follow the links to dive deeper into the reference documentation which does significantly more explaining.

Hint Blocks

You will see various hint blocks throughout the tutorials. Below are examples of what the various colors represent.

You'll see hint blocks that are green which you should follow if you want to learn more information about the topic currently being discussed.

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

Creating a Blog

In this tutorial we will walk through how to create a blog. We will touch on all the major systems of Masonite and it should give you the confidence to try the more advanced tutorials or build an application yourself.

Routing

Typically your first starting point for your Masonite development flow will be to create a route. All routes are located in routes/web.py and are extremely simple to understand. They consist of a request method and a route method. Routing is simply stating what incoming URI's should direct to which controllers.

For example, to create a GET request route it will look like:

routes/web.py
Get().route('/url', 'Controller@method')

We'll talk more 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's business 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.

routes/web.py
ROUTES = [
    Get().route('/', 'WelcomeController@show').name('welcome'),

    # Blog Routes
    Get().route('/blog', 'BlogController@show')
]

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.

Creating a Controller

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. If this seems weird to you it might be worth a try to see if you like this opinionated layout.

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 directory 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 earlier.

Returning a View

We can return a view from our controller. A view in Masonite are html files or "templates". They are not Python objects themselves like other Python frameworks. Views are what the users will see.

This is important as this is our first introduction to Python's IOC container. We specify in our parameter list that we need a view class and Masonite will inject it for us:

app/http/controllers/BlogController.py
from masonite.view import View 

def show(self, view: View):
    return view.render('blogView')

Notice here we type hinted the View class. This is what Masonite call's "Auto resolving dependency injection". If you don't like the semantics of this there are other ways to "resolve" from the container that you will discover in the reference documentation but for now let's stay with this method of resolving.

Creating Our View

You'll notice now that we are returning the blog view but it does not exist yet.

All views are in the resources/templates directory. We can create a new file called resources/templates/blogView.html or we can use another craft command:

terminal
$ craft view blog

This will create that template we wanted above for us.

We can put some text in this file like:

resources/templates/blogView.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"

Authentication

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 installations of Masonite since it will create controllers routes and views for you.

For our blog, we will need to setup some form of registration 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.

We will observe what was created for us in a bit.

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 but we will assume that you do not. In this case we can just use SQLite.

Now we just need to change a few environment variables so Masonite can create the SQLite 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_CONNECTION=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 by adding sqlite to the DB_CONNECTION variable and whatever you want for your database which will be created for you when you migrate. We will call it blog.db:

.env
DB_CONNECTION=sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=blog.db
DB_USERNAME=root
DB_PASSWORD=root

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 or remove columns 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
Username: demo
Email: demo@email.com
Password: password

Migrations

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

terminal
$ craft migration create_posts_table --create posts

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 something that looks like:

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()

Lets add a title, an author, and a body to our posts tables.

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()

Now we can migrate this migration to create the posts table

terminal
$ craft migrate

Models

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, some models may rely on each other so it is typically better to perform the import inside the relationship like we did above to prevent any possibilities of circular imports.

Designing Our Blog

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 a layout and start creating and updating blog posts.

We will also check if the user is 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 }}

    <input type="name" name="title">
    <textarea name="body"></textarea>
</form>

Notice here we have this strange {{ csrf_field }} 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 }}

        <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 %}

auth() is a globally available function that either returns the current user or returns None.

Static Files

For simplicity sake, we won't be 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 right at the top:

resources/templates/blog.html
<link href="/static/blog.css" rel="stylesheet">
{% if auth() %}
    <form action="/blog/create" method="POST">
        {{ csrf_field }}

        <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 }}

        <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
from masonite.view import View
....
def show(self, view: View): 
    return view.render('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
from masonite.request import Request
...

def store(self, request: 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".

Showing Our Posts

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.

terminal
$ craft view posts
$ craft view single

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.

terminal
$ craft controller Post

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:

app/http/controllers/PostController.py
from masonite.view import View
from app.Post import Post
...

def show(self, view: View):
    posts = Post.all()

    return view.render('posts', {'posts': posts})

Posts Route

We need to add a route for this method:

routes/web.py
Get().route('/posts', 'PostController@show')

Posts View

Our posts view can be very simple:

resources/templates/posts.html
{% for post in posts %}
    {{ post.title }}
    <br>
    {{ post.body }}
    <hr>
{% endfor %}

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:

resources/templates/posts.html
{% for post in posts %}
    {{ post.title }} by {{ post.author.name }}
    <br>
    {{ post.body }}
    <hr>
{% endfor %}

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:

routes/web.py
Get().route('/post/@id', 'PostController@single')

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.

app/http/controllers/PostController.py
from masonite.view import View
from app.Post import Post

...

def single(self, view: View):
    post = Post.find(request().param('id'))

    return view.render('single', {'post': 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:

resources/templates/single.html
{{ post.title }}
<br>
{{ post.body }}
<hr>

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.

Updating and Deleting Posts

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
from masonite.view import View
...
def update(self, view: View):
    post = Post.find(request().param('id'))

    return view.render('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" method="POST">
    {{ csrf_field }}

    <label for="">Title</label>
    <input type="text" name="title" value="{{ post.title }}"><br>

    <label>Body</label>
    <textarea name="body">{{ post.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" method="POST">
    {{ csrf_field }}

    <label for="">Title</label>
    <input type="text" name="title" value="{{ post.title }}"><br>

    <label>Body</label>
    <textarea name="body">{{ post.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!

Masonite Notifications

Introduction

Masonite Notifications can easily add new notification sending semantics for your Masonite project. These notifications could be sending an email or a slack message. This package is designed to be extremely simple and modular so allow new notification abilities to be added to it through third party integrations.

Installation

In order to get started using Masonite Notifications, we first have to pip install it:

$ pip install masonite-notifications

And then add the provider to our PROVIDERS list:

from notifications.providers import NotificationProvider
...

PROVIDERS = [
    ...
    NotificationProvider,
    ...
]

Thats it! Let's see how it works!

Usage

There are a few concepts we'll need to cover so you can fully understand how Notifications work. First we will cover the high level stuff and then slowly work down into the lower level implementations. For the purposes of this documentation, we will walk through how to setup a welcome notification so we can send an email when a user signs up.

Creating a Notification

In order to use it we can create a notification class. We can do this simply with a new craft command.

$ craft notification WelcomeNotification

This will create a notification in the app/notifications directory. Feel free to move it wherever you like though.

This will create a class like so:

''' A WelcomeNotification Notification '''
from notifications import Notifiable

class WelcomeNotification(Notifiable):

    def mail(self):
        pass

Building Our Mail Notification

Let's now walk through how to build a notification so we can send the email to our user.

Since our notification inherits from Notifiable, we have access to a few methods we will use to build the notification. We'll show a final product of what it looks like since it's pretty straight forward but we'll walk through it after:

from notifications import Notifiable
import os


class WelcomeNotification(Notifiable):

    def mail(self):
        return self.subject('New account signup!') \
            .driver('smtp') \
            .panel('GBALeague.com') \
            .heading('You have created a new account!') \
            .line('We greatly value your service!') \
            .line('Attached is an invoice for your recent purchase') \
            .action('Sign Back In', href="http://gbaleague.com") \
            .line('See you soon! Game on!') \
            .view('/notifications/snippets/mail/heading',
                  {'message': 'Welcome To The GBA!'})

Notice here we are calling a few methods such as driver, panel, line, etc. If we send this message it will look like:

Not bad. We can use this logic to easily build up emails into a nice format simply.

Options

Let's walk through the different options to build an email notification and what they do.

Method

Description

Example

.line()

Creates a single line of text like text you would see in a paragraph tag

line('this is a line of text')

.action()

This creates a clickable looking button. The kwargs include href and style. The styles are bootstraps button styles to include default, success, danger, info etc.

.view()

This is the normal view object here so you can pass in any templates and dictionary you need.

.view('mail/header', {'key': 'value'})

.panel()

This creates a grey background header panel.

.panel('Some Header')

.heading()

Creates a header

.heading('Welcome!')

.subject()

The subject of the email

.subject('New Account!')

.dry()

Sets all the necessary fields but does not actually send the email. This is great for testing purposes. This takes no parameters

.dry()

.driver()

The driver you want to use to send the email

.driver('mailgun')

Sending the Notification

Now that we have built our notification above, we can send it in our controller (or anywhere else we like):

from app.notifications.WelcomeNotification import WelcomeNotification
from notifications import Notify
...

def show(self, notify: Notify):
    notify.mail(WelcomeNotification, to='user@gmail.com')

Notice here we simply specified the Notify class in the parameter list and we are able to pass in our awesome new WelcomeNotification into the mail method.

NOTE: The method you should use on the notify class should correspond to the method on the notification class. So for example if we want to execute the slack method on the WelcomeNotification then we would call :

notify.slack(WelcomeNotification)

The method you call should be the same as the method you want to call on the notification class. The Notify class actually doesn't contain any methods but will call the same method on the notification class as you called on the Notify class.

Sending Via

You can also send multiple notifications and notification types with the via and send method like so:

from app.notifications.WelcomeNotification import WelcomeNotification
from notifications import Notify
...

def show(self, notify: Notify):
    notify.via('mail', 'slack').send(WelcomeNotification, to='user@gmail.com')

This is useful for sending mail more dynamically or just sending multiple types of notifications in 1 line.

Queuing the Notification

If you would like to queue the notification then you just need to inherit the ShouldQueue class and it will automatically send your notifications into the queue to be processed later. This is a great way to speed up your application:

from notifications import Notifiable
from masonite.queues import ShouldQueue
import os


class WelcomeNotification(Notifiable, ShouldQueue):

    def mail(self):
        return self.subject('New account signup!') \
            .driver('smtp') \
            .panel('GBALeague.com') \
            .heading('You have created a new account!') \
            .line('We greatly value your service!') \
            .line('Attached is an invoice for your recent purchase') \
            .action('Sign Back In', href="http://gbaleague.com") \
            .line('See you soon! Game on!') \
            .view('/notifications/snippets/mail/heading',
                  {'message': 'Welcome To The GBA!'})

Building Our Slack Notification

Out of the box, Masonite notifications comes with Slack support as well in case we want to send a message to a specific slack group.

NOTE: In order to use this feature fully, you'll need to generate a token from Slack. This token should have at minimum the channels:read, chat:write:bot, chat:write:user and files:write:user permission scopes. If your token does not have these scopes then parts of this feature will not work.

Going back to our WelcomeNotification, we can simply specify a new method called slack.

class WelcomeNotification(Notifiable):

    def mail(self):
        return self.subject('New account signup!') \
            .driver('smtp') \
            .panel('GBALeague.com') \
            .heading('You have created a new account!') \
            .line('We greatly value your service!') \
            .line('Attached is an invoice for your recent purchase') \
            .action('Sign Back In', href="http://gbaleague.com") \
            .line('See you soon! Game on!') \
            .view('/notifications/snippets/mail/heading',
                  {'message': 'Welcome To The GBA!'})

      def slack(self):
          pass

Notice the new slack method at the bottom. we will use this method to build our slack notification. Again we will show you a full example and then run through all the methods:

class WelcomeNotification(Notifiable):

    def mail(self):
        return self.subject('New account signup!') \
            .driver('smtp') \
            .panel('GBALeague.com') \
            .heading('You have created a new account!') \
            .line('We greatly value your service!') \
            .line('Attached is an invoice for your recent purchase') \
            .action('Sign Back In', href="http://gbaleague.com") \
            .line('See you soon! Game on!') \
            .view('/notifications/snippets/mail/heading',
                  {'message': 'Welcome To The GBA!'})

    def slack(self):
        return self.token(os.getenv('BOT')) \
            .text('Masonite Notification: Read The Docs!, https://docs.masoniteproject.com/') \
            .channel('#bot') \
            .as_user('Masonite Bot') \
            .icon(':fire:') \
            .button('Sure!', "https://docs.masoniteproject.com/")

Options

Method

Description

Example

.token()

This is your Slack token that has the correct permission scopes.

.token('xoxp-359926262626-35...')

.text()

The text you want to show in the message

.text('Welcome to Masonite!')

.channel()

The channel you want to broadcast to. If the value you supply starts with a # sign then Notifications will make a POST request with your token to the Slack channel list API and get the channel ID. You can specify the channel ID directly if you don't want this behavior

.channel('#general') .channel('CHSUU862')

.as_user()

The username you want to show as the message

.as_user('Masonite Bot')

.icon()

The icon you want to show. This needs to be a Slack emoji

.icon(':fire:')

.as_current_user()

This sets a boolean value to True on whether the message should show as the currently authenticated user.

.as_current_user()

.without_markdown()

This will not parse any markdown in the message. This is a boolean value and requires no parameters.

.without_markdown()

.dont_unfurl()

This sets a boolean to False on whether the message should show any attachments. Usually slack will show an icon of the website when posting a link. This disables that feature for the current message.

.dont_unfurl()

.as_snippet()

Used to post the current message as a snippet instead of as a normal message. This option has 3 keyword arguments. The file_type, name, and title. This uses a different API endpoint so some previous methods may not be used.

.as_snippet(file_type='python', name='snippet', title='Awesome Snippet')

.comment()

Only used when using the .as_snippet() method. This will set a comment on the snippet.

.comment('Great Snippet')

.button()

Used to create action buttons under a message. This requires text and a url but can also contain style, and confirm

.dry()

Sets all the necessary fields but does not actually send the email. This is great for testing purposes. This takes no parameters

.dry()

Sending a Slack Notification

Now that we have built our notification above, we can send it in our controller (or anywhere else we like):

from app.notifications.WelcomeNotification import WelcomeNotification
from notifications import Notify
...

def show(self, notify: Notify):
    notify.slack(WelcomeNotification)

Notice here we simply specified the Notify class in the parameter list and we are able to pass in our awesome new WelcomeNotification into the slack method.

Building Integrations

The Notifiable class is very modular and you are able to build custom integrations if you like pretty simply. In this section we'll walk through how to create what are called Components.

What are Components?

Components are classes that can be added to a Notification class that extend the behavior of the notification. In fact, the Notifiable class is just a simple abstraction of two different components. Let's look at the signature of the class that we have been inheriting from.

from notifications.components import MailComponent, SlackComponent


class Notifiable(MailComponent, SlackComponent):
    pass

The Component classes are the classes that have our methods we have been using. If you'd like to see the source code on those components you can check them out on GitHub to get a lower level understanding on how they work.

Creating a Component

Let's walk through a bit on how we created our MailComponent by creating a simplified version of it. First let's create a simple class:

class MailComponent:
    pass

Now let's add a line and a subject method to it:

class MailComponent:

    def line(self, message):
        pass

    def subject(self, subject)
        pass

and let's use these two methods to build a template attribute

class MailComponent:

    template = ''

    def line(self, message):
        self.template += template
        return self

    def subject(self, subject)
        self._subject = subject
        return self

Since we returned self we can keep appending onto the notification class like we have been.

The actual MailComponent class is a bit more complex than this but we'll keep this simple for explanatory purposes.

The Fire Method

Whenever we insert the notification into the Notify class:

notify.mail(WelcomeNotification)

This will call the mail method on the notification class (or whatever other method we called on the Notify class).

Once that is returned then it will call the fire_mail method which you will specify in your component.

If you are created a discord notification then you should have a fire_discord method on your component and you will call it using notify.discord(WelcomeNotification).

Since we want to call the mail method on it, we will create a fire_mail method:

class MailComponent:

    template = ''

    def line(self, message):
        self.template += template
        return self

    def subject(self, subject)
        self._subject = subject
        return self

    def fire_mail(self):
        pass

Passing Data With Protected Members

Sometimes you will want to pass in data into the fire_mail method. In order to keep this simple and modular, any keyword arguments you pass into the Notify class will be set on the notification class as a protected member. For example if we have this:

notify.mail(WelcomeNotification, to='admin@site.com')

It will set a _to attribute on the notification class BEFORE we get to the fire method.

So using the example above we will be able to do:

def fire_mail(self):
    self._to # admin@site.com

We can use this behavior to pass in information into the fire_mail method while keeping everything clean.

A practical example is sending the message to a certain user:

notify.mail(WelcomeNotification, to='admin@site.com')
class WelcomeNotification(Notifiable):

    def mail(self):
        return self.line('{0} We greatly value your service!'.format(self._to)) \
            .action('Sign Back In', href="http://gbaleague.com")

Notice here we now have a _to member on our class we can use because we passed it through from our Notify class.

Sending The Mail

Ok so finally we have enough information we need to send the actual email. The fire_method is resolved by the container so we can simply specify what we need to send the email.

Our notification class will look like:

from your.package import MailComponent

class WelcomeNotification(Notifiable, MailComponent):

    def mail(self):
        return self.subject('New account signup!') \
            .line('We greatly value your service!')

Our Notify class will look like:

notify.mail(WelcomeNotification, to='admin@site.com')

and our fire method will look like:

from masonite import Mail

class MailComponent:

    template = ''

    def line(self, message):
        self.template += template
        return self

    def subject(self, subject)
        self._subject = subject
        return self

    def fire_mail(self, mail: Mail):
        mail.to(self._to) \
            .subject(self._subject) \
            .send(self.template)

Remember the _to class attribute that came from the keyword argument in the Notify class.

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.BUGFIX.

This means that a framework version of 1.3.20 may have breaking changes with version 1.4.0. Masonite uses a 6 month major release cycle in order to maintain it's state as a modern Python web framework. Each release is a solid and stable release and upgrading typically takes minimal time.

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.

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 6 month major release cycle. This means that once six months will be a new 2.x release.

Releases are planned to be upgradable from the previous release in 30 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 6 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.

The core repository is uploaded to PyPi under the new release. At this time, everyone could theoretically install the package. Once core is released, craft is updated for any minor changes it may require. Once these two repositories are completed, the masonite repository is updated and finally released. Once this repo is released to the latest version, all future projects will default to the latest version of Masonite.

After that we will bump the version of Masonite in the documentation and verify and update any documentation that is not up to standard.

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.6 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.

Contributing Guide

Contributing Guide

Introduction

Please note we have a code of conduct, please follow it in all your interactions with the project. You can find it in the base of the core project directory.

Getting Started

The framework has three main parts: The "core" repo, the "cookie-cutter" repo and the craft CLI tool.

The MasoniteFramework/cookie-cutter repository is the main repository that will install when creating new projects using the craft new command. This is actually a full Masonite project. When you run craft new it simply reaches out to this GitHub repo, fetches the zip and unzips it on your computer. Not much development will be done in this repository and won't be changed unless something requires changes in the default installation.

The MasoniteFramework/core repository is deprecated and development has been moved into Masoniteframework/masonite. This repository contains all the development of Masonite and contains all the releases for Masonite. If you need to fix a bug or add a new feature then this is the repository to fork.

The MasoniteFramework/craft is deprecated. This was where the craft CLI tool lives that has since been moved into the masonite repository. Masonite 2.2 and below still requires repository so if you need to make a change to craft for Masonite 2.2 then you can do so in this repo. If you need to make a change to craft in Masonite 2.3 and above then do so directly in core.

Getting the Masonite cookie-cutter 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/cookie-cutter repo.

  • Clone that repo into your computer:

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

  • Checkout the current release branch (example: develop)

    • git checkout -b 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 (<feature|fix>-<issue-number>) 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/masonite repo,

  • Clone that repo into your computer:

    • git clone http://github.com/your-username/masonite.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.

This repository has a barebones skeleton of a sample project in order to aid in testing all the features of Masonite against a real project. If you install this as editable by passing the --editable flag then this may break your project because it will override the modules in this package with your application modules.

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.

If the change in Masonite's CLI tool is for 2.3 or above, please make the changes in the MasoniteFramework/craft repo.

  • 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
...

Notice there are no spaces before and after the sentence.

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 ...

Methods and Functions with Dependencies

Most methods will require some dependency or parameters. You must specify them like this:

def route(self, route, output):
    """Load the route into the class. This also looks for the controller and attaches it to the route.

    Arguments:
        route {string} -- This is a URI to attach to the route (/dashboard/user).
        output {string|object} -- Controller to attach to the route.

    Returns:
        self
    """

And if your dependencies are objects it should give the path to the module:

def __init__(self, request: Request, csrf: Csrf, view: View):
    """Initialize the CSRF Middleware

    Arguments:
        request {masonite.request.Request} -- The normal Masonite request class.
        csrf {masonite.auth.Csrf} -- CSRF auth class.
        view {masonite.view.View} -- The normal Masonite view class.

    Returns:
        self
    """
    pass

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()

Pull Request Process

Please read this process carefully to prevent pull requests from being rejected.

  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 or not be added at all. 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 for several minutes.

  2. Ensure any changes are well commented and any configuration files that are added have docstring comments on the variables it's setting. See the comments section above.

  3. Update the MasoniteFramework/docs repo (and the README.md inside MasoniteFramework/masonite repo if applicable) with details of changes to the UI. This includes new environment variables, new file locations, container parameters, new feature explanations etc.

  4. Name your branches in the form of <feature|fix>/<issue-number>. For example if you are doing a bug fix and the issue number is 576 then name your branch fix/576. This will help us locate the branches on our computers at later dates. If it is a new feature name it feature/576.

  5. You must add unit testing for any changes made. Of the three repositories listed above, only the masonite repo require unit testing.

  6. 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 one review from a maintainer or Masonite creator.

Branching

Branching is also important. Depending on what fixes or features you are making you will need to branch from (and back into) the current branch. Branching for Masonite simple:

1) All of Masonite repositories, packages, etc. follow the same basic branching flow. 2) Each repository has: a current release branch, previous release branches, a master branch and a develop branch. 3) The current release branch is what the current release is on. So for example, Masonite is on version 2.3 so the current release branch will be 2.3. 4) Previous release branches are the same thing but instead are just previous versions. So if Masonite is currently on 2.3 then the previous release branches could be 2.0, 2.1, 2.2, etc. 5) The master branch is a staging branch that will eventually be merged into the current release branch. Here is where all non breaking changes will be staged (new non breaking features and bug fixes). 6) The develop branch is a staging branch to the next major version. So for example, Masonite will be on version 2.3. If you have an idea for a feature but it will break the existing feature then you will branch from (and back into) the develop branch. This branch will eventually be merged into 2.4 and become apart of the next major version when that is released.

Examples:

For example, if you want to make a new feature and you know it will not break anything (like adding the ability to queue something) then you will branch from the master branch following the Pull Request flow from above. The PR will be open to the master branch. This feature will then be merged into the current release branch and be released as a new minor version bump (2.3.1).

Bug fixes that do not break anything is the same process as above.

New feature will be the same process but be branched off of the develop branch. You will make your change and then open a pull request to the develop branch. This is a long running branch and will merge once the next major version of Masonite is ready to be released.

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

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, a thriving community and have as little bugs as possible. Below I will explain how to contribute to this project in different ways both including and exluding code contributions.

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.

Create Packages

Every framework needs great packages and we as the maintainers of Masonite can only do so much with coming out with great packages and maintaining the framework at the same time. We look forward to our community coming out with awesome additions to the Masonite ecosystem. If you have any issues then be sure to open in the gitter chatroom on the Github homepage.

White Page

Outline ORM White Paper

The Flow

I will discuss the flow at a high level first and then can talk about each part separately.

There are a few different paths you can start out with. Not everything starts out at the model level. You may use the query builder class directly to build your queries. The query builder class is exactly that: a class to build queries. So you will interact with this class (more on the class later) and it will set things like wheres, limits, selects, etc to the class and then pass all that off to build a query.

The Model

First let's talk about the flow of the Model. The Model is probably what most people will be using the majority of the time. The Model is basically an wrapper entity around a table. So 1 table will likely equal to 1 model. A users table will have a User model and a articles table will have an Article model.

The interesting things about the Model is that its just a shell around the QueryBuilder class. The majority of the time you call something on the Model it's actually just building a query builder class immediately and passing the rest of the call off. This is important to understand:

For example,

user = User
user #== <class User>
user.where('id', 1) #== <masonite.orm.Querybuilder object>

Since it returns a query builder we can simply build up this class and chain on a whole bunch of methods:

user.where('id', 1).where('active', 1) #== <masonite.orm.Querybuilder object>

Finally when we are done building a query we will call a .get() which is basically an executionary command:

user.select('id').where('id', 1).where('active', 1).get() #== <masonite.orm.Collection object>

When you call get, the query builder will pass everything you built up (1 select, 2 where statements) and pass those into a Grammar class. The Grammar class is responsible for looping through the 3 statements and compiling them into a SQL query that will run. So the Grammar class will compile a query that looks like this:

SELECT `id` FROM `users` WHERE `id` = '1' AND `active` = 1

If it needs to build a Qmark query (a query with question marks which will be replaced with query bindings to prevent SQL injection) then it will look like this:

SELECT `id` FROM `users` WHERE `id` = '?' AND `active` = ?

and have 2 query bindings:

(1,1)

Once we get the query we can then pass the query into the connection class which will connect to the MySQL database to send the query.

We will then get back a dictionary from the query and "hydrate" the original model. When you hydrate a model it simply means we set the dictionary result into the model so when we access something like user.name we will get the name of the user. Think of it as loading the dictionary into the class to be used later during accession and setting.

Grammar Classes

Grammar classes are classes which are responsible for the compiling of attributes into a SQL statement. Grammar classes are used for DML statements (select, insert, update and delete). Grammars are not used for DDL statements (create and alter). The SQL statement will then be given back to whatever called it (like the QueryBuilder class) and then passed to the connection class to make the database call and return the result. Again the grammar class is only responsible for compiling the query into a string. Simply taking attributes passed to it and looping through them and compiling them into a query.

The grammar class will be responsible for both SQL and Qmark. Again, SQL looks like this:

SELECT * FROM `users` where `age` = '18'

And Qmark is this:

SELECT * FROM `users` where `age` = '?'

Qmark queries will then be passed off to the connection class with a tuple of bindings like (18,). This helps protect against SQL injection attacks. All queries passed to the connection class should be the qmark query. Compiling SQL is really for debugging purposes while developing. Passing straight SQL into the connection class could leave queries open to SQL injection.

Any values should be able to be qmarked. This is done inside the grammar class by replacing the value with a '?' and then adding the value to the bindings. The grammar class knows it should be qmarked by passing the qmark boolean variable throughout the grammar class.

The grammar class is also really an abstraction as well. All the heavy lifting is done inside the BaseGrammar class. Child classes (like MySQLGrammar and PostgresGrammar, etc) really just contain the formatting of the sql strings.

Currently there are 2 different grammar classes for each of the supported grammars. There is one for normal queries and one for schema queries. They could be 1 big class but the class would be giant and it is hard to maintain a god class like this responsable for everything. It also makes it harder to first build the grammar up for quering (selects, updates, deletes, etc) and then later support schema building.

Almost all SQL is bascially the same but with slightly different formats or placements for where some syntax goes. This is why this structure we use is so powerful and easy to expand or fix later on.

For example, MySQL has this format for select statements with a limit:

SELECT * from `users` LIMIT 1

But Microsoft SQL Server has this:

SELECT TOP 1 * from `users`

Notice the SQL is bascially the same but the limiting statement is in a different spot in the SQL.

We can accomplish this by specifying the general: select, insert, update and delete formats so we can better organize and swap the placement later. We do this by using Python keyword string interpolation. For example let's break down to a more low level way on how we can accomplish this:

Here is the MySQL grammar class select statement structure. I will simplify this for the sake of explanation but just know this also contains the formatting for joins, group by's in the form of {joins}, {group_by} etc:

MySQL:

def select_format(self):
    return "SELECT {columns} FROM {table} {limit}"

Microsoft SQL:

def select_format(self):
    return "SELECT {limit} {columns} FROM {table}"

Simply changing the order in this string will allow us to replace the format of the SQL statement generated. The last step is to change exactly what the word is.

Again, MySQL is LIMIT X and Microsoft is TOP X. We can accomplish this by specifying the differences in their own method. Remember these are all in the subclasses of the grammar class. Mysql is in MySQLGrammar and Microsoft is in MSSQLGrammar

MySQL:

# MySQLGrammar 

def limit_string(self):
  return "LIMIT {limit}"

and Microsoft:

# MSSQLGrammar

def limit_string(self):
  return "TOP {limit}"

Now we have abstracted the differences into their own classes and class methods. Now when we compile the string, everything falls into place. This code snippet is located in the BaseGrammar class (which calls the supported grammar class we built above).

# Everything completely abstracted into it's own class and class methods.
sql = self.select_format().format(
    columns=self.process_columns(),
    table=self.process_table(),
    limit=self.process_limit()
)

Let's remove the abstractions and explode the variables a bit so we can see more low level what it would be doing:

MySQL:

"SELECT {columns} FROM {table} {limit}".format(
    columns="*",
    table="`users`",
    limit="LIMIT 1"
)
#== 'SELECT * FROM `users` LIMIT 1'

Microsoft:

"SELECT {limit} {columns} FROM {table} ".format(
    columns="*",
    table="`users`",
    limit="TOP 1"
)
#== 'SELECT TOP 1 * FROM `users`'

So notice here the abstractions can be changed per each grammar for databases with different SQL structures. You just need to change the response of the string returning methods and the structure of the select_format methods

Format Strings

The child grammar classes have a whole bunch of these statements for getting the smaller things like a table

Most methods in the child grammar classes are actually just these strings.

MySQL tables are in the format of this:

`users`

Postgres and SQLite tables are in the format of this:

"users"

and Microsoft are this:

[users]

So again we have the exact same thing on the grammar class like this:

table = self.table_string().format(table=table)

Which unabstracted looks like this for MySQL:

# MySQL
table = "`{table}`".format(table=table)

and this for Microsoft:

# MSSQL
table = "[{table}]".format(table=table)

There are a whole bunch of these methods in the grammar classes for a whole range of things. Any differences that there can possible be between databases are abstracted into these methods.

Compiling Methods

There are a whole bunch of methods that begin with process_ or _compile_ so let's explain what those are.

Now that all the differences between grammars are abstracted into the child grammar classes, all the heavy listing can be done in the BaseGrammar class which is the parent grammar class and really the engine behind compiling the queries for all grammars.

This BaseGrammar class is responsible for doing the actual compiling in the above section. So this class really just has a bunch of classes like process_wheres, process_selects etc. These are more supporting methods that help process the sql strings for the _compile_ methods.

There are also methods that begin with _compile_. These are for responsable for compiling the actual respective queries. The heart of this class really lies in the _compile_select, _compile_create, _compile_update, _compile_delete methods.

Let's bring back the unabstracted version first:

def _compile_select(self):
    "SELECT {columns} FROM {table} {limit}".format(
        columns="*",
        table="`users`",
        limit="LIMIT 1"
    )
#== 'SELECT * FROM `users` LIMIT 1'

Now let's start abstracting until we get what is really in the class.

And now what that method would really look likes with the supporting _compile methods in place:

def _compile_select(self):
    "SELECT {columns} FROM {table} {wheres} {limit}".format(
        columns=self.process_columns(),
        table=self.process_from(),
        limit=self.process_limit()
        wheres=self.process_wheres
    )

    #== 'SELECT * FROM `users` LIMIT 1'

So notice we have a whole bunch of _compile methods but they are mainly just for supporting the main compiling of the select, create or alter statements.

And now finally what the method actually looks like in the class:

def _compile_select(self):
    self.select_format().format(
        columns=self.process_columns(),
        table=self.process_from(),
        limit=self.process_limit()
        wheres=self.process_wheres
    )
    #== 'SELECT * FROM `users` LIMIT 1'

Models and Query Builder

Models and query builders are really hand in hand. In almost all cases, a single method on the model will pass everything off to the QueryBuilder class immediately.

Just know the Model is really just a small proxy for the QueryBuilder. Most methods on the model simply call the QueryBuilder so we will focus on the QueryBuilder.

The only thing the model class does is contains some small settings like the table name, the attributes after a database call is made (query results) and some other small settings like the connection and grammar to use.

It is important though to know the differences between class (cls) and an object instance. Be sure to read the section below.

Meta Classing

One of the trickier bits of magic we have when it comes to the model is we set a meta class on the Model class (the base class that all of your User and Article models will inherit). What this does is essentially creates a middleware between first calling methods. Since its really hard to do everything while handling different class instantances and class classes it's easier to catch the call and turn it into an instance before moving on.

This is hard to explain but let's see what this really solves:

We COULD just do this with everything:

class User(Model):
    pass

And then perform model calls:

result = User().where('...')

But it doesn't look as clean as:

result = User.where('...')

(Also for backwards compatability with Orator it would be a huge change if we didn't support this).

So if you look at the Model.py class we have a meta class inherited (you'll notice if you look at the file) which actually does a bit of magic and actually instanitates the class before any methods are called. This is similiar to any normal Python hook you can tie into like __getattr__.

This makes handling cls and self much easier. Although there are special use cases where we need to handle cls directly which is why you will see some @classmethod decorators on some model methods.

Pass Through

We mentioned that the model simply constructs a query builder and essentially passes everything off to the query builder class.

The issue though is that when you call something like User.where(..) it will call the where on the User class. Since theres actually no where method on the model class it will hook into the __getattr__ on the model class. From there we catch a bunch of different methods located in the __passthrough__ attribute on the model and pass that right off to the query builder. This is important to understand.

Query Builder

This QueryBuilder class is responsible for building up the query so it will have a whole bunch of attributes on it that will eventually be passed off to the grammar class and compiled to SQL. That SQL will then be passed to the connection class and will do the database call to return the result.

The QueryBuilder class is really the meat and potatoes of the ORM and really needs to be perfect and will have the most features and will take the most time to build out and get right.

For example, when you call where on the model it will pass the info to the query builder and return this QueryBuilder class.

user = User.where('age', 18)
#== <masonite.orm.QueryBuilder object>

All additional calls will be done on THAT query builder object:

user = User.where('age', 18).where('name', 'Joe').limit(1)
#== <masonite.orm.QueryBuilder object x100>

Finally when you call a method like .get() it will return a collection of results.

user = User.where('age', 18).where('name', 'Joe').limit(1).get()
#== <masonite.orm.Collection object x101>

If you call first() it will return a single model:

user = User.where('age', 18).where('name', 'Joe').limit(1).first()
#== <app.User object x100>

So again we use the QueryBuilder to build up a query and then later execute it.

Expression Classes

There are a few different classes which will aid in the compiling of SQL from the grammar class. These really are just various classes with different attributes on them. They are internal only classes made to better compile things inside the BaseGrammar class, since we use things like isinstance checks and attribute conditionals. You will not be using these directly when developing applications. These classes are:

  • QueryExpression - Used for compiling of where statements

  • HavingExpression - Used for the compiling of Having statements

  • JoinExpression - Used for the compiling of Join statements

  • UpdateExpression - Used for the compiling of Update statements.

  • SubSelectExpression - Used for compiling sub selects. Sub selects can be placed inside where statements to make complex where statements more powerful

  • SubGroupExpression- Used to be passed into a callable to be executed on later. This is useful again for sub selects but just a layer of abstraction for callables

These are simply used when building up different parts of a query. When the _compile_wheres, _compile_update and other methods are ran on the grammar class, these just make it more simple to fetch the needed data and are not too generic to make difficult use cases challenging to code for.

How classes interact with eachother

Model -> QueryBuilder

The Model passes off anything set on it directly to the query builder once accessed. All calls after will be based on a new query builder class. All query building will be done on this class.

QueryBuilder -> Grammar

To be more clear, once we are done building the query and then call .get() or .first(), all the wheres, selects, group_by's etc are passed off to the correct grammar class like MySQLGrammar which will then compile down to a SQL string.

QueryBuilder -> Connection

That SQL string returned from the grammar class is then sent to the connection class along with the bindings from the grammar class. We then have a result in the form of a dictionary. We don't want to be working with a bunch of dictionaries though, we want to work with more models.

QueryBuilder Hydrating

The QueryBuilder object when returning the response is also responsible for hydrating your models if a model is passed in. If no model is passed into the initializer then it will just return a dictionary or list. Hydrating is really just a fancy word for filling dummy models with data. We really don't want to work with dictionaries in our project so we take the dictionary response and shove it into a Model and return the model. Now we have a class much more useful than a simple dictionary.

For times we have several results (a list of dictionaries) we simply loop through the list and fill a different model with each dictionary. So if we have a result of 5 results we loop through each one and build up a collection of 5 hydrated models. We do this by calling the .hydrate() method which creates a new instance and hydrates the instance with the dictionary.

Relationships

RELATIONSHIPS ARE STILL A WORK IN PROGRESS AND SUBJECT TO CHANGE

Relationships are a bit magical and uses a lot of internal low level Python magic to get right. We needed to do some Python class management magic to nail the inherently magical nature of the relationship classes. For example we have a relationship like this:

class User:

    @belongs_to('local_key', 'foreign_key')
    def profile(self):
        return Profile

This is innocent enough but we would like when you access something like this:

user = User.find(1)
user.profile.city

BUT we also want to be able to extend the relationship as well:

user = User.find(1)
user.profile().city

so we need to both access the attribute AND call the attribute. Very strange I know. How would we get an attribute accession to:

  • find the correct model in the method

  • build the query

  • Find the correct foreign key's to fetch on

  • return a fully hydrated model ready to go

  • but when you call it simple do the wheres and return the query builder.

For this we do some decorator and attribute accession magic using the __get__ magic method which is called whenever an attribute is accessed. We can then hijack this hook and return whatever we need. In this case, a fully hydrated model or a query builder.

Relationship classes

Its useful to explain the relationship classes.

We have a BaseRelationship class which really just contains all the magic we need for the actual decorator to work.

We then have a BelongsTo relationship (which is imported as belongs_to in the __init__.py file so this is where the name change comes from in the decorator) which has a simple apply_query method with does the query needed to return the connection using the models QueryBuilder. Here we have foreign and owner variables. foreign is the relationship class (In this case, Profile) and owner is the current model (in this case User).

The query is applied and returns a result from the query builder in the form of a dictionary or a list (for one result it will be a dictionary and if multiple are returned it will be a list). Then the normal process takes its course. If a dictionary it will return a hydrated model and if a list is returned it will return a collection of hydrated models.

Schema Class

The Schema class is responsible for the creation and altering of tables so will have a slightly different syntax for building a normal Query Builder class. Here we don't have things like where and limit. Instead of have things in the format of:

CREATE TABLE `table` (
    `name` VARCHAR(255)
)

Classes

So now let's talk about how each class of the 3 primary classes talk to eachother here.

Schema -> Blueprint

The Schema class is responsible for specifying the table and/or the connection to use. It will then will pass that information off to the Blueprint class which really is the same thing as the relationship between Model and QueryBuilder. The Schema class is also responsible for setting either the create or alter modes. This is set if you either use Schema.create('users') or Schema.table('users') respectively.

The Blueprint class is similiar to the QueryBuilder class because both simply build up a bunch of columns to to act on. One is just used for fetching data and the other is used for changing or creating tables.

The Schema class calls the blueprint class as a context manager.

The blueprint class will be built up in this format:

Schema.table('users') as blueprint:
    blueprint.string('name')
    blueprint.integer('age')

Notice we are just building up a blueprint class.

When we start up the blueprint class, if we are creating columns then we will be setting additional attributes on a Table class. If we are updating a table then we will be setting attributes on the TableDiff class.

For example when we call:

Schema.table('users') as blueprint:
    blueprint.string('name')

it is a proxy call to

table.add_column('name', column_type='string')

The blueprint class then builds up the table class.

Blueprint -> Platform

Compiling DDL statements are much more complicated than compiling DML statements so there is an entire class dedicated to compiling DDL statements. The Platform classes are similiar to Grammar classes as they are both used to compile sql.

For example in SQLite there is an extremely limited alter statement. So adding, renaming or modifying columns relies on actually creating temporary tables, migrating the existing table to the temp table, then creating a new table based on the existing and modified schema, then migrating the old columns to the new columns and then finally dropping the temp table. You can see how this is not generic so it requires its own logic.

Because of this, there are Platform classes. SQLitePlatform, MySQLPlatform, etc. These class have a compile_create_sql and compile_alter_sql methods. These methods take a single table class. The same table class the blueprint class built up.

This Table class has methods like added_columns, removed_indexes, etc. We can use these to build up our alter and create statements.

For example, Postgres requires alter statements for adding columns to be ran 1 at a time. So we can't add multiple columns with 1 alter query. So we need to loop through all the Table.added_columns and create multiple alter queries for each column.

Compiling

Finally we need to compile the query which is simply done by doing blueprint.to_sql() which will either build a create or alter query depending on what was originally set by the Schema class before.

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:

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:

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:

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:

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:

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

Added Queues and Jobs

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 2.2

Route Prefixes

Previously you had to append all routes with a / character. This would look something like:

You can now optionally prefix this without a / character:

URL parameters can now optionally be retrieved from the controller definition

Previously we had to do something like:

Now we can optionally get the parameter from the method definition:

Added a storage manager and disk storage drivers

This is used as a wrapper around I/O operations. It will also be a wrapper around the upload drivers and moving files around and other file management type operations

Async driver now can be specified whether to use threading or processing

We can now specify directly in the configuration file whether or not the threading or multiprocessing for the async type operations.

Added new HTTP Verbs

We added 4 new HTTP verbs: HEAD, CONNECT, OPTIONS, TRACE. You import these and use them like normal:

JSON error responses

If the incoming request is a JSON request, Masonite will now return all errors as JSON

Rearranged Drivers into their own folders

This is more of an internal change for Core itself.

Craft serve command defaults to auto-reloading

Before we had to specify that we wanted the server to auto-reload by specifying a -r flag:

Now we can just specify the serve command it will default to auto-reloading:

You can now specify it to NOT auto-reload by passing in 1 of these 2 commands:

Added Accept('*') to drivers

By default you can only upload image files because of security reasons but now you can disable that by doing an accept('*') option:

Added much more view helpers

All Tests are now unittests

We moved from pytest to unittests for test structures.

Added a better way to run database tests

Added a new DatabaseTestcase so we can properly setup and teardown our database. This works for sqlite databases by default to prevent your actual database from being destroyed.

The back view helper now defaults to the current path

Before in templates we had to specify a path to go back to but most of the time we wanted to go back to the current path.

Instead of:

We can now do:

Added a completely new validation library

We built a new validation library from scratch and completely ripped out the old validation code. Any current validation code will need to be updated to the new way.

Auth class does not need the request class.

Previously we needed to pass in the request object to the Auth class like this:

Now we have it a bit cleaner and you can just resolve it and the request class will be injected for you

Completely changed how classes are resolved on the backend

You may not notice anything but now if you bind a class into the container like this:

It will be resolved when you resolve it:

This is why the Auth class no longer needs to accept the request class. Masonite will inject the request class for you when you resolve the class.

This works with all classes and even your custom classes to help manage your application dependencies

Added new register method to the Auth class.

You can now do something like:

Changed all regex compiling to be done before the server starts

Previously, each route's regex was being compiled when Masonite checked for it but we realized this was redundant. So now all route compiling is done before the server starts.

This has given Masonite a bit of a speed boost.

Container Remembering

Masonite now has the ability to remember the previous container bindings for each object. This can speed of resolving your code by 10-15x. This is disabled by default as it is still not clear what kind of issues this can cause.

This is scheduled to be set by default in the next major version of Masonite

Added a new with_errors() method in order to cut down on setting an errors session.

Now instead of doing this:

we can now shorten down the flashing of errors and do:

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 }} 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.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:

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

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:

Now you can switch drivers from the driver itself:

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:

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:

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

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

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:

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:

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:

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:

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.

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:

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:

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.

Masonite 2.3

Preface

Masonite 2.3 brings a lot of code and quality of life improvements. There aren't a lot of major ground breaking changes and depending on how your application is structured, may not require much effort to upgrade.

Below is a list of changes that will be in Masonite 2.3:

Package Changes

Some larger changes include all packages for Masonite use SEMVER versioning while Masonite still using ROMVER as it has been since Masonite started.

Masonite will also not require any packages for you through Masonite requirements and will instead put the requirements in the requirements.txt file in new projects. This will allow packages to release new Majors outside of the major upgrades of Masonite. So we can develop new package improvements faster.

Remove TestSuite Class

The masonite.testing.TestSuite class has been completely removed. This was a class that was obsolete and has been replace by the masonite.testing.TestCase class anyway. The TestSuite class was bascially wrappers around some of the commands that predated the newer testing features.

Removed SassProvider

Recently there has been a good amount of talks about the limitations of compiling sass with the Python libsass provider. Because of these limitations, Masonite 2.3 will remove it completely.

Added Laravel Mix By Default

A Laravel Mix file will now come with new installed applications created by craft.

Added Responsable Class

The Responsable class will be able to be inherited by any class and be returned in the controller. If the class has a get_response() method then the class will be rendered using that method. The View class now acts in this way.

Dropped Support For Python 3.4

Masonite has always supported the previous 4 Python versions. Since the release of Python 3.8 recently, Masonite will now only support Python 3.5, 3.6, 3.7 and 3.8. When Python 3.9 comes out, Python 3.5 will be dropped.

You can still use previous versions of Masonite if you need a previous supported Python version.

Completely Rewrote The Authentication

Masonite has moved away from the Auth class and towards Guard classes. These classes will be responsable for handling everything from getting the user to handling logging in and logging out.

There is also a much better way to register new guards so packages will be able to register their own guards with your app which you can then use or swap on the fly.

Swapped the Auth class for a Guard class

This is a very small change but will save a lot of time when upgrading your application. Now anytime you resolve the Auth class you will get an instance of a Guard class instead. The use of both classes are identical so they should just work as is.

(This swap may be removed in Masonite 2.4+)

Added A New AuthenticationProvider

All the authentication stuff in the previous improvements have been abstracted to their own provider so you will need to a add a new provider to your providers list.

Auth Scaffolding Now Imports Auth Class

The Auth class now contains a new method which returns a list of routes. This cleans up the web.py file nicely when scaffolding.

Removed The Ability For The Container To Hold Modules

The container can not longer have modules bound to it. These should instead be imported.

Added Several New Assertions

Added a few new assertion methods to help chaining methods and keeping tests short and fast. These include assertHasHeader and assertNotHasHeader.

Added Download Class

Added a new download class to make it very easy to force downloads or display files.

This is used like this:

Forcing will make the file download and not forcing will display the file in the browser.

Added Preset Command

You can now run a craft preset command which will generate some .json files and example templates. There is a react, vue and bootstrap preset currently.

Changed How Query Strings Are Parsed

Instead of a url like /?filter[name]=Joe&filter[user]=bob&email=user@email.com

parsing to:

It will now parse into:

Parsing the query to the original way is no longer possible. This also comes with a query_parse helper which you can use to parse a query string the same way Masonite does.

Improved Container Collection

The container has a helpful collect method which allows you to collect all the classes in the container with a certain key or an instance of a class like this:

Will collect everything in the container where the binding key ends with Command.

You can also collect everything that is a subclass:

This will collect everything that is a subclass of Responsable

This has now been expanded to also include instances of. So it will work for objects now and not just classes.

Moved The masonite/ Directory To The src/masonite Directory

This is an internal change mostly and completely transparent to all who install Masonite. This allows installing third party packages into Masonite with the same namespace without namespace conflicts.

Changed The Way The WSGI Server Handles Responses

Masonite now handles the response as bytes. This allows for different classes to handle the response in different ways.

Previously Masonite ultimately converted everything to a string at the end but some things needed to be returned to the WSGI server as bytes (like the new file download feature). So if you need to handle the raw response then you will now expect bytes instead of a string.

Scheduler

The Scheduler has a few changes.

New Methods

There are a few new methods on the tasks you can use like every, every_minute, every_15_minutes, every_30_minutes, every_45_minutes, daily, hourly, weekly, monthly.

These can either be used inside the provider or inside the command to make a more expressive scheduling syntax.

New Scheduling Helper Class

Providers can now inherit the CanSchedule class which gives it access to the new self.call() method (which is used to schedule commands) and self.schedule() method (which is used to schedule jobs).

These are really just helper methods to help bind things to the container more expressively.

New Scheduling

You can now schedule jobs or commands in a new expressive way. In addition to setting the schedule interval as attributes on the Task you can now do it directly in the provider:

There are several other methods that will be documented on release.

Namespace Change

We also changed the namespace from scheduler to masonite.scheduler. So you will need to refactor your imports.

Mailable classes

There are now mailable classes which you can use to wrap some of the logic of building emails around. Instead of doing something like this:

You can now do:

Returning tuples inside controllers changes status codes

Now you can simply return a tuple if you want to change the status code that gets returned.

For example before we had to do something like this:

Now you can simply return a tuple:

Changed how the WSGI server returns responses

Previously we converted the response to a string when the request was finished but this prevented use cases where we wanted to return bytes (like returning an image or PDF). Now the conversion is happens (or doesn't happen) internally before the WSGI server needs to render a response. This results in a slight change in your application.

Masonite CLI is now inside Masonite core.

The CLI tool no longer needs to be installed as the first step. Now the first step would be to install masonite which will give you access to craft. From there you can create a new project.

Changed namespace for Masonite API

Masonite API now uses the from masonite.api. namespace rather than the previous from api.

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 continue on.

You can read more about routes in the documentation

Let's create the BlogController in the next step:

Be sure to learn more about the .

and head over to and fill out the form. You can use whatever name and email you like but for this purpose we will use:

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.

We won't go into much more detail here about different types of relationships but to learn more, read the documentation.

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.

action('Click Me', href="", style="danger")

.button('Sure!', '', style='primary', confirm='Are you sure?')

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.

When contributing to Masonite, please first discuss the change you wish to make via a GitHub issue or in the channel.

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 .

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

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 (SemVer and RomVer).

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 by 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.

This project is not currently in Masonite but is in development. This article is designed to be a learning document to aid in those who wish to contribute to the project. This article will be updated when concepts are added to the ORM project or when concepts change. You can contribute to the project at the

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.

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.

Learn more in the .

Learn more in the .

Learn more in the .

Learn more in the .

Learn more in the .

Learn more in the .

Learn more in the .

In order to learn how to use this you can visit the .

Learn more in the .

The new way is MUCH better. You can read about it in the new .

Learn more in the .

Learn more in the .

Learn more in the .

Learn more in the .

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.

Installation
Routing
Part 2 - Creating Our First Controller
Service Container
localhost:8000/register
Database Migrations here
Database Migrations
ORM
read their documentation
Static Files
Service Container
Service Container
Service Container
Helper Functions
Service Provider
The Craft Command
Masonite Slack
You can read about how the framework flows, works and architectural concepts here
RomVer
Contributor Covenant
http://contributor-covenant.org/version/1/4
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
Masonite ORM Repo
<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'))
def show(self):
    return view('welcome')
def boot(self, View, Request):
    View.share({'request': Request})
Get().domain('test').route('/dashboard', 'DashboardController@show')
Get().module('thirdpary.routes').route('/dashboard', 'DashboardController@show')
Get('/some/url')
Get('some/url')
def show(self, view: View, request: Request):
    user = User.find(request.param('user_id'))
    return view.render('some.template', {'user': user})
def show(self, user_id, view: View):
    user = User.find(user_id)
    return view.render('some.template', {'user': user})
from masonite.routes import Connect, Trace
ROUTES = [
    Connect('..'),
    Trace('..'),
]
{
  "error": {
    "exception": "Invalid response type of <class 'set'>",
    "status": 500,
    "stacktrace": [
        "/Users/joseph/Programming/core/bootstrap/start.py line 38 in app",
        "/Users/joseph/Programming/core/masonite/app.py line 149 in resolve",
        "/Users/joseph/Programming/core/masonite/providers/RouteProvider.py line 92 in boot",
        "/Users/joseph/Programming/core/masonite/response.py line 105 in view"
    ]
  }
}
$ craft serve -r
$ craft serve
$ craft serve -d
$ craft serve --dont-reload
def show(self, upload: Upload):
    upload.accept('*').store(request.input('file'))
<form ..>
    {{ back(request().path) }}
</form>
<form ..>
    {{ back() }}
</form>
from masonite.auth import Auth
from masonite.request import Request

def show(self, request: Request):
    Auth(request).login(..)
from masonite.auth import Auth
from masonite.request import Request

def show(self, request: Request, auth: Auth):
    auth.login(..)
from masonite.auth import Auth

def register(self):
    self.app.bind('Auth', Auth)
from masonite.auth import Auth

def show(self, auth: Auth):
    auth.login(..)
from masonite.auth import Auth

def show(self, auth: Auth):
    auth.register({
        'name': 'Joe',
        'email': 'joe@email.com',
        'password': 'secret'
    })
from masonite.request import Request
from masonite.validation import Validator

def show(self, request: Request, validate: Validator):
    errors = request.validate(
      validate.required('user')
    )

    if errors:
      request.session.flash('errors', errors)
      return request.back()
from masonite.request import Request
from masonite.validation import Validator

def show(self, request: Request, validate: Validator):
    errors = request.validate(
      validate.required('user')
    )

    if errors:
      return request.back().with_errors(errors)
request().cookie('key', 'value', http_only=False)
pip install masonite-cli==2.0 --user
def show(self, Upload, UploadManager):
    Upload.store(...) # default driver
    UploadManager.driver('s3').store(...) # switched drivers
def show(self, Upload):
    Upload.store(...) # default driver
    Upload.driver('s3').store(...) # switched drivers
ROUTES = [
    ...
    # Old Syntax:
    Get().route('/url/here', 'Controller@show').module('billing.controllers')

    # New Syntax:
    Get().route('/url/here', '/billing.controllers.Controller@show')
    ...
]
$ craft controller DashboardController
$ craft controller Dashboard
$ craft controller Dashboard -e
craft controller Dashboard -r
def show(self):
    return view('/masonite/views/index')
routes/web.py
from masonite.helpers.routes import get, group
ROUTES = [
    get('/home', ...)
    group('/dashboard', [
        get('/user', ...)
        get('/user/1')
    ])
]
from masonite.request import Request

def show(self, Upload, request: Request):
    pass
from masonite.request import Request

def show(self, Upload, request: Request, Broadcast):
    pass
from masonite.contracts.UploadContract import UploadContract

def show(self, upload: UploadContract):
    upload.store(...)
app.collect('Sentry*Hook')
from masonite.response import Download

def show(self):
    return Download('/path/to/file', force=True)
{
    "filter[name]": "Joe",
    "filter[user]": "Bob",
    "email": "user@email.com"
}
{
    "email": "user@email.com",
    "filter": {
        "name": "Joe",
        "user": "bob"
    }
}
app.collect('*Command')
from masonite.response import Responsable

app.collect(Responsable)
def register(self):
    self.call('your:command --flag').daily().at('9:00')
    self.call('your:command --flag').every_minute()
    self.schedule(YourJob()).every('3 days')
mail.to('user@example.com').reply_to('admin@me.com').template('emails.register').subject(..).send()
from app.mailables import RegistrationMailable
mail.mailable(RegistrationMailable()).send()
def show(self, response: Response):
    return response.json({'error': 'unauthenticated'}, status=401)
def show(self):
    return {'error': 'unauthenticated'}, 401

Masonite 2.1

Introduction

Masonite 2.1 introduces a few new changes that are designed to correct course for the 2.x family and ensure we can go far into the 2.x family without having to make huge breaking changes. It was questionable whether we should break from the 2.x family and start a new 3.x line. The biggest question was removing (actually disabling) the ability to resolve parameters and go with the more favorable annotation resolving. That could have made Masonite a 3.x line but we have ultimately decided to go with the 2.1 as a course correction. Below you will find all changes that went into making 2.1 awesome. Nearly all of these changes are breaking changes.

All classes in core now have docstrings

It is much easier to contribute to Masonite now since nearly classes have awesome docstrings explaining what they do, their dependencies and what they return.

Auto Resolving Parameters

We have completely removed parameter resolving. We can no longer resolve like this:

def show(self, Request):
    return Request.redirect('/')

in favor of the more explicit:

from masonite.request import Request

def show(self, request: Request):
    return request.redirect('/')

This is a bit of a big change and will be most of the time spent on refactoring your application to upgrade to Masonite 2.1. If you already used the more explicit version then you won't have to worry about this change. It is still possible to resolve parameters by passing that keyword argument to your container to activate that feature:

container = App(resolve_parameters=True)

This should help with upgrading from 2.0 to 2.1 until you have refactored your application. Then you should deactivate this keyword argument so you can be in line with future 2.x releases.

You can choose to keep it activated if that is how you want to create applications but it won't be officially supported by packages, future releases or in the documentation.

Class Middleware

All middleware are now classes:

HTTP_MIDDLEWARE = [
    LoadUserMiddleware,
    CsrfMiddleware,
    ResponseMiddleware,
]

this is different from the previous string based middleware

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

Removed the Payload Input

Previously when getting an incoming JSON response, we had to get the values via the payload input like so:

from masonite.request import Request

def show(self, request: Request):
    return request.input('payload')['id']

which was kind of strange in hindsight. Now we can just straight up use the input:

from masonite.request import Request

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

Again this is only a change for incoming JSON responses. Normal form inputs remain the same.

Removed the facades module.

Previously we had a facades module but it was being unused and we didn't see a future for this module so we moved the only class in this module to it's own class. All instances of:

from masonite.facades.Auth import Auth

now become:

from masonite.auth import Auth

Provider Refactoring

Route Provider

Moved parameter parsing into if statement

We also noticed that for some reason we were parsing parameters before we found routes but we only ever needed those parameters inside our routes so we were parsing them whether we found a route or not. We moved the parsing of parameters into the if statement that executes when a route is found.

When we say "parsing route parameters" we mean the logic required to parse this:

/dashboard/@user/@id

into a usable form to use on the request class this:

from masonite.request import Request

def show(self, request: Request):
    request.param('user')
    request.param('id')

StartResponse Provider

This provider has been completely removed for the more recommended ResponseMiddleware which will need to be added to your HTTP middleware list:

from masonite.middleware import ResponseMiddleware
..
HTTP_MIDDLEWARE=[
    ...
    ResponseMiddleware,
]

Moved parameter parsing into if statement

We also noticed that for some reason we were parsing parameters before we found routes but we only ever needed those parameters inside our routes so we were parsing them whether we found a route or not. We moved the parsing of parameters into the if statement that executes when a route is found.

When we say "parsing route parameters" we mean the logic required to parse this:

/dashboard/@user/@id

into a usable form to use on the request class this:

from masonite.request import Request

def show(self, request: Request):
    request.param('user')
    request.param('id')

StartResponse Provider

This provider has been completely removed for the more recommended ResponseMiddleware which will need to be added to your HTTP middleware list:

from masonite.middleware import ResponseMiddleware
..
HTTP_MIDDLEWARE=[
    ...
    ResponseMiddleware,
]

Moved parameter parsing into if statement

We also noticed that for some reason we were parsing parameters before we found routes but we only ever needed those parameters inside our routes so we were parsing them whether we found a route or not. We moved the parsing of parameters into the if statement that executes when a route is found.

When we say "parsing route parameters" we mean the logic required to parse this:

/dashboard/@user/@id

into a usable form to use on the request class this:

from masonite.request import Request

def show(self, request: Request):
    request.param('user')
    request.param('id')

Added ability use dot notation for views

You can now optionally use . instead of / in your views:

def show(self, view: View):
    return view.render('dashboard.user.show')

Moved the CsrfMiddleware into core and extended it

We moved the CSRF middleware completely into the core framework and allow developers to extend from it now. This will allow us to fix any security bugs that are apart of the CSRF feature.

You may see this pattern a lot in the future which is only extending classes from the core framework so we can hot fix things much better.

Completely cleaned the project

Masonite now has a plethora of docstrings on each and every class by default to really give the developer an understanding about what each default class is actually doing and what it is dependent on.

Masonite is also much more PEP 8 compliant. We removed all instances of triple single quotes: ''' for the more preferred and PEP 8 compliant double quotes """ for docstrings.

We also cleaned a lot of the classes generated by the auth command since those were pretty ugly.

Removed Helper functions by default

We also removed all instances of helper functions by default since it was confusing developers and was throwing red squiggly marks for text editors. They are still available to be used but they will not be known to developers unless they discover them in the documentation. Now all default code explicitly resolves via the container and helper functions can be used on the developers own terms.

Helper functions are still available but you will need to use them on your own terms.

Added seeds by default

Now every application has a basic seeding structure setup which is the same as if running the craft seed command. This is to promote more use of this awesome feature which can be used in migration files for quick seeding of databases for development.

Added code to __init__.py file in migrations and seeds

We were previously not able to import code into our migration files or database seeders because the command line tool would not pick up our current working directory to import classes into. Now the migrations module and seeds module have 3 lines of code:

import os
import sys
sys.path.append(os.getcwd())

this helpers when running the command line to import code into these modules.

Route Printing

In development you would see a message like:

GET Route: /dashboard

When you hit a route in development mode. Well you would also hit it in production mode too since that was never turned off. Although this is likely fine, it would slow down the framework significantly under load since it takes a bit of resources to print something that didn't need to be printed. This enables a bit of a performance boost.

Added migrate:status Command

This command gets the statuses of all migrations in all directories. To include third party migration directories that are added to your project.

Added simple container bindings

Sometimes you do not need to bind an object to any key, you just want the object in the container. For this you can now do simple bindings like this:

app.simple(Obj())

Added a new global mail helper

This new mail helper can be used globally which points to the default mail driver:

def show(self):
    mail_helper().to(..)

Removed the need for |safe filters on built in template helpers.

We no longer need to do:

{{ csrf_field|safe }}

We can now simply do:

{{ csrf_field }}

Improved setting status codes

Previously we had to specify the status code as a string:

def show(self, request: Request):
    request.status('500 Internal Server Error')

in order for these to be used properly. Now we can just specify the status code:

def show(self, request: Request):
    request.status(500)

Added several new methods to service providers

There is quite a bit of things to remember when binding various things into the container. For example when binding commands, the key needs to be postfixed with Command like ModelCommand. Now we can do things like:

def register(self):
    self.commands(Command1(), Command2())

Along with this there are several other methods to help you bind things into the container without having to remember all the special rules involved, if any.

Added View Routes

We now have View Routes on all instances of the normal HTTP classes:

Get().view('/url', 'some/template', {'key': 'value'})

Renamed cache_exists to exists

We previously used this method on the Cache class like so:

def show(self, Cache):
    Cache.cache_exists('key')

Now we removed the cache_ prefix and it is just:

from masonite import Cache

def show(self, cache: Cache):
    cache.exists('key')

Added without method to request class

We can now use the .without() method on the request class which returns all inputs except the ones specified:

def show(self, request: Request):
    request.without('key1', 'key2')

Added port to database

Previously the port was missing from the database configuration settings. This was fine when using the default connection but did not work unless added to the config.

Added ability to use a dictionary for setting headers.

Instead of doing something like:

def show(self, request: Request):
    request.header('key1', 'value1')
    request.header('key2', 'value2')

We can now use a dictionary:

def show(self, request: Request):
    request.header({
        'key1': 'value1',
        'key2': 'value2'
    })

Added a new Match route

We can now specify a route with multiple HTTP methods. This can be done like so:

from masonite.routes import Match

Match(['GET', 'POST']).route('/url', 'SomeController@show')

Added Masonite Events into core

Core can now emit events that can be listened to through the container.

Added ability to set email verification

Now you can setup a way to send email verifications into your user signup workflow simply but inherting a class to your User model.

Request redirection set status codes

Now all redirections set the status code implicitly instead of explicitly needing to set them.

Added craft middleware command

Now you can use craft middleware MiddlewareName in order to scaffold middleware like other classes.

View can use dot notation

All views can optionally use dot notation instead of foward slashes:

return view.render('some/template/here')

is the same as:

return view.render('some.template.here')

Added Swap to container

We can now do container swapping which is swapping out a class when it is resolved. In other words we may want to change what objects are returned when certain objects are resolved. These objects do not have to be in the container in the first place.

Added a new env function

You can now use a env function to automatically type cast your environment variables turning a numeric into an int:

from masonite import env

env('DB_PORT', '5432') #== 5432 (int)

Added ability to resolve with paramaters at the same time

You can now resolve from a container with a parameter list in addition to custom parameters.

Added password reset to auth command

In addition to all the awesome things that craft auth generates, we now generate password reset views and controllers as well for you

Route Compiler

Fixed an issue where custom route compilers was not working well with request parameters

Added Database Seeders

All new 2.1 projects have a seeder setup so you can quickly make some mock users to start off your application. All users have a randomly generated email and the password of "secret".

You can run seeders by running:

$ craft seed:run

Made HTTP Prefix to None by Default

When setting headers we had to set the http_prefix to None more times then not. So it is set by default.

This:

def show(self, request: Request):
    request.header('Content-Type', 'application/xml', http_prefix=None)

can change to:

def show(self, request: Request):
    request.header('Content-Type', 'application/xml')

Getting a header returns blank string rather than None

Originally the code:

def show(self, request: Request):
    request.header('Content-Type') #== ''

would return None if there was no header. Now this returns a blank string.

Added Maintenance Mode

There is now an up and down command so you can put that in your application in a maintenance state via craft commands:

$ craft down
$ craft up

There is also a new MaintenanceModeMiddleware:

from masonite.middleware import MaintenanceModeMiddleware

HTTP_MIDDLEWARE = [
    ...
    MaintenanceModeMiddleware,
    ...
]

Removed Store Prepend method

We removed the store_prepend() method on the upload drivers for the filename keyword arg on the store method.

So this:

upload.store_prepend('random-string', request.input('file'))

now becomes:

upload.store(request.input('file'), filename='random-string')

Masonite 2.2 to 2.3

Preface

Welcome to Masonite 2.3! In this guide we will walk you through how to upgrade your Masonite application from version 2.2 to version 2.3.

In this guide we will only be discussing the breaking changes and won't talk about how to refactor your application to use the awesome new features that 2.3 provides. For that information you can check the Whats New in 2.3 documentation to checkout the new features and how to refactor.

We'll walk through both Masonite upgrades and breaking packages as well

Masonite

Pip uninstall masonite-cli

Craft is now a part of Masonite core so you can uninstall the masonite-cli tool. You now no longer need to use that as a package.

This is a little weird but we'll get craft back when we install Masonite 2.3

$ pip uninstall masonite-cli

Upgrade Your Masonite and CLI Version

Next, we can upgrade your Masonite version. Depending on your dependancy manager it will look something like this:

Change it from this:

masonite>=2.2,<2.3

to

masonite>=2.3,<2.4

Go ahead and install masonite now:

pip install "masonite>=2.3,<2.4"

Change Server Response

Masonite changed the way the response is generated internally so you will need to modify how the response is retrieved internally. To do this you can go to your bootstrap/start.py file and scroll down to the bottom.

Change it from this:

return iter([bytes(container.make('Response'), 'utf-8')])

to this:

return iter([container.make('Response')])

This will allow Masonite to better handle responses. Instead of converting everything to a string like the first snippet we can now return bytes. This is useful for returning images and documents.

Package Requirements

Previously Masonite used several packages and required them by default to make setting everything up easier. This slows down package development because now any breaking upgrades for a package like Masonite Validation requires waiting for the the next major upgrade to make new breaking features and improve the package.

Now Masonite no longer requires these packages by default and requires you as the developer to handle the versioning of them. This allows for more rapid development of some of Masonite packages.

Masonite packages also now use SEMVER versioning. This is in the format of MAJOR.MINOR.PATCH. Here are the required versions you will need for Masonite 2.3:

  • masonite-validation>=3.0.0

  • masonite-scheduler>=3.0.0

These are the only packages that came with Masonite so you will need to now manage the dependencies on your own. It's much better this way.

Authentication has been completely refactored

Masonite now uses a concept called guards so you will need a quick crash course on guards. Guards are simply logic related to logging in, registering, and retrieving users. For example we may have a web guard which handles users from a web perspective. So registering, logging in and getting a user from a database and browser cookies.

We may also have another guard like api which handles users via a JWT token or logs in users against the API itself.

Guards are not very hard to understand and are actually unnoticable unless you need them.

In order for the guards to work properly you need to change your config/auth.py file to use the newer configuration settings.

You'll need to change your settings from this:

AUTH={
    'driver': env('AUTH_DRIVER', 'cookie'),
    'model': User,
}

to this:

AUTH = {
    'defaults': {
        'guard': 'web'
    },
    'guards': {
        'web': {
            'driver': 'cookie',
            'model': User,
            'drivers': { # 'cookie', 'jwt'
                'jwt': {
                    'reauthentication': True,
                    'lifetime': '5 minutes'
                }
            }
        },
    }
}

Add The Authentication Provider

To manage the guards (and register new guards) there is the new AuthenticationProvider that needs to be added to your providers list.

from masonite.providers import AuthenticationProvider

PROVIDERS = [
    # Framework Providers
    # ...
    AuthenticationProvider,

    # Third Party Providers
    #...
]

Removed The Sass Provider

Masonite no longer supports SASS and LESS compiling. Masonite now uses webpack and NPM to compile assets. You will need to now reference the Compiling Assets documentation.

You will need to remove the SassProvider completely from the providers list in config/providers.py. As well as remove the SassProvider import from on top of the file.

You can also completely remove the configuration settings in your config/storage.py file:

SASSFILES = {
    'importFrom': [
        'storage/static'
    ],
    'includePaths': [
        'storage/static/sass'
    ],
    'compileTo': 'storage/compiled'
}

Be sure to reference the Compiling Assets documentation to know how to use the new NPM features.

Removed The Ability For The Container To Hold Modules

The container can no longer hold modules. Modules now have to be imported in the class you require them. For example you can't bind a module like this:

from config import auth

app.bind('AuthConfig', auth)

and then make it somewhere else:

class ClassA:

    def __init__(self, container: Container):
        self.auth = container.make('AuthConfig')

This will throw a StrictContainerError error. Now you have to import it so will have to do something like this using the example above:

from config import auth

class ClassA:

    def __init__(self, container: Container):
        self.auth = auth

Remove Modules from Container

Now that we can no longer bind modules to the container we need to make some changes to the wsgi.py file because we did that here.

Around line 16 you will see this:

container.bind('Application', application)

Just completely remove that line. Its no longer needed.

Also around line 19 you will see this line:

container.bind('ProvidersConfig', providers)

You can completely remove that as well.

Lastly, around line 31 you can change this line:

for provider in container.make('ProvidersConfig').PROVIDERS:

to this:

for provider in providers.PROVIDERS:

Changed How Query Strings Are Parsed

It's unlikely this effects you and query string parsing didn't change much but if you relied on query strings like this:

/?filter[name]=Joe&filter[user]=bob&email=user@email.com

Or html elements like this:

<input name="options[name]" value="Joe">
<input name="options[user]" value="bob">

then query strings will now parse to:

{
    "email": "user@email.com",
    "options": {
        "name": "Joe",
        "user": "bob"
    }
}

You'll have to update any code that uses this. If you are not using this then don't worry you can ignore it.

Scheduler Namespace

Not many breaking changes were done to the scheduler but there are alot of new features. Head over to the Whats New in Masonite 2.3 section to read more.

We did change the namespace from scheduler to masonite.scheduler. So you will need to refactor your imports if you are using the scheduler.

Conclusion

You should be all good now! Try running your tests or running craft serve and browsing your site and see if there are any issues you can find. If you ran into a problem during upgrading that is not found in this guide then be sure to reach out so we can get the guide upgraded.

http://google.com
http://google.com
Helper Functions
Routing
Queues and Jobs
Masonite Entry
Routing
Requests
Routing
Requests
Requests
Caching
The Craft Command
The Craft Command
Creating Packages
validation section here
Contributing Guide
How To Contribute
Masonite 1.3 to 1.4
About Drivers
Controllers documentation here
Queues documentation here
Routes documentation here
Craft commands documentation here
Uploading documentation here
Views documentation here
Testing documentation here
documentation here
Requests documentation here
Validation documentation here
Requests documentation here
Authentication documentation here
Service Container documentation here

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

Masonite 2.1 to 2.2

Masonite 2.1 to 2.2

Introduction

Welcome to the upgrade guide to get your Masonite 2.1 application working with Masonite 2.2. We'll be focusing on all the breaking changes so we can get all your code working on a Masonite 2.2 release cycle.

Masonite 2.2 is jam packed with amazing new features and most of which are backwards compatible so upgrading from Masonite 2.1 to 2.2 is really simple.

We'll go through each section that your application will need to be upgraded and how it can be done.

Each upgrade will have an impact rating from LOW to HIGH. The lower the rating, the less likely it will be that your specific application needs the upgrade.

Getting Started

First let's upgrade Masonite to 2.2 first so we can see any exceptions that will be raised.

Let's upgrade by doing:

pip install masonite==2.2.0

You can also add it to your requirements.txt or Pipfile.

Removing route helpers

Impact: MEDIUM

In Masonite 2.1, route helpers were deprecated and you likely started receiving deprecation warnings. In Masonite 2.2, these were removed. You may have had routes that looks like this:

from masonite.helpers.routes import get, post

ROUTES = [
    get('/url/home').name('home')
]

You will now need to remove all these and use the class based ones. To make this easier we can just import the get and post helpers and alias them like this:

from masonite.routes import Get as get, Post as post

ROUTES = [
    get('/url/home').name('home')
]

Changed Validation

Impact: MEDIUM

Masonite 2.2 completely removes the validation library that shipped with Masonite in favor of a brand new one that was built specifically for Masonite.

Validation Provider

You'll need to add a new validation provider if you want your application to have the new validation features.

Add it by importing it into config/providers.py and add it to your PROVIDERS list:

from masonite.validation.providers import ValidationProvider

PROVIDERS = [
    ...
    ValidationProvider,
    ...
]

Replacing Validation Code

Masonite 2.2 completely removed the validation package from 2.1 and created an even better all new validation package. You'll have to remove all your validation classes and use the new validation package.

For example you may have had a validation class that looked like this:

class RegisterValidator(Validator):

    def register(self):
        users = User.all()
        self.messages({
            'email': 'That email already exists',
            'username': 'Usernames should be between 3 and 20 characters long'
        })

        return self.validate({
            'username': [Required, Length(3, 20)],
            'email': [Required, Length(1), Not(In(users.pluck('email')))],
            'password': [Required]
        })

and used it inside your views like this:

from app.validators import RegisterValidator

    def store(self):
        validate = RegisterValidator(self.request).register()
        if validate.check():
            validate.check_exists()

        if not validate.check():
            self.request.session.flash('validation', json.dumps(validate.errors()))
            return self.request.redirect_to('register')

This is now completely changed to use a better and more sleeker validation. The above validation can now be written like this:

from masonite.validation import Validator

    def store(self, request: Request, validator: Validator):
        errors = request.validate(
            validator.required(['username', 'email', 'password'])
            validator.length('username', min=3, max=20)
            validator.length('email', min=3)
            validator.isnt(
                validator.is_in('email', User.all().pluck('email'))
            )
        )

        if errors:
            return self.request.redirect_to('register').with_errors(errors)

Auth class now auto resolves it's own request class

Masonite 2.2 changes a bit how the masonite.auth.Auth class resolves out of the container and how it resolves its own dependencies.

Now instead of doing something like:

from masonite.auth import Auth
...
def show(self, request: Request):
    Auth(request).user()

You'll need to move this into the parameter list so it can be resolved:

from masonite.auth import Auth
...
def show(self, auth: Auth):
    auth.user()

There should be quite a bit of these in your application if you have used this class or you have used the built in craft auth scaffold command.

Resolving Classes

Impact: MEDIUM

The behavior for resolving classes has now been changed. If you bind a class into the container like this:

from some.place import SomeClass

class SomeProvider:

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

It previously would have resolved and gave back the class:

from some.place import SomeClass

def show(self, request: Request, some: SomeClass):
    some #== <class some.place.SomeClass>
    setup_class = some(request)

This will now resolve from the container when you resolve it as a parameter list. This means that you will never get back a class inside places like controllers.

Now the above code would look something like this:

from some.place import SomeClass

def show(self, request: Request, some: SomeClass):
    some #== <some.place.SomeClass x9279182>

notice it now returns an object. This is because Masonite will check before it resolves the class if the class itself needs to be resolved (if it is a class). If SomeClass requires the request object, it will be passed automatically when you resolve it.

Testing

Masonite 2.2 focused a lot on new testing aspects of Masonite and has some big rewrites of the package internally.

UnitTest class

The UnitTest class has been completely removed in favor of the new masonite.testing.TestCase method.

An import that looked like this:

from masonite.testing import UnitTest

class TestSomeUnit(UnitTest):
    ...

Should now look like this:

from masonite.testing import TestCase

class TestSomeUnit(TestCase):
    ...

Pytest VS Unittest

All classes have now been changed to unittest classes. This will still work with pytest and you can still run python -m pytest. The only thing that changes is the structure of the setup_method(). This has been renamed to setUp().

A class like this:

from masonite.testing import UnitTest
from routes.web import ROUTES

class TestSomeUnit(UnitTest):

    def setup_method(self):
        super().setup_method()

        self.routes(ROUTES)

Should now look like this:

from masonite.testing import TestCase

class TestSomeUnit(TestCase):

    def setUp(self):
        super().setUp()

Method naming

Previously all methods were snake_case but to continue with the unittest convention, all testing methods are camelCase.

A method like:

self.route('/some/protect/route').is_named()

Now becomes:

self.get('/some/protect/route').isNamed()

Again this is to prevent developers from needing to switch between snake_case and camelCase when using Masonite methods and unittest methods.

Route method

The route method that looked something like this:

def test_route_has_the_correct_name(self):
    assert self.route('/testing')

Has now been replaced with the method name of the route. So to get a GET route you would do:

def test_route_has_the_correct_name(self):
    assert self.get('/testing')

or a POST route:

def test_route_has_the_correct_name(self):
    assert self.post('/testing')

So be sure to update all methods of self.route() with the correct request methods.

Loading Routes

In 2.1 you had to manually load your routes in like this:

    def setup_method(self):
        super().setup_method()

        self.routes([
            Get().route('/testing', 'SomeController@show').name('testing.route').middleware('auth', 'owner')
        ])

this is no longer required and routes will be found automatically. There is no longer a self.routes() method.

JSON method

The JSON method signature has changed and you now should specify the request method as the first parameter.

A previous call that looked like this:

self.json('/test/json/response/1', {'id': 1}, method="POST")

should become:

self.json('POST', '/test/json/response/1', {'id': 1})

User

Previously you logged a user in by using the user method but now you can using the actingAs method before you call the route:

A method like this:

class MockUser:
    is_admin = 1
​
def test_owner_user_can_view(self):
    assert self.route('/some/protect/route').user(MockUser).can_view()

Should now be:

from app.User import User
​
def test_owner_user_can_view(self):
    self.assertTrue(
        self.actingAs(User.find(1)).get('/some/protect/route').contains('Welcome')
    )

Masonite 2.0

Masonite 2 brings an incredible new release to the Masonite family. This release brings a lot of new features to Masonite to include new status codes, database seeding, built in cron scheduling, controller constructor resolving, auto-reloading server, a few new internal ways that Masonite handles things, speed improvements to some code elements and so much more. We think developers will be extremely happy with this release.

Upgrading from Masonite 1.6 to Masonite 2.0 shouldn't take very long. On an average sized project, this upgrade should take around 30 minutes. We'll walk you through the changes you have to make to your current project and explain the reasoning behind it.

Controller Constructors

Controller constructors are now resolved by the container so this removed some redundancy within your code and any duplicated auto resolving can now be directly in your constructor:

from masonite.request import Request

class YourController:
    def __init__(self, request: Request):
        self.request = Request
    
    def show(self):
        print(self.request) # <class masonite.request.Request>

Tinker Command

There is a new command that starts a Python shell and imports the container for you already. Test it out to verify that objects are loaded into your container correctly. It's a great debugging tool.

$ craft tinker

Show Routes Command

Masonite 2 ships with an awesome little helper command that allows you to see all the routes in your application

$ craft show:routes

Server Reloading

A huge update to Masonite is the new --reload flag on the serve command. Now the server will automatically restart when it detects a file change. You can use the -r flag as a shorthand:

$ craft serve -r

Autoloading

An incredible new feature is autoloading support. You can now list directories in the new AUTOLOAD constant in your config/application.py file and it will automatically load all classes into the container. This is great for loading command and models into the container when the server starts up.

You can also use this class as a standalone class in your own service providers.

Updated Libraries

Updated all libraries to the latest version with the exception of the Pendulum library which latest version is a breaking change and therefore was left out. The breaking change would not be worth it to add the complexity of upgrading so you may upgrade on a per project basis.

Removed Importing Duplicate Class Names

Previously you had to import classes like:

from masonite.drivers.UploadDriver import UploadDriver

Now you can simply specify:

from masonite.drivers import UploadDriver

Because of this change we no longer need the same duplicated class names in the PROVIDERS list either.

Redirection Provider

Removed the need for the redirection provider completely. You need to remove this from your PROVIDERS list.

Redirection

Renamed Request.redirectTo to Request.redirect_to

Also removed the .send() method and moved the dictionary into a parameter:

def show(self):
    return request().redirect('/dashboard/@id', {'id': '5'})

Request Only

Added a new Request.only method to fetch only specific inputs needed.

Get Request Method

Added a new Request.get_request_method() method to the Request class.

New Argument in Request.all

You can now completely remove fetching of any inputs that Masonite handles internally such as __token and __method when fetching any inputs. This is also great for building third party libraries:

Request.all(internal_variables=False)

Made several changes to the CSRF Middleware

Because of the changes to internal framework variables, there are several changes to the CSRF middleware that comes in every application of Masonite.

Added Scheduler to Masonite

Added a new default package to Masonite that allows scheduling recurring tasks:

Added Database Seeding Support

It's important during development that you have the ability to seed your database with dummy data. This will improve team development with Masonite to get everyones database setup accordingly.

Added a New Static File Helper

Now all templates have a new static function in them to improve rendering of static assets

Added a New Password Helper

You can use the password helper to hash passwords more simply than using straight bcrypt:

from masonite.helpers import password

password('secret') # returns bcrypt password

Added Dot Notation To Upload Drivers And Dictionary Support To Driver Locations.

You can now specify which location in your drivers you want to upload to using a new dot notation:

Upload.store(request().input('file'), 'disk.uploads')

This will use the directory stored in:

DRIVERS = {
  'disk': {
    'uploads': 'storage/uploads',
    'profiles': 'storage/static/users/profiles/images'
  },
  ...
}

Added Status Code Provider

Masonite 2 removes the bland error codes such as 404 and 500 errors and replaces them with a cleaner view. This also allows you to add custom error pages.

Added Explicitly Imported Providers

Providers are now explicitly imported at the top of the file and added to your PROVIDERS list which is now located in config/providers.py. This completely removes the need for string providers and boosts the performance of the application sustantially

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 }} to it. For example:

<form action="/dashboard" method="POST">
    {{ csrf_field }}
    <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 2.0 to 2.1

Introduction

Masonite 2.1 is a fantastic release. It works out a lot of the kinks that were in 2.0 as well as brings several new syntactically good looking code generation

Masonite CLI

For 2.1 you will need masonite-cli>=2.1.0.

Make sure you run:

$ pip install masonite-cli --upgrade

Middleware

Middleware has been changed to classes so instead of doing this in your config/middleware.py file:

HTTP_MIDDLEWARE = [
    'app.http.middleware.DashboardMiddleware.DashboardMiddleware',
]

You will now import it directly:

import app.http.middleware.DashboardMiddleware import DashboardMiddleware

HTTP_MIDDLEWARE = [
    DashboardMiddleware,
]

Auto resolving parameters has been removed

This is likely the biggest change in 2.0. Before 2.1 you were able to fetch by key when resolving by doing something like:

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

We have removed this by default and now you much explicitly import your classes in order to interact with the container resolving:

from masonite.request import Request

def show(self, request: Request):
    request.input(..)

If you truly do not like this change you can modify your container on a per project basis by adding this to your container constructor in wsgi.py:

container = App(resolve_parameters=True)

Just know this is not recommended and Masonite may or may not remove this feature entirely at some point in the future.

Resolving Mail, Queues and Broadcasts

Previously we were able to do something like this:

def show(self, Mail):
    Mail.to(..)

Since we never actually created a class from this and you were not able to explicitly resolve this, we utilized the new container swapping in order to swap a class out for this container binding.

All instances above should be changed to:

from masonite import Mail

def show(self, mail: Mail):
    mail.to(..)

Don't forgot to also do your boot methods on your Service Providers as well:

def boot(self, request: Request):
    ..request..

As well as all your middleware and custom code:

class AuthenticationMiddleware(object):
    """ Middleware To Check If The User Is Logged In """

    def __init__(self, request: Request):
        """ Inject Any Dependencies From The Service Container """
        self.request = request
    ...

Resolving your own code

You may have classes you binded personally to the container like this:

def slack_send(self, IntegrationManager):
    return IntegrationManager.driver('slack').scopes('incoming-webhook').state(self.request.param('id')).redirect()

To get this in line for 2.1 you will need to use Container Swapping in order to be able to resolve this. This is actually an awesome feature.

First go to your provider where you binded it to the container:

from app.managers import IntegrationManager

def boot(self):
    self.app.bind('IntegrationManager', IntegrationManager.driver('something'))

and add a container swap right below it by swapping it with a class:

from app.managers import IntegrationManager

def boot(self):
    self.app.bind('IntegrationManager', IntegrationManager.driver('somedriver'))

    self.app.swap(IntegrationManager, IntegrationManager.driver('somedriver'))

now you can use that class to resolve:

from app.managers import IntegrationManager

def slack_send(self, manager: IntegrationManager):
    return manager.driver('slack').scopes('incoming-webhook').state(self.request.param('id')).redirect()

Removed Masonite Facades

Completely removed the masonite.facades module and put the only class (the Auth class) in the masonite.auth module.

So all instances of:

from masonite.facades.Auth import Auth

need to be changed to:

from masonite.auth import Auth

Removed the StartResponseProvider

The StartResponseProvider was not doing anything crazy and it could be achieved with a simple middleware. This speeds up Masonite slightly by offsetting where the response preparing takes place.

Simply remove the StartResponseProvider from your PROVIDERS list:

PROVIDERS = [
    # Framework Providers
    AppProvider,
    SessionProvider,
    RouteProvider,
    StatusCodeProvider,
    # StartResponseProvider,
    WhitenoiseProvider,
    ViewProvider,
    HelpersProvider,

    ...

As well as put the new middleware in the HTTP middleware

from masonite.middleware import ResponseMiddleware
..

HTTP_MIDDLEWARE = [
    LoadUserMiddleware,
    CsrfMiddleware,
    HtmlMinifyMiddleware,
    ResponseMiddleware, # Here
]

JSON Payloads

In 2.0 you had to fetch incoming JSON payloads like this:

request.input('payload')['id']

So now all instances of the above can be used normally:

request.input('id')

Moved CSRF Middleware into core

CSRF middleware now lives in core and allows you to override some methods or interact with the middleware with class attributes:

Replace your current CSRF Middleware with this new one:

""" CSRF Middleware """

from masonite.middleware import CsrfMiddleware as Middleware


class CsrfMiddleware(Middleware):
    """ Verify CSRF Token Middleware """

    exempt = []

If you made changes to the middleware to prevent middleware from being ran on every request you can now set that as a class attribute:

""" CSRF Middleware """

from masonite.middleware import CsrfMiddleware as Middleware


class CsrfMiddleware(Middleware):
    """ Verify CSRF Token Middleware """

    exempt = []
    every_request = False

This also allows any security issues found with CSRF to be handled on all projects quickly instead of everyone having to patch their applications individually.

Added cwd imports to migrations and seeds

In migrations (and seeds) you will need to put this import inside a __init__.py file in order to allow models to be imported into them

import os
import sys
sys.path.append(os.getcwd())

Bootstrap File

There was a slight change in the bootstrap/start.py file around line 60.

This line:

 start_response(container.make('StatusCode'), container.make('Headers'))

Needs to be changed to:

start_response(
    container.make('Request').get_status_code(), 
    container.make('Request').get_and_reset_headers()
)

Response Binding

You no longer should bind directly to the Response key in the container. You should use the new Response object.

All instances of:

self.app.bind('Response', 'some value')

should now be:

response.view('some value')

and any instance of:

self.app.make('Response')

should be changed to:

response.data()

Restructuring some sample code would be changing this:

self.request.app().bind(
    'Response',
    htmlmin.minify(
        self.request.app().make('Response')
    )
)

to this:

self.response.view(
    htmlmin.minify(self.response.data())
)

Cache Exists name change

The Cache.cache_exists() has been changed to just Cache.exists(). You will need to make changes accordingly:

from masonite import Cache

def show(self, cache: Cache):

    # From
    cache.cache_exists('key')

    # To
    cache.exists('key')

Environment Variables

Although not a critical upgrade, it would be a good idea to replace all instances of retrieval of environment variables with the new masonite.env function.

Change all instances of this:

import os
..

DRIVER = os.getenv('key', 'default')
..
KEY = os.envrion.get('key', 'default')

with the new env function:

from masonite import env
..

DRIVER = env('key', 'default')
..
KEY = env('key', 'default')

What this will do is actually type cast accordingly. If you pass a numeric value it will cast it to an int and if you want a boolean if will cast True, true, False, false to booleans like this:

if you don't want to cast the value you can set the cast parameter to False

KEY = env('key', 'default', cast=False)

Removed Store Prepend method

We removed the store_prepend() method on the upload drivers for the filename keyword arg on the store method.

So this:

upload.store_prepend('random-string', request.input('file'))

now becomes:

upload.store(request.input('file'), filename='random-string')

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.6 to 2.0

Masonite 2 brings an incredible new release to the Masonite family. This release brings a lot of new features to Masonite to include new status codes, database seeding, built in cron scheduling, controller constructor resolving, auto-reloading server, a few new internal ways that Masonite handles things, speed improvements to some code elements and so much more. We think developers will be extremely happy with this release.

Upgrading from Masonite 1.6 to Masonite 2.0 shouldn't take very long although it does have the largest amount of changes in a single release. On an average sized project, this upgrade should take around 30 minutes. We'll walk you through the changes you have to make to your current project and explain the reasoning behind it

Application and Provider Configuration

Masonite 2 adds some improvements with imports. Previously we had to import providers and drivers like:

from masonite.providers.UploadProvider import UploadProvider
from masonite.providers import UploadProvider

Masonite 2 brings a more explicit way of declaring Service Providers in your application. You'll need to take your current PROVIDERS list inside the config/application.py file and move it into a new config/providers.py file.

Now all Service Providers should be imported at top of the file and added to the list:

config/providers.py
from masonite.providers import (
    AppProvider,
    SessionProvider,
    RouteProvider
)

...

PROVIDERS = [
    # Framework Providers
    AppProvider,
    SessionProvider,
    RouteProvider,
    ....
]

String providers will still work but it is not recommended and will not be supported in current and future releases of Masonite.

WSGI changes

There are a few changes in the wsgi.py file and the bootstrap/start.py file.

In the wsgi.py file we should add a new import at the top:

...
# from pydoc import locate - Remove This
...
from config import application, providers
...

container.bind('WSGI', app)
container.bind('Application', application)

# New Additions Here
container.bind('ProvidersConfig', providers)
container.bind('Providers', providers)
container.bind('WSGIProviders', providers)

Then change the code logic of bootstrapping service providers from:

wsgi.py
for provider in container.make('Application').PROVIDERS:
    locate(provider)().load_app(container).register()
    
for provider in container.make('Application').PROVIDERS: 
    located_provider = locate(provider)().load_app(container)
    if located_provider.wsgi is False:
        container.resolve(locate(provider)().load_app(container).boot)

to:

wsgi.py
 for provider in container.make('ProvidersConfig').PROVIDERS: 
    located_provider = provider()
    located_provder.load_app(container).register()
    if located_provider.wsgi:
        container.make('WSGIProviders').append(located_provider)
     else:
        container.resolve(located_provider.boot)
        container.make('Providers').append(located_provider)

and change the logic in bootstrap/start.py to:

bootstrap/start.py
for provider in container.make('WSGIProviders'): 
    container.resolve(located_provider.boot)

Notice here we split the providers list when the server first boots up into two lists which significantly lowers the overhead of each request.

This change should significantly boost speed performances as providers no longer have to be located via pydoc. You should see an immediate decrease in the time it takes for the application to serve a request. Rough time estimates say that this change should increase the request times by about 5x as fast.

Duplicate Class Names

Again, with the addition of the above change, any place you have a duplicated class name like:

from masonite.drivers.UploadDriver import UploadDriver

You can change it to:

from masonite.drivers import UploadDriver

Redirection

Renamed Request.redirectTo to Request.redirect_to. Be sure to change any of these instances accordingly.

All instances of:

return request().redirectTo('home')

should be changed to:

return request().redirect_to('home')

Redirect Send Method

Also removed the .send() method completely on the Request class so all instances of:

def show(self):
    return request().redirect('/dashboard/@id').send({'id': league.id})

Need to be changed to:

def show(self):
    return request().redirect('/dashboard/@id', {'id': league.id})

CSRF Middleware

Some variable internals have changed to prepend a double underscore to them to better symbolize they are being handled internally. Because of this we need to change any instances of csrf_token to __token in the CSRF Middleware file.

Autoloading

Simply add a new AUTOLOAD constant in your config/application.py file. This is the entire section of the autoload configuration.

config/application.py
'''
|--------------------------------------------------------------------------
| Autoload Directories
|--------------------------------------------------------------------------
|
| List of directories that are used to find classes and autoload them into 
| the Service Container. This is initially used to find models and load
| them in but feel free to autoload any directories
|
'''

AUTOLOAD = [
    'app',
]

By default this points to the app directory where models are stored by default but if you moved your models to other directories like app/models or app/admin/models then add those directories to your list:

config/application.py
....

AUTOLOAD = [
    'app',
    'app/models',
    'app/admin/models'
]

RedirectionProvider

Because of a minor rewrite of the Request class, we now do not need the RedirectionProvider. You can remove the RedirectionProvider completely in your PROVIDERS list.

StatusCodeProvider

There is a new status code provider which adds support for adding custom status codes and rendering better default status code pages such as 400 and 500 error pages. This should be added right above the StartResponseProvider:

config/application.py
PROVIDERS = [
    # Framework Providers
    ...
    'masonite.providers.RouteProvider',
    'masonite.providers.RedirectionProvider',
    
    # New provider here above StartResponseProvider
    'masonite.providers.StatusCodeProvider',
    
    'masonite.providers.StartResponseProvider',
    'masonite.providers.WhitenoiseProvider',
    ...
]

.env File

The .env got a small upgrade and in order to make the APP_DEBUG variable consistent, it should be set to either True or False. Previously this was set to something like true or false.

.env
APP_DEBUG=True
# or
APP_DEBUG=False

Masonite 2 also removed the APP_LOG_LEVEL environment variable completely.

Finished

That's it! You're all done upgrading Masonite 1.6 to Masonite 2.0. Build something awesome!

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.

We will not go into all the better ways to use some of the features. For those changes be sure to read the "" documentation to the left to see what fits into your application and what doesn't. We will only focus on the breaking changes here.

You can do a lot of other awesome things like rule enclosures. Read more under the

Here is an example application that is being upgraded from 2.1 to 2.2

Checkout the

Read more in the documentation.

Read more in documentation.

Read more in documentation.

Read more in Introduction documentation.

Read more in documentation.

Read more about changing duplicated class names under the documentation.

Read more in the documentation.

Read more in documentation.

Read more in documentation.

Read more in documentation.

Be sure to read the changes in the .

Read about Masonite Scheduler under the documentation.

Read more in the documentation.

Read more in the documentation.

Read more in the documentation.

Read more in the documentation.

Read more in the documentation.

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:

This guide just shows the major changes between version to get your application working on 2.1. You should see the documentation to upgrade smaller parts of your code that are likely to be smaller quality of life improvements.

That is all the main changes in 2.1. Go ahead and run your server and you should be good to go. For a more up to date list on small improvements that you can make in your application be sure to checkout the documentation article.

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

Because of this, all framework will need to cut out the redundant last part. The above code should be changed to:

You can check for what the class should look like from the repository

Masonite 2 comes with a new autoloader. This can load all classes in any directory you specify right into the when the server first starts. This is incredibly useful for loading your models, commands or tasks right into the container.

Be caution that this will autoload all models into the with the class name as the key and the class as the binding.

Be sure to read about all the to ensure that your application is completely up to date with many of the latest decisions and be sure to thoroughly test your application. Feel free to open an issue if any problems arise during upgrading.

Masonite Entry
Whats New in 2.2
Validation documentation
GitHub Repo
Upgrade Guide for Masonite 1.6 to 2.0
Autoloading
Upgrade Guide 1.6 to 2.0
Task Scheduling
Database Seeding
Static Files
Status Codes
GitHub
config/cache.py
config/broadcast.py
CSRF middleware
config/database.py
What's New in 2.1
Whats New in 2.1
What's New in Masonite 1.6
Service Providers
MasoniteFramework/masonite
Service Container
Service Container
changes in Masonite 2
Controllers
The Craft Command Introduction
The Craft Command Introduction
The Craft Command
Duplicate Class Names
Requests
Requests
Requests
Requests
Encryption
Uploading
Mike