Only this pageAll pages
Powered by GitBook
1 of 82

development

Loading...

Prologue

Loading...

Loading...

Loading...

Loading...

The Basics

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Features

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Architecture

Loading...

Loading...

Security

Loading...

Loading...

Masonite ORM

Testing

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Extending

Official Packages

Loading...

How-to Guides

What's New

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Upgrade Guide

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Release Cycle

Masonite uses SEMVER versioning schema and your requirements should be fixed on any one of these requirements depending on your dependency manager:

masonite>=4,<5
masonite~=4

This will allow your application to stay up to date for all minor releases of the framework.

Major Releases

Masonite currently has an 8 month release cycle. Roughly 8 months we will come out with a new major release. Masonite follows a SEMVER versioning schema

Major releases almost always contain some form of breaking changes. You should only upgrade to major versions after careful local upgrades and testing.

Minor Versions

Minor versions come with new features and could release every few days or every few months depending on how many features are being added to the codebase and when those features are ready to be released.

Minor version updates will never come with breaking changes. You should always stay up to date with the latest minor versions of the framework.

Patch Versions

Patch versions are small fixes or security releases.

Patch version updates will never come with breaking changes. You should always stay up to date with the latest patch versions of the framework.

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.

Contributing Guide

When contributing to Masonite, please first discuss the change you wish to make via a GitHub issue. Starting with a GitHub issue allows all contributors and maintainers to have a chance to give input on the issue. It creates a public forum to discuss the best way to implement the issue, fix, or improvement. It also creates an open discussion on if the issue will even be permitted to be in the project.

Please note we have a code of conduct, please follow it in all your interactions with the project. You can find it in this in this documentation as well as all of Masonite's repositories.

Getting Started

The framework has 2 main parts: The "masonite" repo and the "cookie-cutter" repo.

The MasoniteFramework/cookie-cutter repository is the main repository that will install when creating new projects using the project start command. This is actually a full Masonite project. When you run project start 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 project structure.

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 and make your changes from.

The MasoniteFramework/craft is deprecated. This was where the craft CLI tool lived that has since been moved into the masonite repository.

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 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 -e . from inside the masonite directory. This will install masonite as a pip package in editable mode. Any changes you make to the codebase will immediately be available in your project. You may need to restart your development server.

  • Any changes you make to this package just push 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:

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:

Methods and Functions with Dependencies

Most methods will require some dependency or parameters. You must specify them like this:

And if your dependencies are objects it should give the path to the module:

Code Comments

Code comments should be left to a MINIMUM. If your code is complex to the point that it requires a comment then you should consider refactoring your code instead of adding a comment. If you're code MUST be complex enough that future developers may not understand it, add a # comment above it

For normal code this will look something like:

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 before the PR will be merged. If you are unsure how to write unit tests of the repositories listed above, you may open the pull request anyway and we will add the tests together.

  6. The PR must pass the GitHub actions that run on pull requests. The Pull Request can be merged in once you have a successful review from two other collaborators, or one review from a maintainer.

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 4.0 then the previous release branches could be 3.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 4.0. 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 4.1 branch 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 (4.1.0).

Bug fixes that do not break anything is the same process as above.

New features 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 be merged 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 joe@masoniteproject.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

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 Architectural Concepts documentation starting with the Request Lifecycle, 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.

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

Increase the version numbers in any example files (like setup.py) and 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

Controllers
Routing
Service Providers
Service Container
Release Cycle
Caching
Creating Packages
Masonite package
Medium
YouTube
Gitbook.com
GitHub.com
GitHub.com
LaraCasts.com
Contributing Guide
"""This is a module to add support for Billing users."""
from masonite.request import Request
...
def some_function(self):
    """This is a function that does x action. 

    Then give an exmaple of when to use it 
    """
    ... code ...
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
    """
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
# 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()

Middleware

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 middlewar

Getting Started

Middleware classes are placed inside the Kernel class. All middleware are just classes that contain a before method and 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

Configuration

We have one of two configuration attributes we need to work with. These attributes both reside in our Kernel file and are http_middleware and route_middleware.

http_middleware is a simple list which should contain your middleware classes. This attribute is a list because all middleware will simply run in succession one after another, similar to Django middleware

In our Kernel file this type of middleware may look something like:

from masonite.middleware import EncryptCookies

class Kernel:

    http_middleware = [
        EncryptCookies
    ]

Middleware will run on every inbound request to the application whether or not a route was found or not.

Route Middleware

Route middleware is also simple but instead of a list, it is a dictionary with a custom name as the key and a list of middleware. This is so we can specify the middleware based on the key in our routes file.

In our config/middleware.py file this might look something like:

from app.middleware.RouteMiddleware import RouteMiddleware
class Kernel:

  #..

  route_middleware = {
      "web": [
          SessionMiddleware, 
          HashIDMiddleware,
          VerifyCsrfToken,
      ],
  }

By default, all routes inside the web.py file will run the web middleware list

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:

Get('/feeds', 'FeedController').middleware('throttle:2,100')

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

class ThrottleMiddleware:

    def before(self, request, response, minutes, requests):
        # throttle requests

    def after(self, request, response, 
             minutes, requests):
        # throttle requests

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

Get('/dashboard/@user_id/settings', 'FeedController').middleware('permission:@user_id')

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

Middleware:

  • can live anywhere in your project,

  • Inherit from Masonite's base middleware class

  • Contain a before and after method that accepts request and response parameters

from masonite.middleware import Middleware

class AuthenticationMiddleware(Middleware):
    """Middleware class
    """

    def before(self, request, response):
        #..
        return request

    def after(self, request, response):
        #..
        return request

It's important to note that in order for the request lifecycle to continue, you must return the request class. If you do not return the request class, no other middleware will run after that middleware.

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.

Consuming Middleware

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:

Route.get('/dashboard', 'DashboardController@show').name('dashboard').middleware('auth')
Route.get('/login', 'LoginController@show').name('login')
You can read about how the framework flows, works and architectural concepts here
SEMVER
Contributor Covenant
http://contributor-covenant.org/version/1/4

Introduction and Installation

You are browsing the development version of the documentation for an upcoming version of Masonite. It is subject to change.

Stop using old frameworks with just a few confusing features. Masonite is the developer focused dev tool with all the features you need for the rapid development you deserve. Masonite is perfect for beginners getting their first web app deployed or advanced developers and businesses that need to reach for the full fleet of features available.

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.

Some Notable Features Shipped With Masonite

  • Mail support for sending emails quickly.

  • Queue support to speed your application up by sending jobs to run on a queue or asynchronously.

  • Notifications for sending notifications to your users simply and effectively.

  • Task scheduling to run your jobs on a schedule (like everyday at midnight) so you can set and forget your tasks.

  • Events you can listen for to execute listeners that perform your tasks when certain events happen in your app.

  • A BEAUTIFUL Active Record style ORM called Masonite ORM. Amazingness at your fingertips.

  • Many more features you need which you can find in the docs!

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

  • 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.7+ Python installation. If you are having issues with any installation steps just be sure the commands are for Python 3.7+ 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.7-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 python command 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 python3 anytime you see the python command.

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

Installation

First install the Masonite package:

$ pip install masonite

Then start a new project:

$ project start .

This will create a new project in the current directory.

If you want to create the project in a new directory (e.g. my_project) you must provide the directory name with project start my_project.

Then install Masonite dependencies:

$ project install

If you have created the project in a new directory you must go to this directory before running project install.

Once installed you can run the development server:

$ python craft serve

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

Request

The request and the response in Masonite work together to form a well formed response that a browser can read and render. The Request class is used to fetch any incoming cookies, headers, URL's, paths, request methods and other incoming data.

Cookies

Although both the request and response classes have headers and cookies, in most instances, when fetching cookies and headers, it should be fetched from the Request class. When setting headers and cookies it should be done on the response class.

To get cookies you can do so simply:

This will fetch the cookie from the incoming request headers.

You can also set cookies on the request:

Note that setting cookies on the request will NOT return the cookie as part of the response and therefore will NOT keep the cookie from request to request.

Headers

Although both the request and response classes have headers and cookies, in most instances, when fetching cookies and headers, it should be fetched from the Request class. When setting headers and cookies it should be done on the response class.

To get a request header you can do so simply:

You can also set a header on the request class:

Note that setting headers on the request will NOT return the header as part of the response.

Paths

You can get the current request URI:

You can get the current request method:

You can check if the current path contains a glob style schema:

You can get the current subdomain from the host:

You can get the current host:

Inputs

Inputs can be from any kind of request method. In Masonite, fetching the input is the same no matter which request method it is.

To fetch an input we can do:

If an input doesn't exist you can pass in a default:

To fetch all inputs from request we can do:

Or we can only fetch some inputs:

To fetch all inputs from request we can do:

Getting Dictionary Input

If your input is a dictionary you can access nested data in two ways. Take this code example:

You can either access it normally:

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

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

Route Params

Route parameters are parameters specified in a route and captured from the URL:

If you have a route like this:

You can get the value of the @user_id parameter like this:

User

As a convenient way to fetch the user, you can do so directly on the request class if a user is logged in:

If the user is not authenticated then this will be set to None

IP Address

You can fetch the IP address (ipv4) from which the request has been made by adding the IpMiddleware to the HTTP middlewares of the project:

Then you can use the ip() helper:

IP address is retrieved from various HTTP request headers in the following order:

  • HTTP_CLIENT_IP

  • HTTP_X_FORWARDED_FOR

  • HTTP_X_FORWARDED

  • HTTP_X_CLUSTER_CLIENT_IP

  • HTTP_FORWARDED_FOR

  • HTTP_FORWARDED

  • REMOTE_ADDR

The first non-private and non-reserved IP address found by resolving the different headers will be returned by the helper.

The headers used and their order can be customized by overriding the IpMiddleware and changing the headers attribute:

Controllers

Controllers are a place where most of your business logic will be. Controllers are where you put the responses you see in the web browser. Responses can be dictionaries, lists, views or any class that can render a response.

Introduction

You may use a craft command to create a new basic controller or simply make a new controller manually. Controllers are classes with methods that are mapped to a route.

Your route may look something like this:

In this case this route will call the WelcomeController classes show method.

To create a basic controller via a craft command simply run:

This will create a new controller class to get you setup quickly. This controller class will look like a basic class like this:

You may start building your controller out and adding the responses you need.

Note that the controllers inherit Masonite base Controller class. This is required for Masonite to pick up your controller class in routes.

Dependency Injection

Responses

Controllers can have different response types based on what you need to return.

JSON

If you want to return a JSON response you can return a dictionary or a list:

This will return an application/json response.

Strings

You can return strings:

Views

If you want to return a view you can resolve the view class and use the render method:

Models

If you are using Masonite ORM, you can return a model directly:

Redirects

If you want to return a redirect you can resolve the response class and use the redirect method:

Other

You can return any class that contains a get_response() method. This method needs to return any one of the above response types.

Request Parameters

If you had a parameter in your route, you may fetch it by specifying the parameter in the controller's method:

Since the id parameter is in the route we can fetch the value of this parameter by specifying it in the controller method signature:

Another way to fetch route parameters is through the request class:

Controller Locations

The default registered controllers location is app/controllers and is defined in project Kernel.py configuration file:

Setting locations

You can override the registered controllers location in Kernel.py file by editing the default binding controllers.location.

Adding locations

You can multiple additional controller locations with add_controller_locations:

The best place to do this is in your Kernel.py file in the register_routes() method.

You should do it before registering routes, else registering routes will fail as Masonite will fail to resolve controller classes.

Configuration

Configuration files in Masonite are gathered in one folder named config in default projects.

Each feature can have some options in its own file named after the feature. For example you will find mail related options in config/mail.py file.

Getting Started

The Configuration class is responsible for loading all configuration files before the application starts.

It will load all files located at path defined through config.location binding which default to config/.

Then values are accessed based on the file they belong to and a dotted path can be used to access nested options.

Given the following config/mail.py file:

  • Accessing mail will return a dictionary with all the options.

  • Accessing mail.from_email will return the FROM_EMAIL value

  • Accessing mail.drivers.smtp.port will return the port value for smtp driver.

Getting Value

To read a configuration value one can use the Config facade:

or the config helper:

Setting Value

Setting configuration values is achieved through projet configuration files.

Overriding Value

However, you can override on the fly a configuration value with the Config facade:

This should be done sparingly as this could have unexpected side effects depending at which time you override the configuration option.

This is mostly useful during tests, when you want to override a configuration option to test a specific behaviour:

Static Files

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 registered in your configuration files and serves those assets.

Configuration

All configurations that are specific to static files can be found in config/filesystem.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 are simply the location of your static file locations as a relative path starting from the base of your application. 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:

Now in our templates we can use:

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:

this will render:

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

and use the dot notation like so:

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/filesystem.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:

and you can alias this in your STATICFILES constant:

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!

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.

Your controller's constructor and controller methods are resolved by Masonite's . Because of this, you can simply typehint most of Masonite's classes in either the constructor or the methods:

Read more about the benefits of the .

Masonite will be able to pick up controllers (inheriting Controller class) using in the registered controllers locations.

But if you simply want to have different configuration depending on the environment (development, testing or production) you should rely instead on used to define configuration options.

MasoniteCasts.com
Discord Community
from masonite.request import Request
#..

def show(self, request: Request):
  request.cookie('Accepted-Cookies')
from masonite.request import Request
#..

def show(self, request: Request):
  request.cookie('Accepted-Cookies', 'True')
from masonite.request import Request
#..

def show(self, request: Request):
  request.header('X-Custom')
from masonite.request import Request
#..

def show(self, request: Request):
  request.header('X-Custom', 'value')
from masonite.request import Request
#..

def show(self, request: Request):
  request.get_path() #== /dashboard/1
from masonite.request import Request
#..

def show(self, request: Request):
  request.get_request_method() #== PUT
from masonite.request import Request
#..

def show(self, request: Request):
  # URI: /dashboard/1/users
  request.contains("/dashboard/*/users") #== True
from masonite.request import Request
#..

def show(self, request: Request):
  # URI: work.example.com
  request.get_subdomain() #== work
from masonite.request import Request
#..

def show(self, request: Request):
  # URI: work.example.com
  request.get_host() #== example.com
from masonite.request import Request
#..

def show(self, request: Request):
  # GET /dashboard?user_id=1
  request.input('user_id') #== 1
from masonite.request import Request
#..

def show(self, request: Request):
  # GET /dashboard
  request.input('user_id', 5) #== 5
from masonite.request import Request

def show(self, request: Request):
  request.all() #== {"user_id": 1, "name": "John", "email": "john@masoniteproject.com"}
from masonite.request import Request

def show(self, request: Request):
  request.only("user_id", "name") #== {"user_id": 1, "name": "John"}
"""
Request Payload: 
{
"user": {
    "id": 1,
    "addresses": [
        {"id": 1, "street": "A Street"},
        {"id": 2, "street": "B Street"}
    ],
	"name":{
		"first":"user",
		"last":"A"
	}
 }
}
"""
request.input('user')['name']['last'] # A
request.input('user.name.last') # A
request.input('user.addresses.*.id') # [1,2]
Route.get('/dashboard/@user_id', 'DashboardController@show')
from masonite.request import Request
#..

def show(self, request: Request):
  # GET /dashboard?user_id=1
  request.param('user_id') #== 1
from masonite.request import Request
#..

def show(self, request: Request):
  # GET /dashboard?user_id=1
  request.user() #== <app.User.User>
# Kernel.py
from masonite.middleware import IpMiddleware

class Kernel:
    http_middleware = [
      # ...
      IpMiddleware
    ]
def show(self, request: Request):
    request.ip()
class CustomIpMiddleware(IpMiddleware):
    headers = [
      "HTTP_CLIENT_IP",
      "REMOTE_ADDR"
    ]
Route.get('/', 'WelcomeController@show')
$ python craft controller Welcome
from masonite.controllers import Controller
from masonite.views import View


class WelcomeController(Controller):
    def show(self, view: View):
        return view.render("")
def __init__(self, request: Request):
  self.request = request

  #..

def show(self, request: Request):
  return request.param('1')
def show(self):
  return {"key": "value"}
def show(self):
  return "welcome"
def show(self, view: View):
  return view.render("views.welcome")
from app.User import User
#..

def show(self, response: Response):
  return User.find(1)
def show(self, response: Response):
  return response.redirect('/home')
Route.get('/users/@user_id', 'UsersController@user')
def show(self, user_id):
  return User.find(user_id)
from masonite.request import Request

def show(self, request: Request):
  return User.find(request.param('user_id'))
self.application.bind("controllers.location", "app/controllers")
# ...
Route.set_controller_locations(self.application.make("controllers.location"))
from masonite.routes import Route

Route.add_controller_locations("app/http/controllers", "other_module/controllers")
from masonite.environment import env

FROM_EMAIL = env("MAIL_FROM", "no-reply@masonite.com")

DRIVERS = {
    "default": env("MAIL_DRIVER", "terminal"),
    "smtp": {
        "host": env("MAIL_HOST"),
        "port": env("MAIL_PORT", "587"),
        "username": env("MAIL_USERNAME"),
        "password": env("MAIL_PASSWORD"),
        "from": FROM_EMAIL,
    }
}
from masonite.facades import Config

Config.get("mail.from_email")
# a default value can be provided
Config.get("database.mysql.port", 3306)
from masonite.configuration import config

config("mail.from_email")
# a default value can be provided
config("database.mysql.port", 3306)
Config.set("mail.from_email", "support@masoniteproject.com")
    def test_something(self):
        old_value = Config.get("mail.from_email")
        Config.set("mail.from_email", "support@masoniteproject.com")

        # test...

        Config.set("mail.from_email", old_value)
config/storage.py
STATICFILES = {
    'storage/assets/css': 'assets/',
}
<img src="/assets/style.css">
config/filesystem.py
....
's3': {
  's3_client': 'sIS8shn...'
  ...
  'path': 'https://s3.us-east-2.amazonaws.com/bucket'
  },
....
...
<img src="{{ asset('s3', 'profile.jpg') }}" alt="profile" />
...
<img
  src="https://s3.us-east-2.amazonaws.com/bucket/profile.jpg"
  alt="profile"
/>
config/storage.py
....
's3': {
  's3_client': 'sIS8shn...'
  ...
  'path': {
    'east': 'https://s3.us-east-2.amazonaws.com/east-bucket',
    'west': 'https://s3.us-west-16.amazonaws.com/west-bucket'
  },
....
...
<img src="{{ asset('s3.east', 'profile.jpg') }}" alt="profile" />
...
<img src="{{ asset('s3.west', 'profile.jpg') }}" alt="profile" />
...
resources/
routes/
storage/
  static/
  root/
    robots.txt
    manifest.json
config/storage.py
STATICFILES = {
    # folder          # template alias
    'storage/static': 'static/',
    ...
    'storage/public': '/'
}

Response

The request and the response in Masonite work together to form a well formed response that a browser can read and render. The Response class is responsible for what data is returned and how it is formatted. In this case, a response can have cookies and headers. The Request class can also have cookies and headers. In most times, generally, when you have to fetch headers or cookies you should do so on the request class and when you set cookies and headers you should do so on the response class.

Cookies

Cookies on the response class are attached to the response and rendered as part of the response headers. Any incoming cookies you would likely fetch from the request class but you can fetch them from the response if you have a reason to:

from masonite.response import Response

def show(self, response: Response):
    response.cookie("key")

You can also set cookies on the response:

from masonite.response import Response

def show(self, response: Response):
    response.cookie("Accepted-Cookies", "True")

You can also delete cookies:

from masonite.response import Response

def show(self, response: Response):
    response.delete_cookie("Accepted-Cookies")

Redirecting

You can redirect the user to any number of URL's.

First you can redirect to a simple URL:

from masonite.response import Response

def show(self, response: Response):
    return response.redirect('/home')

You can redirect back to where the user came from:

from masonite.response import Response

def show(self, response: Response):
    return response.back()

If using the back method as part of a form request then you will need to use the back view helper as well:

<form>
  {{ csrf_field }}
  {{ back() }}
  <input type="text">
  ...
</form>

You can also redirect to a route by its name:

from masonite.response import Response

def show(self, response: Response):
    return response.redirect(name='users.home')

This will find a named route users.home like:

Route.get('/dashboard/@user_id', 'DashboardController@show').name('users.home')

You may also pass parameters to a route if the URL requires it:

from masonite.response import Response

def show(self, response: Response):
    return response.redirect(name='users.home', params={"user_id", 1})

Finally you may also pass query string parameters to the url or route to redirect:

def show(self, response: Response):
    return response.redirect(name='users.home', params={"user_id", 1}, query_params={"mode": "preview"})
#== will redirect to /dashboard/1?mode=preview

with_success

You can redirect by flashing a success message into session:

def show(self, response: Response):
    return response.redirect('/home').with_success("Preferences have been saved !")

with_errors

You can redirect by flashing an error message or errors messages into session:

def show(self, request: Request, response: Response):
    return response.redirect('/home').with_errors("This account is disabled !")

You can directly use validation errors:

def show(self, request: Request, response: Response):
    errors = request.validate({
        "name": required
    })
    return response.redirect('/home').with_errors(errors)

with_input

You can redirect by flashing form input into session, to re-populate the form when there are errors:

def show(self, request: Request, response: Response):
    errors = request.validate({
        "name": required
    })
    return response.redirect('/home').with_errors(errors).with_input()

Headers

Response headers are any headers that will be found on the response. Masonite takes care of all the important headers for you but there are times where you want to set you own custom headers.

Most incoming headers you want to get can be found on the request class but if you need to get any headers on the response:

from masonite.response import Response

def show(self, response: Response):
	response.header('key')

Any responses you want to be returned should be set on the response class:

from masonite.response import Response

def show(self, response: Response):
	response.header('X-Custom', 'value')

This will set the header on the response.

Status

You can set the status on the response simply:

from masonite.response import Response

def show(self, response: Response):
	response.status(409)

Downloading

You can also very simply download assets like images, PDF's or other files:

def show(self, response: Response):
  return response.download("invoice-2021-01", "path/to/invoice.pdf")

This will set all the proper headers required and render the file in the browser.

When setting the name, the file extension will be picked up from the file type. This example will download the invoice as the name invoice-2021-01.pdf

If you want to force download it, or automatically download the file when the response in rendered, you can add a force parameter and set it to True:

def show(self, response: Response):
    return response.download("invoice-2021-01", "path/to/invoice.pdf", force=True)

Environments

Environment variables in Masonite are defined in a .env file and should contain all environment variables needed for your project.

You can have multiple environment files that are loaded when the server first starts. It is often helpful to have different variable values depending on the environment where the application is running (locally, during tests or on a production server).

Also it might be useful to have more global environment variables that can be shared across your team for 3rd party services like Stripe or Mailgun and then have more developer specific values like database connections or different storage drivers for development.

We'll walk through how to configure your environments in this documentation.

Security

Environment variables should be set on a project per project basis inside your .env file.

Never commit any of your .env files into source control ! It would be a security risk in the event someone gained access to your repository since sensitive credentials and data would get exposed.

That is why .env and .env.* are in the project .gitignore file by default, so you should not worry about accidentally committing those files to source control.

Getting Started

In a fresh Masonite installation, a .env.example file located at project root directory will define minimum and common configuration values for a Masonite application. During the installation process, this file will be copied to .env 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 file.

Loading Order

Environment files are loaded in this order:

  1. Masonite will load the .env file located at your project root into the Python environment.

  2. Masonite will look for an APP_ENV variable inside the already loaded .env. If it is defined it will try to load the .env.{APP_ENV} file corresponding to this environment name.

For example, if APP_ENV is set to local, Masonite will additionally load the .env.local environment file.

.env
APP_ENV=local

When the server is ready all those variables will be loaded into the current environment ready to be accessed in the different Masonite configuration files or directly with env() helper.

Defining Variables

If some variables contain spaces you should put variable content into double quotes:

APP_NAME="Masonite test project"

Reading Variables

os.getenv()

You can use Python standard os.getenv() method to get an environment variable value. It looks like:

import os

is_debug = os.getenv("APP_DEBUG") #== "True" (str)

Notice that this method does not cast types, so here we got a string instead of a boolean value.

env()

You can also use Masonite helper env to read an environment variable value. It looks like:

from masonite import env

is_debug = env("APP_DEBUG", False) #== True (bool)

Note that you can provide a default value if the environment variable is not defined. Default value is "". For convenience this helper is casting types. Here are different examples of variables type casting:

Env Var Value
Casts to (type)

5432

5432 (int)

true

True (bool)

None (None)

""

"" (str)

True

True (bool)

false

False (bool)

False

False (bool)

smtp

smtp (string)

If you do not wish to cast the value then you can provide a third parameter cast=False:

from masonite import env
​
env('APP_DEBUG', False, cast=False) #== "False" (str)

Getting Current Environment

The current Masonite environment is defined through the APP_ENV variable located in your .env file. You can access it easily through the Masonite app environment() helper:

APP_ENV=local
app.environment() #== local

When running tests the environment will be set to testing. You can use is_running_tests() helper to check if environment is testing:

app.is_running_tests() #== True if running tests

You can also check if the environment is a production environment with:

app.is_production() #== True if APP_ENV=production

Debug Mode

The debug mode is controlled by the APP_DEBUG environment variable used in config/application.py configuration file. When crafting a new project, the debug mode is enabled (APP_ENV=True). It should stay enabled for local development.

When debug mode is enabled all exceptions (or routes not found) are rendered as an HTML debug error page containing a lot of information to help you debug the problem. When disabled, the default 500, 404, 403 error pages are rendered.

You can check if debug mode is enabled through the Masonite app is_debug() helper or with the config helper:

app.is_debug() #== True

from masonite.configuration import config

config("application.debug") #== True

Never deploy an application in production with debug mode enabled ! This could lead to expose some sensitive configuration data and environment variables to the end user.

Logging

Logging is a pretty crucial part of any application. Logging 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.

Configuration

To enable logging in your application, ensure that LoggingProvider is added to your application providers:

# config/providers.py
from masonite.providers import LoggingProvider

PROVIDERS = [
  # ...
  LoggingProvider
]

Authorization

Masonite also provides a simple way to authorize user actions against a given resource. This is achieved with two concepts: gates and policies. Gates are as the name suggests an authorization check that you will be able to invoke to verify user access. Policies are a way to groupe authorization logic around a model.

Gates

Gates are simple callable that will define if a user is authorized to perform a given action. A handy Gate facade is available to easily manipulate gates.

Registering Gates

Gates receive a user instance as their first argument and may receive additionals arguments such as a Model instance. You needs to define Gates in boot() method of your service provider.

In the following example we are adding gates to verify if a user can create and update posts. Users can create posts if they are administrators and can update posts they have created only.

from masonite.facades import Gate

class MyAppProvider(Provider):

    def boot(self):
        Gate.define("create-post", lambda user: user.is_admin)
        Gate.define("update-post", lambda user, post: post.user_id == user.id)

You can then check if a gate exists or has been registed by using has() which is returning a boolean:

Gate.has("create-post")

If a unknown gate is used a GateDoesNotExist exception will be raised.

Authorizing Actions

Then anywhere (often in a controller) in your code you can use those gates to check if the current authenticated user is authorized to perform the given action defined by the gate.

Gates exposes different methods to perform verification: allows(), denies(), none(), any(), authorize() inspect().

allows(), denies(), none() and any() return a boolean indicating if user is authorized

if not Gate.allows("create-post"):
    return response.redirect("/")

# ...
post = Post.find(request.input("id"))
if Gate.denies("update-post", post):
    return response.redirect("/")

# ...
Gate.any(["delete-post", "update-post"], post)

# ...
Gate.none(["force-delete-post", "restore-post"], post)

authorize() does not return a boolean but will raise an AuthorizationException exception instead that will be rendered as an HTTP response with a 403 status code.

Gate.authorize("update-post", post)
# if we reach this part user is authorized
# else an exception has been raised and be rendered as a 403 with content "Action not authorized".

Finally for better control over the authorization check you can analyse the response with inspect():

response = Gate.inspect("update-post", post)
if response.allowed():
     # do something
else:
     # not authorized and we can access message
     Session.flash("errors", response.message())

Via the User Model

Authorizes class can be added to your User model to allow quick permission checks:

from masonite.authentication import Authenticates
from masonite.authorization import Authorizes

class User(Model, Authenticates, Authorizes):
    #..

A fluent authorization api will now be available on User instances:

user.can("delete-post", post)
user.cannot("access-admin")
user.can_any(["delete-post", "force-delete-post"], post)

All of those methods receive the gate name as first argument and then some additional arguments if required.

With a given user

You can use the for_user() method on the Gate facade to make the verification against a given user instead of the authenticated user.

from masonite.facades import Gate

user = User.find(1)
Gate.for_user(user).allows("create-post")

Gate Hooks

During the gate authorization process, before and after hooks can be triggered.

A before hook can be added like this:

# here admin users will always be authorized based on the boolean value of this response
Gate.before(lambda user, permission : user.role == "admin")

The after hook works the same way:

Gate.after(lambda user, permission, result : user.role == "admin")

If the after callback is returning a value it will take priority over the gate result check.

Policies

Policies are classes that organize authorization logic around a specific model.

Creating Policies

You can run the craft command:

$ python craft policy AccessAdmin

You can also create a policy with a set of predefined gates by using the --model flag:

$ python craft policy Post --model

A model policy comes with common actions that we can perform on a model:

from masonite.authorization import Policy


class PostPolicy(Policy):
    def create(self, user):
        return False

    def view_any(self, user):
        return False

    def view(self, user, instance):
        return False

    def update(self, user, instance):
        return False

    def delete(self, user, instance):
        return False

    def force_delete(self, user, instance):
        return False

    def restore(self, user, instance):
        return False

You are free to add any other methods on your policies:

from masonite.authorization import Policy


class PostPolicy(Policy):
    #.. 

    def publish(self, user):
        return user.email == "admin@masonite.com"

Registering Policies

Then in your service provider (as for defining gates) you should register the policies and bind them with a model:

from masonite.facades import Gate
from app.models.Post import Post
from app.models.User import User

from app.policies.PostPolicy import PostPolicy
from app.policies.UserPolicy import UserPolicy

class MyAppProvider(Provider):

    #..

    def register(self):
        Gate.register_policies(
            [(Post, PostPolicy), (User, UserPolicy)],
        )

An example policy for the Post model may look like this:

from masonite.authorization import Policy

class PostPolicy(Policy):
    def create(self, user):
        return user.email == "idmann509@gmail.com"

    def view(self, user, instance):
        return True

    def update(self, user, instance):
        return user.id == instance.user_id

If an unknown policy is used then a PolicyDoesNotExist exception will be raised.

Authorizing Actions

You can then use the Gate facade methods to authorize actions defined in your policies. With the previously defined PostPolicy we could make the following calls:

from masonite.facades import Gate

post = Post.find(1)
Gate.allows("update", post)
Gate.denies("view", post)
Gate.allows("force_delete", post)

The create() or view_any() methods do not take a model instance, that is why the model class should be provided so that Gate mechanism can infer from which policy those methods are belonging to.

Gate.allows("create", Post)
Gate.allows("view_any", Post)

Authentication

Masonite makes authentication really simply.

Authentication Scaffold Command

Masonite comes with a command to scaffold out a basic authentication system. You may use this as a great starting point for adding authentication to your application. This command will create controllers, views, and mailables for you.

If you would like to implement your own authentication from scratch you can skip to the sections below.

First run the command to add the news files:

python craft auth

Then add the authentication routes to your routes file:

from masonite.authentication import Auth

ROUTES = [
  # routes
]

ROUTES += Auth.routes()

You may then go to the /login or /register route to implement your authentication.

Configuration

The configuration for Masonite's authentication is quite simple:

from app.User import User

GUARDS = {
    "default": "web",
    "web": {"model": User},
    "password_reset_table": "password_resets",
    "password_reset_expiration": 1440,  # in minutes. 24 hours. None if disabled
}

The default key here is the guard to use for authentication. The web dictionary is the configuration for the web guard.

Login Attempts

You can attempt a login by using the Auth class and using the attempt method:

from masonite.authentication import Auth
from masonite.request import Request

def login(self, auth: Auth, request: Request):
  user = auth.attempt(request.input('email'), request.input("password"))

If the attempt succeeds, the user will now be authenticated and the result of the attempt will be the authenticated model.

If the attempt fails then the result will be None.

If you know the primary key of the model, you can attempt by the id:

from masonite.authentication import Auth
from masonite.request import Request

def login(self, auth: Auth, request: Request):
  user = auth.attempt_by_id(1)

You can logout the current user:

from masonite.authentication import Auth
from masonite.request import Request

def logout(self, auth: Auth):
  user = auth.logout()

User

You can get the current authenticated user:

from masonite.authentication import Auth
from masonite.request import Request

def login(self, auth: Auth, request: Request):
  user = auth.user() #== <app.User.User>

If the user is not authenticated, this will return None.

Routes

You can register several routes quickly using the auth class:

from masonite.authentication import Auth

ROUTES = [
  #..
]

ROUTES += Auth.routes()

This will register the following routes:

URI
Description

GET /login

Displays a login form for the user

POST /login

Attempts a login for the user

GET /home

A home page for the user after a login attempt succeeds

GET /register

Displays a registration form for the user

POST /register

Saved the posted information and creates a new user

GET /password_reset

Displays a password reset form

POST /password_reset

Attempts to reset the users password

GET /change_password

Displays a form to request a new password

POST /change_password

Requests a new password

Guards

Guards are encapsulated logic for logging in, registering and fetching users. The web guard uses a cookie driver which sets a token cookie which is used later to fetch the user.

You can switch the guard on the fly to attempt authentication on different guards:

from masonite.authentication import Auth
from masonite.request import Request

def login(self, auth: Auth, request: Request):
  user = auth.guard("custom").attempt(request.input('email'), request.input("password")) #== <app.User.User>
Service Container
Service Container
environment variables

Error Handling

Masonite error handling is based on the ExceptionHandler class which is responsible for handling all exceptions thrown by your application.

Global Exception Handler

Debug Mode

When disabled, the default errors/500.html, errors/404.html, errors/403.html error pages are rendered depending on error type.

Never deploy an application in production with debug mode enabled ! This could lead to expose some sensitive configuration data and environment variables to the end user.

Lifecycle

When an exception is raised it will be caught by the ExceptionHandler. Then the following cycle will happen:

In Development

  1. A masonite.exception.SomeException event will be fired.

  2. A specific ExceptionHandler will be used if it exists for the given exception.

  3. Exceptionite will then handle the error by rendering it in the console

    • and with the Exceptionite JSON response if accepted content is application/json

    • else with the Exceptionite HTML error page

In Production

  1. A masonite.exception.SomeException event will be fired.

  2. A specific ExceptionHandler will be used if it exists for the given exception. In this case the default ExceptionHandler won't process the exception anymore.

  3. Else this exception has not been yet handled and will be handled as an HTTP exception with 500 Server Error status code.

Report Exceptions

To report an exception you should simply raise it as any Python exceptions:

def index(self, view:View):
    user = User.find(request.param("id"))
    if not user:
        raise RouteNotFoundException("User not found")

There are different type of exceptions.

Simple Exceptions

HTTP Exceptions

HTTP exceptions are standard exceptions using frequently used HTTP status codes such as 500, 404 or 403.

Those exceptions will be rendered by the HTTPExceptionHandler with the corresponding status code and the corresponding default error template if it exists in errors/. (Note that in debug mode this template won't be rendered and the default Exceptionite error page will be rendered instead).

The following HTTP exceptions are bundled into Masonite:

  • AuthorizationException (403)

  • RouteNotFoundException (404)

  • ModelNotFoundException (404)

  • MethodNotAllowedException (405)

In a default Masonite project, existing errors views are errors/404.html, errors/403.html and errors/500.html. Those views can be customized.

You can also build a custom HTTP exception by setting is_http_exception=True to it and by defining the get_response(), get_status() and get_headers() methods:

class ExpiredToken(Exception):
    is_http_exception = True

    def __init__(self, token):
        super().__init__()
        self.expired_at = token.expired_at

    def get_response(self):
        return "Expired API Token"

    def get_status(self):
        return 401

    def get_headers(self):
        return {
            "X-Expired-At": self.expired_at
        }

When the above exception is raised, Masonite will look for the error view errors/401.html in the default project views folder and will render it with a 401 status code. The content of get_response() method will be passed as the message context variable to the view.

If this view does not exist the HTML response will be directly the content of the get_response() method with a 401 status code.

Renderable Exceptions

If you want more flexibility to render your exception without using the HTTPExceptionHandler above, you can just add a get_response() method to it. This method will be given as first argument of response.view(), so that you can render simple string or your own view template.

class CustomException(Exception):

    def __init__(self, message=""):
        super().__init__(message)
        self.message = message

    def get_response(self):
        return self.message

Here when raising this exception in your code, Masonite will know that it should render it by calling the get_response() method and here it will render as a string containing the message raised.

Note that you can also set an HTTP status code by adding - as for HTTP exceptions - the get_status() method to the exception.

class CustomException(Exception):

    def __init__(self, message=""):
        super().__init__(message)
        self.message = message

    def get_response(self):
        return self.message

    def get_status(self):
        return 401

Existing Exception Handlers

Existing Exception Handlers are:

  • HTTPExceptionHandler

  • DumpExceptionHandler

You can build your own Exception Handlers to override the way Masonite handles an exception that is thrown or to add new behaviours.

Adding New Handlers

A handler is a simple class with a handle() method that Masonite will call when a specific exception is thrown by the application.

As an example, you could handle specifically ZeroDivisionError exceptions that could be thrown by your application. It will look like this:

class DivideException:

    def __init__(self, application)
        self.application = application

    def handle(self, exception):
        self.application.make('response').view({
            "error": str(exception),
            "message": "You cannot divide by zero"
        }, status=500)

The name of the class can be whatever you like. In the handle method you should manually return a response by using the response class.

You will then need to register the class to the container using a specific key binding. The key binding will be {exception_name}Handler. You can do this in your Kernel file.

To register a custom exception handler for our ZeroDivisionError we would create a binding that looks like this:

from app.exceptions.DivideException import DivideException

self.application.bind(
    "ZeroDivisionErrorHandler",
    DivideException(self.application)
)

You can add this binding in your AppProvider or in Kernel.py.

Now when your application throws a ZeroDivisionError, Masonite will use your handler rather than Masonite's own exception handlers.

Catch All Exceptions

If you want to hook up an error tracking service such as Sentry or Rollbar you can do this through event listeners: each time an exception is raised, a masonite.exception.{TheExceptionType} is fired, allowing to run any custom logic.

class SentryListener:

    def handle(self, exception_type: str, exception: Exception):
        # process the exception with Sentry
        # ...
class AppProvider(Provider):

    def register(self):
        self.application.make("event").listen("masonite.exception.*", [SentryListener])

Views

Views in Masonite are a great way to return HTML in your controllers. Views have a hierarchy, lots of helpers and logical controls, and a great way to separate out your business logic from your presentation logic.

Getting Started

By default, all templates for your views are located in the templates directory. To create a template, simply create a .html file inside this directory which we will reference to later.

<!-- Template in templates/welcome.html -->
<html>
    <body>
        <h1>Hello, {{ name }}</h1>
    </body>
</html>

Then inside your controller file you can reference this template:

from masonite.views import View

class WelcomeController(Controller):

  def show(self, view: View):
    view.render('welcome', {
    	"name": "Joe"
    })

The first argument here is the name of your template and the second argument should be a dictionary of variables you reference inside your template.

If you have a template inside another directory you can use dot notation to reference those template names

from masonite.views import View

class WelcomeController(Controller):

  # Template is templates/greetings/welcome.html
  def show(self, view: View):
    view.render('greeting.welcome', {
    	"name": "Joe"
    })

View Sharing

View sharing is when you want a variable to be available in all templates that are rendered. This could be the authenticated user, a copyright year, or anything else you want shared between templates.

View sharing requires you to add a dictionary:

from masonite.facades import View

View.share({
  'copyright': '2021'
})

Then inside your template you can do:

<div>
  Copyright {{ copyright }}
</div>
from masonite.facades import View

class AppProvider(Provider):

    def register(self):
        View.share({'copyright': '2021'})

View Composing

Similiar to view sharing, view composing allows you to share templates between specific templates

View.composer('dashboard', {'copyright': '2021'})

You may also pass a list of templates:

View.composer(['dashboard', 'dashboard/users'], {'copyright': '2021'})
from masonite.facades import View

class AppProvider(Provider):

    def register(self):
        View.composer('dashboard', {'copyright': '2021'})

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>

Asset

You can get the location of static assets:

You can create a path to an asset by using the asset helper:

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

this will render a URL like this:

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

See your filesystems.py configuration for how how to set the paths up.

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>

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>

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, response: Response):
    # Some failed validation
    return response.back()

Session

You can access the session here:

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

Config

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

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

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>

DD

To help in debugging, you can use the dd() helper

{{ dd(variable) }}

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.

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 Masonite, 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

from masonite.facades import View


class AppProvider(Provider):

    def register(self):
        View.('slug', self.slug)

    @staticmethod
    def slug(item):
        return item.replace(' ', '-')

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>
    

<div data-gb-custom-block data-tag="if"></div>

        hey boss
    

<div data-gb-custom-block data-tag="else">

        you are an employee
    

</div>

</div>

The code is simple and looks something like this:

from masonite.facades import View


def a_company_owner(user):
    # Returns True or False
    return user.owner == 1

class AppProvider(Provider):

    def register(self):
        # 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 Environments

Environments in views are directories of templates. If you are development packages or building a modular based application, you may have to register different directories for templates. This will allow Masonite to locate your views when referencing them to render. A good place to do this is inside a service provider's register method.

There are 2 separate kinds of loaders.

The first loader is a "package" loader. This is used when registering a package. To do this you can simply register it with the package module path. This will work for most directories that are packages.

from masonite.facades import View

View.add('module_name/directory')

The other loader is a FileSystem loader. This should be used when the directory path is NOT a module but rather just a file system path:

from jinja2.loaders import FileSystemLoader
from masonite.facades import View

View.add(
    os.path.join(
        os.getcwd(), 'package_views'
    )
, loader=FileSystemLoader)

View Syntax

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:

<div data-gb-custom-block data-tag="if">

    <p>do something</p>

</div>

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

<div data-gb-custom-block data-tag="if"> 'something' </div>">

</form>

Whichever syntax you choose is up to you.

Note that if you are using an @ symbol that should not be rendered with Masonite then this will throw an error. An example being when you are using @media tags in CSS. In this case you will need to wrap this statement inside `` and `

` blocks.

Variables

You can show variable or string 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:

<div data-gb-custom-block data-tag="if"></div>

    <p>do something</p>

<div data-gb-custom-block data-tag="elif"></div>

    <p>do something else</p>

<div data-gb-custom-block data-tag="else">

    <p>above all are false</p>

</div>

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:

<div data-gb-custom-block data-tag="for">

    <p>{{ item }}</p>

</div>

Include statement

An include statement is useful for including other templates.

Line Statements:

@include 'components/errors.html'

<form action="/">

</form>

Using alternative Jinja2 syntax:

<div data-gb-custom-block data-tag="include" data-0='components/errors.html'></div>

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

<div data-gb-custom-block data-tag="extends" data-0='components/base.html'></div>

<div data-gb-custom-block data-tag="block">

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

</div>

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>
        

<div data-gb-custom-block data-tag="block">

        <!-- block named "css" defined in child template will be inserted here -->
        

</div>

    </head>

<body>
    <div class="container">
        

<div data-gb-custom-block data-tag="block">

        <!-- block named "content" defined in child template will be inserted here -->
        

</div>

    </div>

<div data-gb-custom-block data-tag="block">

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

</div>

</body>
</html>
<!-- components/blocks.html -->

<div data-gb-custom-block data-tag="extends" data-0='components/base.html'></div>

<div data-gb-custom-block data-tag="block">

    <link rel=".." ..>

</div>

<div data-gb-custom-block data-tag="block">

    <p> This is content </p>

</div>

<div data-gb-custom-block data-tag="block">

    <script src=".." />

</div>

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.

API Development

Adding API Support to your Masonite project is very simple. Masonite comes with supporting for returning JSON responses already but there are a few API features for authenticating and authorizing that are helpful.

Default projects don't come with these features so you'll have to simply register them.

Getting Started

Provider

First, register the ApiProvider in your project by adding the service provider to your PROVIDERS list:

This will register some required classes used for API development.

You should now create an api.py file for adding your API routes to:

You will then have to add a binding to the container for the location of this file. You can do so in your Kernel.py file inside the register_routes method:

Any routes inside this file will be grouped inside an api middleware stack.

Model and Migrations

Next, you must choose a model you want to be responsible for authentication. This is typically your User model. You will have to inherit the AuthenticatesTokens class onto this model:

This will allow the model to save tokens onto the table.

Next you will add a column to the models table for saving the token. Here we are naming it api_token but this is configurable by adding a __TOKEN_COLUMN__ attribute to your model. Your migration file should look like this:

Then migrate your migrations by running:

Configuration

Next, you can create a new API config file. You can do so simply by running the install command:

This will create a new api.py config file in your configuration directory that looks like this:

This will attempt to import your user model but if you have a different model or if its in a different location you can change it in that model.

This command will also generate a secret key, you should store that secret key in an environment variable called JWT_SECRET. This will be used as a salt for encoding and decoding the JWT token.

The authenticates key is used as a check to check against the database on every request to see if the token is set on the user. By default, the database is not called to check if the token is assigned to a user. One of the benefits of JWT is the need to not have to make a database call to validate the user but if you want that behavior, you can set this option to True

Routes

You should add some routes to your web.py file which can be used to authenticate users to give them JWT tokens:

The above parameters are the defaults already so if you don't want to change them then you don't have to specify them.

Middleware

Since the routes in your api.py file are wrapped in an api middleware, you should add a middleware stack in your route middleware in your Kernel file:

This middleware will allow any routes set on this stack to be protected by JWT authorization.

By default, all routes in the routes/api.py file already have the api middleware stack on them so there is no need to specify the stack on all your API routes.

Once these steps are done, you may now create your API's

Creating Your API

Once the setup is done, we may start building the API.

Route Resources

One of the ways to build an API is using controller and route resources.

A controller resource is a controller with several methods on them used to specify each action within an entity in the application (like users).

To create a controller resource you can run the controller command with an -a flag:

This will create a controller with the following methods:

  • index

  • show

  • store

  • update

  • destroy

You can then create a route resource:

This will create the below routes which will match up with your API Controller methods:

Authentication

The routes we added earlier contain 2 authentication methods. The /api/auth route can be used to get a new authentication token:

First, send a POST request with a username and password to get back a JWT token:

You should then send this token with either a token input or a Authorization header:

Reauthentication

If you do not set a value for the expires key in the configuration file then your JWT tokens will not expire and will be valid forever.

If you do set an expiration time in your configuration file then the JWT token will expire after that amount of minutes. If the token expires, you will need to reauthenticate to get a new token. This can be done easily by sending the old token to get back a new one:

You can do this by sending a POST request to /api/reauth with a token input containing the current JWT token. This will check the table for the token and if found, will generate a new token.

Versions

One of the issues with JWT tokens is there is little that can be done to invalidate JWT tokens. Once a JWT token is valid, it is typically valid forever.

One way to invalid JWT tokens, and force users to reauthenticate, is to specify a version. A JWT token authenticated will contain a version number if one exists. When the JWT token is validated, the version number in the token will check against the version number in the configuration file. If they do not match then the token is considered invalid and the user will have to reauthenticate to get back a new token.

Loading Users

Since we store the active api_token on the table we are able to retrieve the user using the LoadUserMiddleware and a new guard route middleware stack:

First add the guard middleware stack and add the LoadUserMiddleware to the api stack.

Lastly, in your route or route groups you can specify the guard middleware and specify the guard name:

Commands

Commands in Masonite are generally designed to make your development life easier. Commands can range from creating controllers and models to installing packages and publishing assets.

Available commands for Masonite can be displayed by running:

This will show a list of commands already available for Masonite.

Every command has a documentation screen which describes the command's available arguments and options. In order to view a command documentation, prefix the name of the command with help. For example, to see help of serve command you can run:

Creating Commands

Commands can be created with a simple command class inheriting from Masonite Command class:

Command's name, description and arguments are parsed from the Command docstring.

Name and Description

The docstring should start with the description of the command

and then after a blank line you can define the command name.

Positional Arguments

After the name of the command in the docstring should come the arguments. Arguments are defined with one indent and enclosed into brackets.

Positional (mandatory) arguments are defined without dashes (- or --).

Here is how to define a positional argument called name with a description:

Inside the command, positional arguments can be retrieved with self.argument(arg_name)

Optional Arguments

Optional arguments are defined with dashes and can be used in any order in the command call. An optional argument --force can have a short name --f.

Here is how to define two optional arguments iterations and force with a description:

Notice how we provided the short version for the force argument but not for the iterations arguments

Now the command can be used like this:

If the optional argument is requiring a value you should add the = suffix:

Here when using iterations, the user should provide a value.

If the argument may or may not have a value, you can use the suffix =? instead.

Finally if a default value should be used when no value is provided, add the suffix ={default}:

Inside the command, optional arguments can be retrieved with self.option(arg_name)

Printing Messages

You can print messages to console with different formatting:

  • self.info("Info Message"): will output a message in green

  • self.warning("Warning Message"): will output a message in yellow

  • self.error("Error Message"): will output a message in bold red

  • self.comment("Comment Message"): will output a message in light blue

Advanced

Masonite Command class is inheriting Cleo Command class so you should be able to use all Cleo features when creating commands.

Registering Commands

add() method takes one or multiple commands:

When you run python craft you will now see the command you added.

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:

This will add a hidden field that looks like:

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

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:

And then you can fetch the token and put it wherever you need:

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:

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:

This can get a bit repetitive so you may specify a wildcard instead:

Broadcasting

Masonite comes with a powerful way to broadcast events in your application. These events can be listened to client-side using Javascript.

These can be things like a new notification which you can show on the frontend without reloading the page.

Configuration

Server Side

You should create an account on Pusher Channels and then create a Pusher application on your account and get the related credentials (client, app_id, secret) and the cluster location name and put this into the broadcast pusher options.

Finally make sure you install the pusher python package

Client Side

Include the pusher-js script tag on your page

Create a Pusher instance configured with your credentials

Now you're ready to subscribe to channels and listen for channels events.

Creating Events

Broadcast events are simple classes that inherit from CanBroadcast. You may use any class, including the Masonite Event classes.

A broadcast event will look like this:

Note that the event name emitted to the client will be the name of the class. Here it would be UserAdded.

Broadcasting Events

You can broadcast the event using the Broadcast facade or by resolving Broadcast class from container.

You can broadcast easily without creating a Broadcast event

Or you can broadcast the event class created earlier

You may broadcast on multiple channels as well:

This type of broadcasting will emit all channels as public. For private and presence channels, keep reading.

Listening For Events

To listen for events on client-side you must first subscribe to the channel the events are emitted on

Then you can listen for events

Channel Types

Different channel types are included in Masonite.

Public Channels

Inside the event class you can specify a Public channel. These channels allow anyone with a connection to listen to events on this channel:

Private Channels

Private channels require authorization from the user to connect to the channel. You can use this channel to emit only events that a user should listen in on.

Private channels are channels that start with a private- prefix. When using private channels, the prefix will be prepended for you automatically.

Private channels can only be broadasting on if users are logged in. When the channel is authorized, it will check if the user is currently authenticated before it broadcasts. If the user is not authenticated it will not broadcast anything on this channel.

This will emit events on the private-channel_name channel.

Routing

On the frontend, when you make a connection to a private channel, a POST request is triggered by the broadcast client to authenticate the private channel. Masonite ships with this authentication route for you. All you need to do is add it to your routes:

This will create a route you can authenticate you private channel on the frontend. The authorization route will be /broadcasting/authorize but you can change this to anything you like:

You will also need to add the /pusher/user-auth route to the CSRF exemption.

The reason for this is that the broadcast client will not send the CSRF token along with the POST authorization request.

Authorizing

The default behaviour is to authorize everyone to access any private channels.

If you want to customize channels authorization logic you can add your own broadcast authorization route with a custom controller. Let's imagine you want to authenticate channels per users, meaning that user with ID 1 will be able to authenticate to channel private-1, user with ID 2 to channel private-2 and so on.

First you need to remove Broadcast.routes() from your routes and add your own route

Then you need to create a custom controller to implement your logic

Presence Channels

Presence channels work exactly the same as private channels except you can see who else is inside this channel. This is great for chatroom type applications.

For Presence channels, the user also has to be authenticated.

This will emit events on the presence-channel_name channel.

Routing

Authorizing

Examples

To get started more easily with event broadcasting in Masonite, two small examples are available here:

Sending public app releases notification

To do this we need to create a NewRelease Broadcast event and trigger this event from the backend.

On the frontend we need to listen to releases channel and subscribe to NewRelease events to display an alert box with the release message.

Sending private alerts to admin users

Let's imagine our User model has two roles basic and admin and that we want to send alerts to admin users only. The basic users should not be authorized to subscribe to the alerts.

To achieve this on the backend we need to:

  • create a custom authentication route to authorize admin users only on channel private-admins

  • create a AdminUserAlert Broadcast event

  • trigger this event from the backend.

Let's first create the authentication route and controller

On the frontend we need to listen to private-admins channel and subscribe to AdminUserAlert events to display an alert box with the message.

You're ready to start broadcasting events in your app !

Compiling Assets

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:

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:

This will move these 2 files, resources/js/app.js and resources/css/app.css and compile them both into the storage/compiled directory.

Installing TailwindCSS

Installing Vue

Compiling

Now that we have our compiled assets configured we can now actually compile them.

You can do so by running:

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 in frontend files. This is similiar to an auto reloading server. To do this just run:

Versioning

Laravel Mix can take care of file hashing when releasing assets to production, by adding a hash suffix to your assets to automatically bust cache when loading assets. To enable this you can add the following in your webpack.mix.js file:

After Laravel Mix compiled your assets you won't be able to know the exact filename (because of the hash) you should use in your views to reference your assets. You can use Masonite mix() helper to resolve the correct file path to your assets.

Caching

Masonite provides a powerful caching feature to keep any data cached that may be needless or expensive to fetch on every request. Masonite caching allows you to save and fetch data, set expiration times and manage different cache stores.

We'll walk through how to configure and use cache in this documentation.

Configuration

Cache configuration is located at config/cache.py file. In this file, you can specify different cache stores with a name via the STORES dictionary and the default to use in your application with the default key.

Masonite is configured to use the File cache driver by default, named local.

For production applications, it is recommended to use a more efficient driver such as Memcached or Redis.

File Cache

File cache driver is storing data by saving it on the server's filesystem. It can be used as is without third party service.

Location where Masonite will store cache data files defaults to storage/framework/cache in project root directory but can be changed with location parameter.

Redis

Redis cache driver is requiring the redis python package, that you can install with:

Then you should define Redis as default store and configure it with your Redis server parameters:

Memcached

Memcached cache driver is requiring the pymemcache python package, that you can install with:

Then you should define Memcached as default store and configure it with your Memcached server parameters:

Using the Cache

Storing Data

Two methods are available: add and put.

add

You can easily add cache data using theadd method. This will either fetch the data already in the cache, if it is not expired, or it will insert the new value.

If age key exists in the cache AND it is not expired, then "21" will be added to the cache and returned. If the age key does not exist or is not expired then it will return whatever data is in the cache for that key.

put

The put method will put data into the cache regardless of if it exists already. This is a good way to overwrite data in the cache:

You can specify the number of seconds that the cache should be valid for. Do not specify any time or specify None to keep the data forever.

You may also cache lists and dictionaries which will preserve the data types:

Getting Data

You can get cache data from the cache. If the data is expired then this will either return None or the default you specify:

This will either fetch the correct age data from the cache or return the default value of 40.

Checking Data Exists

You can also see if a value exists in the cache (and it is not expired):

Forgetting Data

If you want to forget an item in the cache you can:

This will remove this item from the cache.

Increment / Decrement Value

You can increment and decrement a value if it is an integer:

Remembering

Remembering is a great way to save something to a cache using a callable:

Flushing the Cache

To delete everything in the cache you can simply flush it:

Filesystem and Uploading

Masonite comes with a simple way to upload files to different file systems.

In Masonite, a Filesystem is a place where assets are stored. These locations could be somewhere local on the server or in a cloud storage service like Amazon S3.

Configuration

The configuration for the filesystem feature is broken up into "disks". These "disks" or connection configurations can be any name you want.

Here is an example configuration:

Default

The default configuration is the name of the disk you want to be used as the default when using the Filesystem features.

Local Driver

The local driver is used for local filesystems like server directories. All files are stored and managed "locally" on the server.

S3 Driver

The S3 driver is used for connecting to Amazon's S3 cloud service.

Uploading Files

Uploading files is simple. You will have to use the Masonite Storage class.

The first and most simplest method is taking a file and putting text into it. For this we can use the put method

Uploading Form Files

When uploading files from a form you will find the put_file method more useful:

The put_file method will return the relative path to the file so you can save it to the database and fetch it later.

By default, a file name will be auto generated for you using a UUID4 string. You can specify your own name by using a name parameter:

You do not need to specify the extension in the name as the extension will be pulled from the resource object.

Asset Helper

Displaying Files

When uploading images to something like an AWS bucket, you may want to display the images. You may use a combination of the asset helper and setting a path in your filesystem config. This mainly just provides a nice interface for combining 2 strings

When using Amazon S3, you will need to set your bucket permissions and policies appropriately.

First, set a path in your filesystem config:

Then in your templates you can use the asset helper:

The signature is:

Multiple Paths

You may also specify multiple paths as a dictionary:

Then inside your asset helper you can use dot notation to specify the path you want to use:

Creating A Blog Tutorial

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:

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 simply a class that inherits from Masonite's Controller class and contains controller methods. These controller methods will be what our routes will call so they will contain most 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.

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/controllers directory by default and Masonite promotes a 1 controller per file structure. 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:

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

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 Masonite'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 code in between that we are not worried about:

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 templates directory. We can create a new file called templates/blog.html.

We can put some text in this file like:

Let's run the migration for the first time:

and then run the server

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:

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.

You can then add the authentication routes to your project:

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

Database Setup

In order to register these users, we will need a database. By default, Masonite uses SQLite. If you want to use a different database you can change the options that start with DB_ in your .env file. For running MySQL or Postgres you will need to have those databases setup already.

We have already run the migration command before, which was:

If you want to use MySQL, open up the .env file in the root of your project and change the DB_DATABASE to mysql. Also, feel free to change the DB_DATABASE name to something else.

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.

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:

Migrations

We have looked at running migrations but let's look at how to create a new migration.

Now that we have our authentication setup and we are comfortable with migrating our application, 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.

Craft Command

This command simply creates the start of a migration file that we will use to 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:

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

Now we can migrate this migration to create the posts table

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 an Active Record ORM. 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:

Notice we used the singular form for our model. By default, Masonite ORM will check for the plural name of the class in our database (in this case posts) by assuming the name of the table is the plural word of the model name. We will talk about how to specify the table explicitly in a bit.

The model created now resides inside app/models/Post.py and when we open it up it should look like:

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 name but if you called your table something different such as "user_posts" instead of "posts" we can specify the table name explicitly:

Mass Assignment

Masonite ORM 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 (this way we can pass the column names into the create and update methods later).

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:

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

Notice here we have a {{ csrf_field }} below the <form> open tag. Masonite comes with CSRF protection so we need a token to render the hidden field with the CSRF token.

Now we need to make sure the user is logged in before creating this so let's change up our template a bit:

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

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

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:

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 the routes/web.py file and create a new route. Just add this to the ROUTES list:

and create a new store method on our controller:

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.

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 are done exactly the same way by using this input method.

Go ahead and run the server using craft serve again 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. Now that we are more comfortabale using the framework, in this part we will create 2 new templates to show all posts and an individual post.

Creating The Templates

Let's create 2 new templates.

  • templates/posts.html

  • templates/single.html

Let's start with showing all posts

Creating The Controller

Let's create a controller for the posts to separate it out from the BlogController.

Great! So now in our show method we will show all posts and then we will create a single method to show a specific post.

Show Method

Let's get the show method to return the posts view with all the posts:

Posts Route

We need to add a route for this method:

Posts View

Our posts view can be very simple:

Go ahead and run the server and head over to 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. Masonite ORM will take that relationship and make an attribute from it so we can display the author's name as well:

Let's repeat the process but change our workflow a bit.

Single Post Route

Next we want to just show a single post. We need to add a route for this method:

Notice here we have a @id string. We can use this to grab that section of the URL in our controller in the next section below. This is like a route URL capture group.

Single Method

Let's create a single method so we show a single post.

We use the param() method to fetch the id from the URL. Remember this key was set in the route above when we specified the @id

For a real application we might do something like @slug and then fetch it with request().param('slug').

Single Post View

We just need to display 1 post so lets just put together a simple view:

Go ahead and run the server and head over the 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 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 doing in the previous parts.

Update Controller Method

Let's just make an update method on the PostController:

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

  • templates/update.html

Create The Routes:

Remember we made 2 controller methods so let's attach them to a route here:

That should be it! We can now update our posts.

Delete Method

Let's expand a bit and make a delete method.

Make the route:

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:

Great! You now have a blog that you can use to create, view, update and delete posts! Go on to create amazing things!

Facades

Overview

you can do:

To import any built-in facades you can import them from the masonite.facades namespace:

Built-in Facades

Masonite ships with several facades you can use out of the box:

  • Auth

  • Broadcast

  • Config

  • Dump

  • Gate

  • Hash

  • Loader

  • Mail

  • Notification

  • Queue

  • Request

  • Response

  • Session

  • Storage

  • View

  • Url

Creating Your Own Facades

To create your own facade is simple:

Then import and use your facade:

To benefit from code intellisense/type hinting when using Facade (e.g. in VSCode editor) you should create a .pyi file just next to your facade file class to add type hinting to your new facade.

Then in this facade you should declare your typed methods. Here is a partial example of the Mail facades types hints:

Notice that:

  • methods code is not present, but replaced with ....

  • methods do not receive self as first argument. Because when calling the facade we don't really instantiate it (even if we get an instance of the object bound to the container). This allow to have the correct type hint behaviour.

All exceptions are handled by the ExceptionHandler class which is bound to the through exception_handler key.

This handler has a logic to decide how to handle exceptions depending on the exception type, the environment type, the request accepted content type and the .

This handler has by default one driver which is responsible of handling errors in development by providing a lot of context to help debug your application.

When is enabled all exceptions are rendered through Exceptionite HTML debug page.

If exception is an it will be handled by the HTTPExceptionHandler which is responsible for selecting an error template (errors/500.html, errors/404.html, errors/403.html) and rendering it.

If exception is a it will be rendered accordingly.

Simple exceptions will be handled as a 500 Server Error if no are defined for it.

First to run your custom logic:

In a you then need to register this listener:

You will typically do this inside your app (if you don't have one, you should ):

You will typically do this inside your app (if you don't have one, you should ):

Learn more about session in the documentation.

Adding filters is typically done inside your app (if you don't have one, you should ):

In order to do this we need to add a test on the View class. We can once again do this inside the app

Method
URL
Action
Route Name

Masonite uses the package for shell command feature.

You can find more information and more features for creating commands in .

Once created you can register the command to Masonite's inside a (if you don't have one, you should ):

Masonite comes with one server-side driver: .

Server side configuration for broadcasting is done in config/broadcast.py configuration file. For now there is one driver available .

To be able to receive broadcast events in the browser you should install .

This is the quickest way to install Pusher. But for a real app you will often use a build system to and will install the Javascript Pusher SDK with npm install pusher-js and then import Pusher class with import Pusher from 'pusher-js';.

It is advised to use environment variables instead of hard-coding credentials client-side. If you're using Laravel Mix to then you should prefix your environment variables with MIX_.

In this section we will use the client pusher instance .

Pusher is expecting the authentication route to be /pusher/user-auth . If you want to change this client-side you can do it when creating Pusher instance

If you want to keep CSRF protection you can read more about it .

Adding the authentication route is the same as for .

Authorizing channels is the same as for .

Sending public app releases notification to every users (using )

Sending private alerts to admin users (using )

Feel free to change which directories the files get compiled to. For more information on additional configuration values take a look at the

Laravel is using Laravel mix so you can just follow guidelines to setup TailwindCSS for Laravel Mix on .

Please follow the guidelines directly on

More information on this feature in .

Masonite supports the following cache drivers: , and a basic driver.

Finally ensure that the Redis server is running and you're ready to .

Finally ensure that the Memcached server is running and you're ready to .

You can access Cache service via the Cache facade or by resolving it from the .

Option
Description
Option
Description
Method
Description

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

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 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, refer to 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.

Facades are an easy way to access the classes without making them from the .

Facades are just a shortcut to resolve a key in the . Instead of doing:

Service Container
Exceptionite
Service Provider
Session
Service Provider
configured exception handlers
HTTP exception
Renderable exception
custom exceptions handlers
Debug mode
from masonite.api.providers import ApiProvider

PROVIDERS = [
    #..
    ApiProvider
]
# routes/api.py

ROUTES = [
    Route.get('/users', 'UsersController@index')
]
def register_routes(self):
    #..
    self.application.bind("routes.api.location", "routes/api")
from masoniteorm.models import Model
from masonite.api.authentication import AuthenticatesTokens

class User(Model, AuthenticatesTokens):
    #..
with self.schema.table("users") as table:
    table.string("api_token").nullable()
$ python craft migrate
python craft api:install
"""API Config"""
from app.models.User import User
from masonite.environment import env

DRIVERS = {
    "jwt": {
        "algorithm": "HS512",
        "secret": env("JWT_SECRET"),
        "model": User,
        "expires": None,
        "authenticates": False,
        "version": None,
    }
}
from masonite.api import Api

ROUTES += [
    #.. web routes
]

ROUTES += Api.routes(auth_route="/api/auth", reauth_route="/api/reauth")
# Kernel.py

from masonite.api.middleware import JWTAuthenticationMiddleware
#.. 

class Kernel:

    # ..

    route_middleware = {
        # ..
        "api": [
            JWTAuthenticationMiddleware
        ],
    }
$ python craft controller api/UsersController -a
# routes/api.py
from masonite.routes import Route

ROUTES = [
    # ..
    Route.api('users', "api.UserController")
]

GET

/users

index

users.show

GET

/users/@id

show

users.show

POST

/users

store

users.store

PUT/PATCH

/users/@id

update

users.update

DELETE

/users/@id

destroy

users.destroy

{
    "data": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJleHBpcmVzIjpudWxsLCJ2ZXJzaW9uIjpudWxsfQ.OFBijJFsVm4IombW6Md1RUsN5v_btPwl-qtY-QSTBQ0b7N2pca8BnnT4OjfXOVRrKCWaKUM3QsGj8zqxCD4xJg"
}
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJleHBpcmVzIjpudWxsLCJ2ZXJzaW9uIjpudWxsfQ.OFBijJFsVm4IombW6Md1RUsN5v_btPwl-qtY-QSTBQ0b7N2pca8BnnT4OjfXOVRrKCWaKUM3QsGj8zqxCD4xJg 
##.. 
"api": [
    JWTAuthenticationMiddleware,
    LoadUserMiddleware
],
"guard": [
    GuardMiddleware
],
# api.py
ROUTES = [
    #..
    Route.get('/users', 'UserController@show').middleware(["guard:jwt"])
]
python craft
python craft help serve
from masonite.commands import Command

class MyCommand(Command):
    """
    Description of Command

    command:signature
        {user : A positional argument for the command}
        {--f|flag : An optional argument for the command}
        {--o|option=default: An optional argument for the command with default value}
    """

    def handle(self):
        pass
"""
Description of Command

"""
"""
Description of Command

command_name
"""
"""
    {name : Description of the name argument}
"""
    def handle(self):
        name = self.argument("name")
"""
command_name
    {--iterations : Description of the iterations argument}
    {--f|force : Description of the force argument}
"""
python craft command_name --f --iterations
python craft command_name --iterations --force
"""
command_name
    {--iterations= : Description of the iterations argument}
"""
python craft command_name --iterations 3
python craft command_name --iterations=3
"""
command_name
    {--iterations=?: Description of the iterations argument}
"""
python craft command_name --iterations
python craft command_name --iterations 3
"""
command_name
    {--iterations=3: Description of the iterations argument}
"""
# iterations will be equal to 3
python craft command_name --iterations
# iterations will be equal to 1
python craft command_name --iterations 1
    def handle(self):
        name = self.option("iterations")
from some.place.YourCommand import YourCommand

class AppProvider(Provider):
    def __init__(self, application):
        self.application = application

    def register(self):
        self.application.make('commands').add(YourCommand())
<form action="/dashboard" method="POST">
    {{ csrf_field }}

    <input type="text" name="first_name">
</form>
<input type="hidden" name="__token" value="8389hdnajs8...">
<p> Token: {{ csrf_token }} </p>
<meta name="csrf-token" content="{{ csrf_token }}">
token = document.head.querySelector('meta[name="csrf-token"]')
from masonite.middleware import VerifyCsrfToken as Middleware

class VerifyCsrfToken(Middleware):

    exempt = ['/oauth/github']
from masonite.middleware import VerifyCsrfToken as Middleware

class VerifyCsrfToken(Middleware):

    exempt = [
        '/api/document/reject-reason',
        '/api/document/*/reject',
        '/api/document/*/approve',
        '/api/document/*/process/@user',
    ]

    ...
from masonite.middleware import VerifyCsrfToken as Middleware

class VerifyCsrfToken(Middleware):

    exempt = [
        '/api/document/*',
    ]

    ...
"pusher": {
    "driver": "pusher",
    "app_id": "3456678"
    "client": "478b45309560f3456211" # key
    "secret": "ab4229346et64aa8908"
    "cluster": "eu",
    "ssl": False,
},
pip install pusher
<script src="https://js.pusher.com/7.0.3/pusher.min.js"></script>
const pusher = new Pusher("478b45309560f3456211", {
  cluster: "eu",
});
from masonite.broadcasting import CanBroadcast, Channel

class UserAdded(CanBroadcast):

    def broadcast_on(self):
        return Channel("channel_name")
from masonite.facades import Broadcast

Broadcast.channel('channel_name', "event_name", {"key": "value"})
from masonite.facades import Broadcast
from app.broadcasts import UserAdded

broadcast.channel(UserAdded())
Broadcast.channel(['channel1', 'channel2'], "event_name", {"key": "value"})
const channel = pusher.subscribe("my-channel");
channel.bind("my-event", (data) => {
  // Method to be dispatched when event is received
});
from masonite.broadcasting import Channel

class UserAdded(CanBroadcast):

    def broadcast_on(self):
        return Channel("channel_name")
from masonite.broadcasting import PrivateChannel

class UserAdded(CanBroadcast):

    def broadcast_on(self):
      return PrivateChannel("channel_name")
from masonite.broadcasting import Broadcast

ROUTES = [
  # Normal route list here
]

ROUTES += Broadcast.routes()
ROUTES += Broadcast.routes(auth_route="/pusher/user-auth")
const pusher = new Pusher("478b45309560f3456211", {
  cluster: "eu",
  userAuthentication: {
    endpoint: "/broadcast/auth",
  },
});
class VerifyCsrfToken(Middleware):

    exempt = [
        '/pusher/user-auth'
    ]
# routes/web.py
Route.post("/pusher/user-auth", "BroadcastController@authorize")
# app/controllers/BroadcastController.py
from masonite.controllers import Controller
from masonite.broadcasting import Broadcast
from masonite.helpers import optional


class BroadcastController(Controller):

    def authorize(self, request: Request, broadcast: Broadcast):
        channel_name = request.input("channel_name")
        _, user_id = channel_name.split("-")

        if int(user_id) == optional(request.user()).id:
            return broadcast.driver("pusher").authorize(
                channel_name, request.input("socket_id")
            )
        else:
            return False
from masonite.broadcasting import PresenceChannel

class UserAdded(CanBroadcast):

    def broadcast_on(self):
      return PresenceChannel("channel_name")
# app/broadcasts/NewRelease.py
from masonite.broadcasting import CanBroadcast, Channel

class NewRelease(CanBroadcast):

    def __init__(self, version):
        self.version = version

    def broadcast_on(self):
        return Channel("releases")

    def broadcast_with(self):
        return {"message": f"Version {self.version} has been released !"}
from masonite.facades import Broadcast
from app.broadcasts import NewRelease

Broadcast.channel(NewRelease("4.0.0"))
<html lang="en">
<head>
  <title>Document</title>
  <script src="https://js.pusher.com/7.0/pusher.min.js"></script>
</head>
<body>
  <script>
    const pusher = new Pusher("478b45309560f3456211", {
      cluster: "eu"
    });

    const channel = pusher.subscribe('releases');
    channel.bind('NewRelease', (data) => {
      alert(data.message)
    })
  </script>
</body>
</html>
# routes/web.py
Route.post("/pusher/user-auth", "BroadcastController@authorize")
# app/controllers/BroadcastController.py
from masonite.controllers import Controller
from masonite.broadcasting import Broadcast
from masonite.helpers import optional


class BroadcastController(Controller):

    def authorize(self, request: Request, broadcast: Broadcast):
        channel_name = request.input("channel_name")
        authorized = True

        # check permissions for private-admins channel else authorize every other channels
        if channel_name == "private-admins":
            # check that user is logged in and admin
            if optional(request.user()).role != "admin":
                authorized = False

        if authorized:
            return broadcast.driver("pusher").authorize(
                channel_name, request.input("socket_id")
            )
        else:
            return False
# app/broadcasts/UserAlert.py
from masonite.broadcasting import CanBroadcast, Channel

class AdminUserAlert(CanBroadcast):

    def __init__(self, message, level="error"):
        self.message = message
        self.level = level

    def broadcast_on(self):
        return Channel("private-admins")

    def broadcast_with(self):
        return {"message": self.message, "level": self.level}
from masonite.facades import Broadcast
from app.broadcasts import AdminUserAlert

Broadcast.channel(AdminUserAlert("Some dependencies are outdated !", level="warning"))
<html lang="en">
<head>
  <title>Document</title>
  <script src="https://js.pusher.com/7.0/pusher.min.js"></script>
</head>
<body>
  <script>
    const pusher = new Pusher("478b45309560f3456211", {
      cluster: "eu"
    });

    const channel = pusher.subscribe('private-admins');
    channel.bind('AdminUserAlert', (data) => {
      alert(`[${data.level.toUpperCase()}] ${data.message}`)
    })
  </script>
</body>
</html>
$ npm install
mix
  .js("resources/js/app.js", "storage/compiled/js")
  .postCss("resources/css/app.css", "storage/compiled/css", [
    //
  ]);
$ npm run dev
$ npm run watch
if (mix.inProduction()) {
  mix.version();
}
<script src="{{ mix('resources/js/app.js') }}"></script>
config/cache.py
STORES = {
    "default": "local",
    "local": {
        "driver": "file",
        "location": "storage/framework/cache"
    },
    "redis": {
        "driver": "redis",
        "host": "127.0.0.1",
        "port": "6379",
        "password": "",
        "name": "masonite4",
    },
    # ...
}
pip install redis
STORES = {
    "default": "redis",
    "redis": {
        "driver": "redis",
        "host": env("REDIS_HOST", "127.0.0.1"),
        "port": env("REDIS_PORT", "6379"),
        "password": env("REDIS_PASSWORD"),
        "name": env("REDIS_PREFIX", "project name"),
    },
}
pip install pymemcache
STORES = {
    "default": "memcached",
    "memcached": {
        "driver": "memcached",
        "host": env("MEMCACHED_HOST", "127.0.0.1"),
        "port": env("MEMCACHED_PORT", "11211"),
        "password": env("MEMCACHED_PASSWORD"),
        "name": env("MEMCACHED_PREFIX", "project name"),
    },
}
from masonite.cache import Cache

def store(self, cache: Cache):
    data = cache.add('age', '21')
cache.put('age', '21')
cache.put('age', '21', seconds=300) # stored for 5 minutes
cache.put('user', {"name": "Joe"}, seconds=300) # stored for 5 minutes
cache.get("user")['name'] #== "Joe"
cache.get('age', '40')
cache.has('age')
cache.forget('age')
cache.get('age') #== 21
cache.increment('age') #== 22
cache.decrement('age') #== 21
from app.models import User

cache.remember("total_users", lambda cache: (
    cache.put("total_users", User.all().count(), 300)
))
cache.flush()
DISKS = {
    "default": "local",
    "local": {
        "driver": "file",
        "path": os.path.join(os.getcwd(), "storage/framework/filesystem")
        #
    },
    "s3": {
        "driver": "s3",
        "client": os.getenv("AWS_CLIENT"),
        "secret": os.getenv("AWS_SECRET"),
        "bucket": os.getenv("AWS_BUCKET"),
          "path": "https://bucket.s3.us-east-2.amazonaws.com"
    },
}

driver

The driver to use for this disk

path

The base path to use for this disk

driver

The driver to use for this disk

client

The Amazon S3 client key

secret

The Amazon S3 secret key

bucket

The Amazon S3 bucket name

path

A path to be used for displaying resources

from masonite.filesystem import Storage
from masonite.request import Request

def store(self, storage: Storage, request: Request):
  storage.disk('local').put('errors/info.log', 'errors')

exists(file_path)

Boolean to check if a file exists.

missing(file_path)

Boolean to check if a file does not exist.

stream

Creates a FileStream object to manage a file stream.

copy('/file1.jpg', '/file2,jpg')

Copies a file from 1 directory to another directory

move('/file1.jpg', '/file2,jpg')

Moves a file from 1 directory to another

prepend('file.log', 'content')

Prepends content to a file

append('file.log', 'content')

Appends content to a file

put('file.log', 'content')

Puts content to a file

get('file.log')

Gets content of a file

put_file('directory', resource, "name")

Puts a file resource to a directory. Must be an instance of Masonite's UploadedFile. Takes an optional third name parameter to specify the file name

from masonite.filesystem import Storage
from masonite.request import Request

def store(self, storage: Storage, request: Request):
  path = storage.disk('local').put_file('avatars', request.input('avatar'))
storage.disk('local').put_file('avatars', request.input('avatar'), name="user1")
DISKS = {
    "default": "local",
    # "..",
    "s3": {
        "driver": "s3",
        # "..",
          "path": "https://bucket.s3.us-east-2.amazonaws.com"
    },
}
<img src="{{ asset('s3', user.avatar_url) }}">
asset('disk', file_name)
DISKS = {
    "default": "local",
    # "..",
    "s3": {
        "driver": "s3",
        # "..",
          "path": {
          "logos": "https://bucket.s3.us-east-2.amazonaws.com/logos",
          "invoices": "https://bucket.s3.us-east-2.amazonaws.com/invoices"
        }
    },
}
<img src="{{ asset('s3.logos', user.avatar_url) }}">
<a href="{{ asset('s3.invoices', invoice_url) }}"
from masonite.routes import Route

ROUTES = [
    Route.get('/url', 'Controller@method')
]
ROUTES = [
    Route.get('/', 'WelcomeController@show').name('welcome'),

    # Blog Routes
    Route.get('/blog', 'BlogController@show')
]
$ python craft controller Blog
from masonite.controllers import Controller
from masonite.views import View


class BlogController(Controller):
    def show(self, view: View):
        return view.render("")
from masonite.view import View
# ...
def show(self, view: View):
    return view.render('blog')
This is a blog
$ python craft migrate
$ python craft serve
$ python craft auth
from masonite.authentication import Auth

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

    # Blog Routes
    Route.get('/blog', 'BlogController@show')
]

ROUTES += Auth.routes()
$ python craft migrate
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=masonite
DB_USERNAME=root
DB_PASSWORD=
$ python craft migrate
$ python craft serve
Username: demo
Email: demo@email.com
Password: password!!11AA
$ python craft migration create_posts_table --create posts
def up(self):
    """
    Run the migrations.
    """
    with self.schema.create('posts') as table:
        table.increments('id')
        table.timestamps()
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()
$ python craft migrate
$ python craft model Post
"""Post Model."""
from masoniteorm.models import Model

class Post(Model):
    pass
"""Post Model."""
from masoniteorm.models import Model

class Post(Model):
    __table__ = 'user_posts'
"""A Post Database Model."""
from masoniteorm.models import Model

class Post(Model):
    __fillable__ = ['title', 'author_id', 'body']
"""Post Model."""
from masoniteorm.models import Model
from masoniteorm.relationships import belongs_to

class Post(Model):
    __fillable__ = ['title', 'author_id', 'body']

    @belongs_to('author_id', 'id')
    def author(self):
        from app.models.User import User
        return User
<form action="/blog/create" method="POST">
    {{ csrf_field }}

    <input type="name" name="title">
    <textarea name="body"></textarea>
</form>
@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
html {
    background-color: #ddd;
}
<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
<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>
from masonite.routes import Route
# ...
ROUTES = [
    # ...
    Route.post('/blog/create', 'BlogController@store')
]
...
def show(self, view: View):
    return view.render('blog')

# New store Method
def store(self):
    pass
from app.models.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'
$ python craft controller Post
from app.models.Post import Post

...

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

    return view.render('posts', {'posts': posts})
Route.get('/posts', 'PostController@show')
<!-- templates/posts.html -->
@for post in posts
    {{ post.title }}
    <br>
    {{ post.body }}
    <hr>
@endfor
@for post in posts
    {{ post.title }} by {{ post.author.name }}
    <br>
    {{ post.body }}
    <hr>
@endfor
Route.get('/post/@id', 'PostController@single')
from app.models.Post import Post
from masonite.request import Request
from masonite.views import View
...

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

    return view.render('single', {'post': post})
{{ post.title }}
<br>
{{ post.body }}
<hr>
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'
<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>
Route.get('/post/@id/update', 'PostController@update'),
Route.post('/post/@id/update', 'PostController@store'),
from masonite.request import Request
...

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

    post.delete()

    return 'post deleted'
Route.get('/post/@id/delete', 'PostController@delete'),
<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>
application.make("mail").send()
from masonite.facades import Mail

Mail.send()
from masonite.facades import Request

def show(self):
    avatar = Request.input('avatar_url')
from masonite.facades import Facade

class YourFacade(metaclass=Facade):
  key = 'container_key'
from app.facades import YourFacade

YourFacade.method()
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from ..mail import Mailable

class Mail:
    """Handle sending e-mails from Masonite via different drivers."""

    def mailable(mailable: "Mailable") -> "Mail":
        ...
    def send(driver: str = None):
        ...

Hash ID's

The Masonite hashing feature is a very useful feature to prevent exposing ID's in your application.

Many times you need to expose your database primary keys to the frontend. For example, when updating a record, you might need to pass in a primary key value to a URL like /users/10/settings.

Typically you want to hide these key values from a hacker trying to change these values easily.

With the Masonite hashing feature you can change a value like 10 to l9avmeG and prevent exposing those sensitive integer values.

Setup

The Masonite hashing feature automatically decodes values before they get to the controller. To do this it you need to specify both a middleware to help decode the values as well as the provider to register the helper in the templates.

For the middleare you can add it easily:

from masonite.hashid.middleware import HashIDMiddleware

# ..
route_middleware = {
  "web": [
    HashIDMiddleware,
    SessionMiddleware,
    EncryptCookies,
    VerifyCsrfToken,
  ],
}

You should put the Hash ID middleware towards the top of the middleware stack so it will decode the request properly before getting to the other middleware in the stack.

The provider should also be added:

from masonite.providers import HashIDProvider

PROVIDERS = [
  # ..
  HashIDProvider,
]

This will register a template helper and some other useful features.

Helper

You can use the helper directly to encode or decode integers easily:

from masonite.helpers import hashid

def show(self):
  hashid(10) #== l9avmeG
  hashid('l9avmeG', decode=True) #== 10

Templates

Inside your templates you can use the hashid template helper:

<!-- user.id == 10 -->
<input type="hidden" name="user_id" value="{{ hashid(user.id) }}"

When submitted to the backend the will now be the normal integer value of the user id:

def store(self, request: Request):
  request.input("user_id") #== 10

Route Parameters

When using the template helper, you may also use the hashid feature for request params:

If you have a route like this:

Route.get('/user/@user_id/updates', 'Controller@method')
<!-- user.id == 10 -->
<form action="/user/{{ user.id }}/update" method="POST">
</form>

Inside your controller you are able to get the unhashed request parameter:

def store(self, request: Request):
  request.param('user_id') #== 10

Mail

Masonite has a simple yet powerful mail feature which is used to send emails from your application.

Creating Emails

To create and send an email with Masonite, you must first built a Mailable class. This class will act as the settings for your email such as the address you are sending from, the subject, the text of the email, the html of the email, etc.

All of these settings on the mailable can be changed or overwritten when you are ready to send you email, more on this later on.

The first step of building a mailable is running the command:

$ python craft mailable Welcome

This will create your mailable and it will look something like this:

class Welcome(Mailable):
    def build(self):
        (
            self.subject("Welcome to our site!")
            .from_("admin@example.com")
            .view("mailables.welcome", {})
        )

Sending Mailables

You can send your mailable inside your controller easily by resolving the Mail class in your controller:

from masonite.mail import Mail
from app.mailables.Welcome import Welcome

class WelcomeController(Controller):
  
    def welcome(self, mail: Mail):
        mail.mailable(Welcome().to('user@example.com')).send()

Notice at this point you can call any building options you want on the mailables to modify the behavior of it before sending.

Note that you can also use the Mail facade anywhere in your code:

from masonite.facades import Mail
from app.mailables.Welcome import Welcome

Mail.mailable(Welcome().to('user@example.com')).send()

Mail Options

You can modify the behavior of the mailable by using any one of these options

Options
Description

to('user@example.com')

Specifies the user to send the email to. You may also specify the users name like to('Joseph <user@example.com>').

from_("admin@example.com")

Specifies the address that the email should appear it is from.

cc(["accounting@example.com"])

A list of the addresses that should be "carbon copied" onto this email

bcc(["accounting@example.com"])

A list of the addresses that should be "blind carbon copied" onto this email

subject('Subject of the Email')

Specifies the subject of the email.

reply_to('customers@example.com')

Specifies the address that will be set if a user clicks reply to this email

text('Welcome to Masonite')

Specifies the text version of the email.

html('Welcome to Masonite')

Specifies the HTML version of the email.

view('mailables.view', {})

Specifies a view file with data to render the HTML version of the email

priority(1)

Specifies the priority of the email, values should be 1 through 5.

low_priority()

Sets the priortiy of the email to 5.

high_priority()

Sets the priortiy of the email to 1.

attach('MAY.pdf', 'path/invoice.pdf')

Attaches a file to the email.

Sending Attachments

Sending attachments is really simply with Masonite. Simply attach the file to the mailable before sending it:

user = user.find(1)
welcome_mailable = WelcomeMailable().to(f"{user.name} <{user.email}>')
welcome_mailable.attach("MAY-2021-invoice.pdf", "storage/pdf/invoice.pdf")
mail.mailable(welcome_mailable).send()

You will then see your attachment in the email.

Mailable Responses

When you are building your emails it might be nice to see how they look before sending them. This can save a lot of time when you're trying to get those styles just right.

You can simply return your mailable in your controller and it will return like a normal view file.

from app.mailables.Welcome import Welcome

class WelcomeController(Controller):
  
    def welcome(self):
        return Welcome()

If you are using the view() option in your mailable then you will need to set the application on the mailable:

from app.mailables.Welcome import Welcome
from wsgi import application

class WelcomeController(Controller):
  
    def welcome(self):
        return Welcome().set_application(application)

Changing Drivers

You can change the driver which sends the email by using the driver argument in the send() method:

mail.send(Welcome().to('user@example.com'), driver="smtp")
cleo
Cleo documentation
Pusher
Pusher
Javascript Pusher SDK
by default
here
Laravel Mix Documentation
TailwindCSS Official Documentation
Laravel Mix Vue Support Documentation
Laravel Mix Versioning
Service Container
Installation
Routing
http://localhost:8000/register
Database Migrations here
Database Migrations
Masonite ORM Relationships
Read Their Documentation
Static Files
Service Container
Service Container
Service Container
Service Container
Service Container
install and compile assets
compile assets
configured earlier
Private channels
Private channels
Public channels
Private channels
Redis
Memcached
File
start using cache
start using cache
Service Provider
create one
Service Provider
create one
Service Provider
create one
Service Container
Service Provider
create one

Rate Limiting

Masonite includes a rate limiting feature which make it really easy to limit an action during a given time window.

The most frequent use of this feature is to add throttling to some API endpoints but you could also use this to limit how many time a model can be updated, a job can be run in the queue or a mail should be sent.

Overview

Rate Limiting feature can be accessed via the container application.make("rate") or by using the RateLimiter facade.

To limit an action, we need:

  • a key that will uniquely identify the action

  • a number of authorized attempts

  • a delay after which number of attempts will be reset

Setup

Before we can start using the rate limiting features of Masonite, we need to register the RateProvider class in our providers list:

from masonite.providers import RateProvider
# ..
PROVIDERS = [
    #..
    RateProvider,
]

Throttle HTTP Requests

You can add throttling to HTTP requests easily by using the ThrottleRequestsMiddleware.

First register the middleware as a route middleware into your project:

# Kernel.py
from masonite.middleware import ThrottleRequestsMiddleware

class Kernel:
    route_middleware = {
        "web": [
            SessionMiddleware,
            LoadUserMiddleware,
            VerifyCsrfToken,
        ],
        "throttle": [ThrottleRequestsMiddleware],
    }

This middleware is taking one argument which can be either a limit string or a limiter name.

Using Limit Strings

By using limit strings such as 100/day you will be able to add a global limit which does not link this limit to a given user, view or IP address. It's really an absolute limit that you will define on your HTTP requests.

Units available are: minute, hour and day.

We now just have to use it in our routes:

# web.py
Route.post("/api/uploads/", "UploadController@create").middleware("throttle:100/day")
Route.post("/api/videos/", "UploadController@create").middleware("throttle:10/minute")

Using Limiters

To be able to add throttling per users or to implement more complex logic you should use Limiter. Limiter are simple classes with a allow(request) method that will be called each time a throttled HTTP request is made.

Some handy limiters are bundled into Masonite:

  • GlobalLimiter

  • UnlimitedLimiter

  • GuestsOnlyLimiter

But you can create your own limiter:

from masonite.rates import Limiter

class PremiumUsersLimiter(Limiter):

    def allow(self, request):
        user = request.user()
        if user:
            if user.role == "premium":
                return Limit.unlimited()
            else:
                return Limit.per_day(10).by(request.ip())
        else:
            return Limit.per_day(2).by(request.ip())

Here we are creating a limiter authorizing 2 requests/day for guests users, 10 requests/day for authenticated non-premium users and unlimited requests for premium users. Here we are using by(key) to define how we should identify users. (TODO: explain more)

Finally you can register your limiter(s) in your application provider:

from masonite.facades import RateLimiter
from masonite.rates import GuestsOnlyLimiter
from app.rates import PremiumUsersLimiter

class AppProvider(Provider):

    def register(self):
        RateLimiter.register("premium", PremiumUsersLimiter())
        # register an other limiter which gives unlimited access for authenticated users
        # and 2 request/day for guests users
        RateLimiter.register("guests", GuestsOnlyLimiter("2/hour"))

We now just have to use it in our routes:

# web.py
Route.post("/api/uploads/", "UploadController@create").middleware("throttle:premium")
Route.post("/api/videos/", "UploadController@create").middleware("throttle:guests")

Now when making unauthenticated requests to our /api endpoints we will see some new headers in the response:

  • X-Rate-Limit-Limit : 5

  • X-Rate-Limit-Remaining : 4

After reaching the limit two headers are added in the response, X-Rate-Limit-Reset which is the timestamp in seconds defining when rate limit will be reset and when api endpoint will be available again and Retry-After which is the number of seconds in which rate limit will be reset:

  • X-Rate-Limit-Limit : 5

  • X-Rate-Limit-Remaining : 0

  • X-Rate-Limit-Reset : 1646998321

  • Retry-After : 500

A ThrottleRequestsException exception is raised and a response with status code 429: Too Many Requests and content Too many attempts is returned.

Customizing response

The response can be customized to provide different status code, content and headers. It can be done by adding a get_response() method to the limiter.

In the previous example it would look like this:

class PremiumUsersLimiter(Limiter):
    # ...

    def get_response(self, request, response, headers):
        if request.user():
            return response.view("Too many attempts. Upgrade to premium account to remove limitations.", 400)
        else:
            return response.view("Too many attempts. Please try again tomorrow or create an account.", 400)

Defining Limits

To use Rate Limiting feature we should define limits. A limit is a number of attempts during a given time frame. It could be 100 times per day or 5 times per hour. In Masonite limits are abstracted through the Limit class.

Here is an overview of the different ways to create limits:

from masonite.rates import Limit

Limit.from_str("100/day")
Limit.per_day(100)  # equivalent to above

Limit.per_minute(10)
Limit.per_hour(5)
Limit.unlimited()  # to define an unlimited limit

Then to associate a given key to this limit we can do:

username = f"send_mail-{user.id}"
Limit.per_hour(5).by(username)

Limit an action

This feature allows to simply limit calling a Python callable. Here we will limit the given callable to be called 3 times per hour for the key sam.

Let's make one attempt:

def send_welcome_mail():
    # ...

RateLimiter.attempt(f"send_mail-{user.id}", send_welcome_mail, max_attempts=3, delay=60*60)

Alternatively you can manually incrementing attempts:

RateLimiter.hit(f"send_mail-{user.id}", delay=60*60)

We can get the number of attempts:

RateLimiter.attempts(f"send_mail-{user.id}") #== 1

We can get the number of remaining attempts:

RateLimiter.remaining(f"send_mail-{user.id}", 3) #== 2

We can check if too many attempts have been made:

if RateLimiter.too_many_attempts(f"send_mail-{user.id}", 3):
    print("limited")
else:
    print("ok")

We can reset the number of attempts:

RateLimiter.reset_attempts(f"send_mail-{user.id}")
RateLimiter.attempts(f"send_mail-{user.id}") #== 0

We can get the seconds in which will be able to attempt the action again:

RateLimiter.available_in(f"send_mail-{user.id}") #== 356

We can get the UNIX timestamps in seconds when will be able to attempt the action again:

RateLimiter.available_at(f"send_mail-{user.id}") #== 1646998321

Here is a complete use case, that will determine if an email should be send to the given user:

class WelcomeController(Controller):

    def welcome(self, request: Request):
        user = request.user()
        rate_key = f"send_mail_{user.id}"
        if (RateLimiter.remaining(rate_key, 2)):
            WelcomeMail(user).send()
            RateLimiter.hit(rate_key, delay=3600)
        else:
            seconds = RateLimiter.available_in(rate_key)
            return "You may try again in {seconds} seconds."

Package Development

Introduction

Creating packages is very simple for Masonite. You can create a package and publish it on PyPi in less than 5 minutes. With Masonite packages you will scaffold and centralize some features to reuse it all your 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.

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, you can obviously use all Python packages that aren’t designed for a specific framework. For example, you can obviously use a library like requests but you can’t use specific Django Rest Framework.

Package Discovery

Package providers are the connection between your package and Masonite. A service provider is responsible for binding things into Masonite container and specifying from where to load package resources such as views, configuration, routes and assets.

Your Masonite project will discover packages through the PROVIDERS list defined in your providers.py configuration file. When a package provider is added to this list, this will allow additional bindings, commands, views, routes, migrations and assets to be registered in your project.

Keep in mind that some simple packages do not need to register resources in your project though.

Creating a Package

Package Scaffolding

We provide two ways to quickly scaffold your package layout:

starter-package

cookiecutter template

Install cookiecutter globally (or locally) on your computer:

$ pip install cookiecutter

Then you just have to run (in the directory where you want your package repository to be created):

$ cookiecutter https://github.com/girardinsamuel/cookiecutter-masonite-package.git

You can now start developing your package !

Development process

Creating and activating your virtual environment

There are man ways to create a virtual environment in Python but here is a simple way to do it:

$ python3 -m venv venv

Activating your virtual environment on Mac and Linux is simple:

$ source venv/bin/activate

or if you are on Windows:

$ ./venv/Scripts/activate

Initializing your environment

The default package layout contains a Makefile that help getting started quickly. You just have to run:

$ make init

This will install Masonite, development tools such as flake8 and pytest and it will also install your package locally so that you can start using and testing it in the test project directly.

Running the tests

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

$ python -m pytest

The default package layout come with one basic test. You should see this test passing. You can then start building your package and adding more unit tests.

Running the test project

$ python craft serve

You can then visit http://localhost:8000 to see the welcome page.

When you make changes to your package as your package is installed locally and registered into your project, your changes will be directly available in the project. You will just need to refresh your pages to see changes.

Migrating the test project

$ masonite-orm migrate -d tests/integrations/databases/migrations

Releasing the package

Registering on PyPi

Creating a publish token

API tokens provide an alternative way (instead of username and password) to authenticate when uploading packages to PyPI. It is is strongly recommended to use an API token where possible.

Go to your PyPi account and find the API tokens section. Click on Add API token, give a significant name such as publish-token and create the token. Write down the token key somewhere for later.

Configuring PyPi on your computer

If you never uploaded a package on PyPi before you will need to configure PyPi on your computer. For this you will need to add a .pypirc file in your home directory. If you do not have one then you can easily creating one using:

$ make pypirc

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

Then fill in password key with the token you created later prefixed by pypi-. With a token starting with AgEIcHlwaS5vcmcCJGNjYjA4M... the .pypirc file would look like:

[distutils]
index-servers =
  pypi
  pypitest

[pypi]
username=__token__
password=pypi-AgEIcHlwaS5vcmcCJGNjYjA4M...

Publishing the package to PyPI

Now you're ready to upload your package to PyPI. Ensure that all parameters of setup.py file are up to date. It's the file describing your package. Fill in the correct version number you want to publish. When ready you can run:

$ make publish

You should always check that the package name is available on PyPi and that the version number to publish has not been published before. Else you won't be able to publish your package.

Make the package available on masonite packages list

# setup.py
    classifiers=[
        #...
        "Framework :: Masonite",
    ]

Registering Resources

When developing a package you might need to use a configuration file, to add migrations, routes and controllers or views. All those resources can be located in your package but at one point a user might want to override it and will need to publish those resources locally in its project.

The following section will explain how to register those resources in your package to be used in a Masonite project and how to make those resources publishable.

Masonite makes it really easy to do this by creating a specific package provider that will register your package resources. The default package layout comes with such a provider inheriting from PackageProvider class:

# providers/SuperAwesomeProvider.py
from masonite.packages import PackageProvider

class SuperAwesomeProvider(PackageProvider):

    def configure(self):
        (
            self.root("super_awesome_package")
            .name("super_awesome")
        )

    def boot(self):
        pass

configure() method is called in usual register() method and is used to register all resources used in your package.

root(import_path) method should be called first and is used to specify the import path of your package. If your package needs to be used like this:

from super_awesome_package.providers import SuperAwesomeProvider

Then super_awesome_package is the import path of your package. If your package is imported like this:

from masonite.inertia.providers import InertiaProvider

Then masonite.inertia is the import path of your package.

name(string) method should be called in second and is used to specify the name of your package (not the PyPi package name neither the Python module name) but the name that will be used to reference your package in the publish command or in the resources paths (it should be a name without special characters and spaces i.e. a Python valid name). This will also be the name of your package configuration file.

Configuration

Your package is likely to have a configuration file. You will want to make your package configuration available through the handy config() Masonite helper. For this you will need to call config(path, publish=False) inside configure() method:

def configure(self):
    (
        self.root("super_awesome_package")
        .name("super_awesome")
        .config("config/super_awesome.py")
    )

This will load the package configuration file located at super_awesome_package/config/super_awesome.py into Masonite config. The configuration will then be available with config("super_awesome.key").

If you want to allow users to publish the configuration file into their own project you should add publish=True argument.

def configure(self):
    (
        self.root("super_awesome_package")
        .name("super_awesome")
        .config("config/super_awesome.py", publish=True)
    )

Configuration values located in packages and in local project will be merged. Values defined locally in the project takes precedance over the default values of the package.

Migrations

If your package contains migrations you can register the migration files to be published in a project:

def configure(self):
    (
        self.root("super_awesome_package")
        .name("super_awesome")
        .migrations("migrations/create_some_table.py", "migrations/create_other_table.py")
    )

Routes / Controllers

If your package contains routes you can register them by providing your route files and the locations to load controllers (used by your routes) from. For this you will need to call controllers(*locations) and then routes(*routes) inside configure() method.

If your routes are defined in super_awesome_package/routes/api.py and super_awesome_package/routes/web.py and the controllers files available in super_awesome_package/controllers you can do:

def configure(self):
    (
        self.root("super_awesome_package")
        .name("super_awesome")
        .controllers("controllers") # before routes !
        .routes("routes/api.py", "routes/web.py")
    )

Now Masonite should be able to resolve new routes from your packages.

Views

If your package contains views you can register them by providing folders containing your views. For this you will need to call views(*folders, publish=False) inside configure() method. The views will be namespaced after your package name:

For example if your package contains an admin folder located at super_awesome_package/admin/ containing a index.html view you can do:

def configure(self):
    (
        self.root("super_awesome_package")
        .name("super_awesome")
        .views("admin")
    )

Views will be available in controllers:

class ProjectController(Controller):

    def index(self, view: View):
        return view.render("super_awesome.admin.index")

Assets

If your project contains assets (such as JS, CSS or images files) you can register them to be published in the project by calling assets(*paths) inside configure() method.

For example if your package contains an assets folder located at super_awesome_package/assets/ containing some asset files and folders you can do:

def configure(self):
    (
        self.root("super_awesome_package")
        .name("super_awesome")
        .views("assets")
    )

Commands

If your project contains commands you will want to register it when your package is installed in a project so that we can run python craft my_package_command. For this you will need to call commands(*command_class) inside configure() method.

from ..commands import MyPackageCommand, AnOtherCommand

def configure(self):
    (
        self.root("super_awesome_package")
        .name("super_awesome")
        .commands(MyPackageCommand, AnOtherCommand)
    )

Now when you run python craft you should see the two registered commands.

Publishing Resources

When using PackageProvider class to create your package service provider, you will be able to publish all package resources defined below in a project. You just need to run the command package:publish with the name of the package (declared inside configure() method). With our example it would be:

$ python craft package:publish super_awesome

If you want to publish some specific resources only, you can use --resources flag:

$ python craft package:publish super_awesome --resources config,views

Here this will only publish configuration and views into your project.

Finally if you want to check what resources a package can publish you just need to run:

$ python craft package:publish super_awesome --dry

This will output the list of resources that the package is going to publish into your project.

Consuming a Package

If the package has been released on PyPi you need to install it as any Python package:

$ pip install super-awesome-package

Then you should follow package installation guidelines but often it will consist in:

from super_awesome_package.providers import SuperAwesomeProvider

PROVIDERS = [
  # ...
  SuperAwesomeProvider,
]
  • publishing some files if you need to tweak package resources or configuration:

$ python craft package:publish super-awesome-package

You should be ready to use the package in your project !

Sessions

Configuration

Session configuration is located at config/session.py file. In this file, you can configure which driver to use.

Masonite is configured to use the Cookie session driver by default, named cookie.

config/session.py
DRIVERS = {
    "default": "cookie",
    "cookie": {},
    "redis": {
        "host": "127.0.0.1",
        "port": 6379,
        "password": "",
        "options": {"db": 1},  # redis module driver specific options
        "timeout": 60 * 60,
        "namespace": "masonite4",
    },
}

Cookie

Cookie driver will store all session data in the users cookies. It can be used as is.

Redis

Redis driver is requiring the redis python package, that you can install with:

pip install redis

Then you should define Redis as default driver and configure it with your Redis server parameters:

DRIVERS = {
    "default": "redis",
    "redis": {
        "host": "127.0.0.1",
        "port": 6379,
        "password": "",
        "options": {"db": 1},  # redis module driver specific options
        "timeout": 60 * 60,
        "namespace": "masonite4",
    },
}

Finally ensure that the Redis server is running and you're ready to start using sessions.

Saving Session Data

To save session data you can simply "set" data into the session:

from masonite.sessions import Session

def store(self, session: Session):
  data = session.set('key', 'value')

Flashing Data

Flash data is data specific to the next request. This is useful for data such as error messages or alerts:

from masonite.sessions import Session

def store(self, session: Session):
  data = session.flash('key', 'value')

Retrieving Session Data

To get back the session data you set you can simply using the "get" method:

from masonite.sessions import Session

def store(self, session: Session):
  data = session.get('key')

Checking For Existence

You can check if a session has a specific key:

from masonite.sessions import Session

def store(self, session: Session):
  if session.has('key'):
    pass

Deleting Session Data

You can also delete a key from the session

from masonite.sessions import Session

def store(self, session: Session):
  session.delete('key')

Resetting the Session

You can reset all data in a session:

session.flush()

Helpers

Masonite has several helpers available. Many helpers are used internally to help write clean Masonite code but can be used in your projects as well.

All helpers can be imported from helpers module.

Masonite Specifics

app

from masonite.helpers import app

app().make("session")

You can also resolve dependencies directly and even pass arguments when resolving:

from masonite.helpers import app

app("session") #== Session

app("my_service", args)

dump

You can easily dump variables into console for debugging, from inside a controller for example: For this you can use Dump facade or the built-in dump python method:

from masonite.facades import Dump

test = 1
data = {"key": "value"}
Dump.dump(test, data)

# OR

dump(test, data)

This will dump data in console in a nice format to ease debugging.

dd

If you want the code to stop and renders a dump page instead you can use the dump and die helper named dd:

from masonite.facades import Dump

test = 1
data = {"key": "value"}
Dump.dd(test, data)

# OR

dd(test, data)

This will stop code at this line and renders a nice dump page where you can see all variables dumped until now.

Note that dumps will accumulate into session. If you want to clear dumps, you can use Dump.clear() or you can enable the HTTP middleware ClearDumpsBetweenRequestsMiddleware to clear dumps between every requests.

Kernel.py
from masonite.middleware import ClearDumpsBetweenRequestsMiddleware

class Kernel:

    http_middleware = [
        #...
        ClearDumpsBetweenRequestsMiddleware
    ]

config

TODO

env

TODO

Paths

base_path

Get the absolute path to your project root directory or build the absolute path to a given file relative to the project root directory.

from masonite.utils.location import base_path

base_path() # /Users/johndoe/my-project/

base_path("storage/framework") # /Users/johndoe/my-project/storage/framework

views_path

Get the absolute path to your project views directory or build the absolute path to a given file relative to the project views directory.

from masonite.utils.location import views_path

views_path()  # /Users/johndoe/my-project/templates

views_path("admin/index.html")  # /Users/johndoe/my-project/templates/admin/index.html

controllers_path

Get the absolute path to your project controllers directory or build the absolute path to a given file relative to the project controllers directory.

from masonite.utils.location import controllers_path

controllers_path()  # /Users/johndoe/my-project/app/controllers

controllers_path("admin/AdminController.py")  # /Users/johndoe/my-project/app/controllers/admin/AdminController.py

mailables_path

Get the absolute path to your project mailables directory or build the absolute path to a given file relative to the project mailables directory.

from masonite.utils.location import mailables_path

mailables_path()  # /Users/johndoe/my-project/app/mailables

mailables_path("WelcomeUser.py")  # /Users/johndoe/my-project/app/mailables/WelcomeUser.py

config_path

Get the absolute path to your project config directory or build the absolute path to a given file relative to the project config directory.

from masonite.utils.location import config_path

config_path()  # /Users/johndoe/my-project/config

config_path("custom.py")  # /Users/johndoe/my-project/config/custom.py

migrations_path

Get the absolute path to your project migrations directory or build the absolute path to a given file relative to the project migrations directory.

from masonite.utils.location import migrations_path

migrations_path()  # /Users/johndoe/my-project/databases/migrations

migrations_path("2022_11_01_043202_create_users_table.py")  # /Users/johndoe/my-project/databases/migrations/2022_11_01_043202_create_users_table.py

seeds_path

Get the absolute path to your project seeds directory or build the absolute path to a given file relative to the project seeds directory.

from masonite.utils.location import seeds_path

seeds_path()  # /Users/johndoe/my-project/databases/seeds

seeds_path("products_table_seeder.py")  # /Users/johndoe/my-project/databases/seeds/products_table_seeder.py

jobs_path

Get the absolute path to your project jobs directory or build the absolute path to a given file relative to the project jobs directory.

from masonite.utils.location import jobs_path

jobs_path()  # /Users/johndoe/my-project/app/jobs

jobs_path("SendInvoices.py")  # /Users/johndoe/my-project/app/jobs/SendInvoices.py

resources_path

Get the absolute path to your project resources directory or build the absolute path to a given file relative to the project resources directory.

from masonite.utils.location import resources_path

resources_path()  # /Users/johndoe/my-project/resources

resources_path("css/app.css")  # /Users/johndoe/my-project/resources/css/app.css

models_path

Get the absolute path to your project models directory or build the absolute path to a given file relative to the project models directory.

from masonite.utils.location import models_path

models_path()  # /Users/johndoe/my-project/app/models

models_path("Product.py")  # /Users/johndoe/my-project/app/models/Product.py

All above paths helper return an absolute path to the location. When providing a file path to the helper you can set absolute=False to get the path relative given directory.

from masonite.utils.location import models_path

models_path("blog/Article.py", absolute=False)  # app/models/blog/Article.py

URLs and Routes

url

The Url helper allows you to create a full path URL:

from masonite.helpers import url

url.url("/dashboard") #== example.com/dashboard

The URL will come from the APP_URL in your application config file.

It accepts a dictionary to add query string parameters when building the url:

url.url("/search/users",  query_params={"search": "John Doe" "order": "asc"})
#== http://masonite.app/users/?search=John%2Doe&order=asc

asset

You can also generate a URL for an asset:

url.asset("s3.invoices", "invoice-01-2021.pdf")

route

You can also generate a URL for a route by its route name:

url.route("users.profile", {"id": 1}) #== http://masonite.app/users/1/profile/

You can also generate just a path:

url.route("users.profile", {"id": 1}, absolute=False) #== /users/1/profile/

It accepts a dictionary to add query string parameters when building the route url:

url.route("user.profile", {"id": 1}, query_params={"preview": 1})
#== http://masonite.app/users/1/profile/?preview=1

mix

TODO

Python Helpers

compact

The compact helper is a shortcut helper when you want to compile a dictionary from variables.

There are times when you will have instances like this:

from masonite.view import View

def show(self, view: View):
  users = User.all()
  articles = Article.all()
  return view.render('some.view', {"users": users, "articles": articles})

Notice we repeated the users and articles key the same as the variables name. In this case we can use the compact helper to clean the code up a bit:

from masonite.view import View
from masonite.helpers import compact

def show(self, view: View):
  users = User.all()
  articles = Article.all()
  return view.render('some.view', compact(users, articles))

optional

The optional helper takes an object and allows any method calls on the object. If the method exists on the object it will return the value or else it will return None:

You may have a peice of code that looks like this:

if request.user() and request.user().admin == 1:
  #.. do something

with the optional helper you can condense the code into something like this:

from masonite.helpers import optional

if optional(request.user()).admin == 1:
  #.. do something

collect

TODO

flatten

TODO

Notifications

Masonite has a simple yet powerful notification feature which is used to send notifications from your application. Here is a brief overview of what you can do with notifications:

  • Send E-mail, Slack and SMS notifications

  • Store notifications in a database so they may be displayed in your web interface.

  • Queue notifications

  • Broadcast notifications

Creating a Notification

To create and send a notification with Masonite, you must first build a Notification class. This class will act as the settings for your notification such as the delivery channels and the content of the notification for those different channels (mail, slack, sms, ...).

The first step of building a notification is running the command:

$ python craft notification Welcome

This will create your notification and it will look something like this:

class Welcome(Notification, Mailable):

    def to_mail(self, notifiable):
        return (
            self.to(notifiable.email)
            .subject("Welcome to our site!")
            .from_("admin@example.com")
            .text(f"Hello {notifiable.name}")
        )

    def via(self, notifiable):
        return ["mail"]

Each notification class has a via method that specify on which channels the notification will be delivered. Notifications may be sent on the mail, database, broadcast, slack and vonage channels. More details on this later. When sending the notification it will be automatically sent to each channel.

via method should returns a list of the channels you want your notification to be delivered on. This method receives a notifiable instance.

class WelcomeNotification(Notification, Mailable):
    ...
    def via(self, notifiable):
        """Send this notification by email, Slack and save it in database."""
        return ["mail", "slack", "database"]

Sending a Notification

Basic

You can send your notification inside your controller easily by using the Notification class:

from masonite.notification import NotificationManager
from app.notifications.Welcome import Welcome

class WelcomeController(Controller):

    def welcome(self, notification: NotificationManager):
        notification.route('mail', 'sam@masonite.com').send(Welcome())

If the notification needs to be delivered to multiple channels you can chain the different routes:

notification.route('mail', 'sam@masonite.com').route('slack', '#general').send(Welcome())

database channel cannot be used with those notifications because no Notifiable entity is attached to it.

To notifiables

from masonite.notification import NotificationManager
from app.notifications.Welcome import Welcome

class WelcomeController(Controller):

    def welcome(self, notification: NotificationManager):
        user = self.request.user()
        notification.send(user, Welcome())

        # send to all users
        users = User.all()
        notification.send(users, Welcome())

Or you can use a handy notify() method:

user = self.request.user()
user.notify(Welcome())

Using Notifiables

ORM Models can be defined as Notifiable to allow notifications to be sent to them. The most common use case is to set User model as Notifiable as we often need to send notifications to users.

To set a Model as Notifiable, just add the Notifiable mixin to it:

from masonite.notification import Notifiable

class User(Model, Notifiable):
    # ...

You can now send notifications to it with user.notify() method.

Routing

Then you can define how notifications should be routed for the different channels. This is always done by defining a route_notification_for_{driver} method.

For example, with mail driver you can define:

from masonite.notification import Notifiable

class User(Model, Notifiable):
    ...
    def route_notification_for_mail(self):
        return self.email

This is actually the default behaviour of the mail driver so you won't have to write that but you can customize it to your needs if your User model don't have email field or if you want to add some logic to get the recipient email.

Queueing Notifications

If you would like to queue the notification then you just need to inherit the Queueable 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 masonite.notification import Notification
from masonite.queues import Queueable

class Welcome(Notification, Queueable):
    # ...

Channels

Mail

You should define a to_mail method on the notification class to specify how to build the email notification content.

class Welcome(Notification, Mailable):

    def to_mail(self, notifiable):
        return (
            self.to(notifiable.email)
            .subject("Welcome to our site!")
            .from_("admin@example.com")
            .text(f"Hello {notifiable.name}")
        )

    def via(self, notifiable):
        return ["mail"]

If you want to override the mail driver for a given notification you can do:

class Welcome(Notification, Mailable):

    def to_mail(self, notifiable):
        return (
            self.to(notifiable.email)
            .subject("Welcome to our site!")
            .from_("admin@example.com")
            .text(f"Hello {notifiable.name}")
            .driver("mailgun")
        )

    def via(self, notifiable):
        return ["mail"]

Slack

You should define a to_slack method on the notification class to specify how to build the slack notification content.

from masonite.notification.components import SlackComponent

class Welcome(Notification):

    def to_slack(self, notifiable):
        return SlackComponent().text('Masonite Notification: Read The Docs!, https://docs.masoniteproject.com/') \
            .channel('#bot') \
            .as_user('Masonite Bot') \

    def via(self, notifiable):
        return ["slack"]

Options

SlackComponent takes different options to configure your notification:

Method
Description
Example

.text()

The text you want to show in the message

.text('Welcome to Masonite!')

.to()

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

.to('#general') .to('CHSUU862')

.send_from()

The username you want to show as the message sender. You can also specify either the url or icon that will be displayed as the sender.

.as_current_user()

This sets a boolean value to True on whether the message should show as the currently authenticated user.

.as_current_user()

.link_names()

This enables finding and linking channel names and usernames in message.

.link_names()

.can_reply()

This auhtorizes replying back to the message.

.can_reply()

.without_markdown()

This will not parse any markdown in the message. This is a boolean value and requires no parameters.

.without_markdown()

.unfurl_links()

This enable showing message attachments and links previews. Usually slack will show an icon of the website when posting a link. This enables that feature for the current message.

.unfurl_links()

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

.token()

Override the globally configured token.

.token('xoxp-359926262626-35...')

Notifications can be sent to a Slack workspace in two ways in Masonite:

Incoming Webhooks

Web API

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.

Then you can define this token globally in config/notifications.py file as SLACK_TOKEN environment variable. Or you can configure different tokens (with eventually different scopes) per notifications.

Advanced Formatting

$ pip install slackblocks

Then you can import most of the blocks available in Slack documentation and start building your notification. You need to use the block() option. Once again you can chain as many blocks as you want.

from masonite.notification.components import SlackComponent
from slackblocks import HeaderBlock, ImageBlock, DividerBlock

class Welcome(Notification):
    def to_slack(self, notifiable):
        return SlackComponent() \
            .text('Notification text') \
            .channel('#bot') \
            .block(HeaderBlock("Header title")) \
            .block(DividerBlock()) \
            .block(ImageBlock("https://path/to/image", "Alt image text", "Image title"))

Some blocks or elements might not be yet available in slackblocks, but most of them should be there.

Routing to notifiable

You should define the related route_notification_for_slack method on your notifiable to return either

class User(Model, Notifiable):

    def route_notification_for_slack(self):
        """Examples for Incoming Webhooks."""
        # one webhook
        return "https://hooks.slack.com/services/..."
        # multiple webhooks
        return ["https://hooks.slack.com/services/...", "https://hooks.slack.com/services/..."]
class User(Model, Notifiable):

    def route_notification_for_slack(self):
        """Examples for Slack Web API."""
        # one channel name
        return "#general"
        # multiple channel name
        return ["#users", "#general"]
        # one channel ID
        return "C1234567890"

Routing to anonymous

To send a Slack notification without having a notifiable entity you must use the route method

notification.route("slack", "#general").notify(Welcome())

The second parameter can be a channel name, a channel IDor a webhook URL.

When specifying channel names you must keep # in the name as in the example. Based on this name a reverse lookup will be made to find the corresponding Slack channel ID. If you want to avoid this extra call, you can get the channel ID in your Slack workspace (right click on a Slack channel > Copy Name > the ID is at the end of url)

SMS

$ pip install vonage

Then you should configure the VONAGE_KEY and VONAGE_SECRET credentials in notifications.py configuration file:

# config/notifications.py

VONAGE = {
  "key": env("VONAGE_KEY"),
  "secret": env("VONAGE_SECRET"),
  "sms_from": "1234567890"
}

You can also define (globally) sms_from which is the phone number or name that your SMS will be sent from. You can generate a phone number for your application in the Vonage dashboard.

You should define a to_vonage method on the notification class to specify how to build the sms notification content.

from masonite.notification.components import Sms

class Welcome(Notification):

    def to_vonage(self, notifiable):
        return Sms().text("Welcome!")

    def via(self, notifiable):
        return ["vonage"]

Options

If the SMS notification contains unicode characters, you should call the unicode method when constructing the notification

Sms().text("Welcome unicode message!").set_unicode()

The global sms_from number can be overriden inside the notification class:

Sms().text("Welcome!").sms_from("+123 456 789")

Routing to notifiable

You should define the related route_notification_for_vonage method on your notifiable to return a phone number or a list of phone numbers to send the notification to.

class User(Model, Notifiable):

    def route_notification_for_vonage(self):
        return self.phone
        # or return [self.mobile_phone, self.land_phone]

Routing to anonymous

To send a SMS notification without having a notifiable entity you must use the route method

notification.route("vonage", "+33612345678").notify(Welcome())

Saving notifications

Notifications can be stored in your application database when sent to Notifiable entities. The notification is stored in a notifications table. This table will contain information such as the notification type as well as a JSON data structure that describes the notification.

To store a notification in the database you should define a to_database method on the notification class to specify how to build the notification content that will be persisted.

class Welcome(Notification):

    def to_database(self, notifiable):
        return {"data": "Welcome {0}!".format(notifiable.name)}

    def via(self):
        return ["mail", "database"]

This method should return str, dict or JSON data (as it will be saved into a TEXT column in the notifications table). You also need to add database channel to the via method to enable database notification storage.

Initial setup

Before you can store notifications in database you must create the database notifications table.

$ python craft notification:table

Then you can migrate your database

$ python craft migrate

The ORM Model describing a Notification is DatabaseNotification and has the following fields:

  • id is the primary key of the model (defined with UUID4)

  • type will store the notification type as a string (e.g. WelcomeNotification)

  • read_at is the timestamp indicating when notification has been read

  • data is the serialized representation of to_database()

  • notifiable is the relationship returning the Notifiable entity a notification belongs to (e.g. User)

  • created_at, updated_at timestamps

Querying notifications

A notifiable entity has a notifications relationship that will returns the notifications for the entity:

user = User.find(1)
user.notifications.all() # == Collection of DatabaseNotification belonging to users

You can directly get the unread/read notifications:

user = User.find(1)
user.unread_notifications.all() # == Collection of user unread DatabaseNotification
user.read_notifications.all() # == Collection of user read DatabaseNotification

Managing notifications

You can mark a notification as read or unread with the following mark_as_read and mark_as_unread methods

user = User.find(1)

for notification in user.unread_notifications.all():
    notification.mark_as_read()

Finally, keep in mind that database notifications can be used as any Masonite ORM models, meaning you can for example make more complex queries to fetch notifications, directly on the model.

from masonite.notification import DatabaseNotification

DatabaseNotification.all()
DatabaseNotification.where("type", "WelcomeNotification")

Broadcasting Notifications

If you would like to broadcast the notification then you need to:

  • inherit the CanBroadcast class and specify the broadcast_on method

  • define a to_broadcast method on the notification class to specify how to build the notification content that will be broadcasted

class Welcome(Notification, CanBroadcast):

    def to_broadcast(self, notifiable):
        return f"Welcome {notifiable.name} !"

    def broadcast_on(self):
        return "channel1"

    def via(self, notifiable):
        return ["broadcast"]

Broadcasting to notifiables

By default notifications will be broadcasted to channel(s) defined in broadcast_on method but you can override this per notifiable by implementing route_notification_for_broadcast method on your notifiable:

class User(Model, Notifiable):

    def route_notification_for_broadcast(self):
        return ["general", f"user_{self.id}"]

Broadcasting to anonymous

notification.route("broadcast", "channel1").notify(Welcome())

Adding a new driver

Masonite ships with a handful of notification channels, but you may want to write your own drivers to deliver notifications via other channels. Masonite makes this process simple.

Creating the driver

Two methods need to be implemented in order to create a notification driver: send and queue.

from masonite.notification.drivers import BaseDriver

class VoiceDriver(BaseDriver):

    def send(self, notifiable, notification):
        """Specify here how to send the notification with this driver."""
        data = self.get_data("voice", notifiable, notification)
        # do something

get_data() method will be available and will return the data defined in the to_voice() method of the notification.

Registering the driver

As any drivers it should be registered, through a custom provider for example:

from masonite.providers import Provider

class VoiceNotificationProvider(Provider):

    def register(self):
        self.application.make("notification").add_driver("voice", VoiceDriver(self.application))

Then you could scaffold this code into a new Masonite package so that community can use it 😉 !

Advanced Usage

Dry run

You can enable dry notifications to avoid notifications to be sent. It can be useful in some cases (background task, production commands, development, testing...)

user.notify(WelcomeNotification(), dry=True)
# or
notification.send(WelcomeNotification(), dry=True)

Ignoring errors

When fail_silently parameter is enabled, notifications sending will not raise exceptions if an error occurs.

user.notify(WelcomeNotification(), fail_silently=True)
# or
notification.send(WelcomeNotification(), fail_silently=True)

Overriding channels

Channels defined in via() method of the notification can be overriden at send:

class Welcome(Notification):
    def to_mail(self, notifiable):
        #...

    def to_slack(self, notifiable):
        #...

    def to_database(self, notifiable):
        #...

    def via(self):
        """Default behaviour is to send only by email."""
        return ["mail"]

Using channels parameter we can send to other channels (if correctly defined in notification class):

user.notify(Welcome(), channels=["slack", "database"])
# or
notification.send(Welcome(), channels=["slack", "database"])

Task Scheduling

Masonite ships with an incredibly simple way to run recurring tasks. Tasks could be as simple as cleaning records on a database table every minute, or syncing records between databases, or sending invoices out at the end of the month. They are those automated recurring tasks you need to run on a schedule, like every minute, every hour, every day, every month, or anywhere in between.

Creating Tasks

Tasks are what you use to register to the Masonite scheduler do it knows which tasks to run and how often.

To create a task, simply run the command:

$ python craft task SendInvoices

This will create a task that looks like this:

from masonite.scheduling import Task

class SendInvoices(Task):
    def handle(self):
        pass

You can change the task to do whatever you need it to do:

from masonite.scheduling import Task

class SendInvoices(Task):
    def handle(self):
      users = User.have_invoices().get()
      for user in users:
        # send invoice to user
        pass

Registering Tasks

from app.tasks.SendInvoices import SendInvoices

class AppProvider(Provider):

    def register(self):
        self.application.make('scheduler').add(
          SendInvoices().daily()
        )

Tasks will run as often as you specify them to run using the time options.

Options

You can specify the tasks to run as often as you need them to. Available options are:

Option
Description

every_minute()

Specifies this task to run every minute

every_15_minutes()

Specifies this task to run every 15 minutes

every_30_minutes()

Specifies this task to run every 30 minutes

every_45_minutes()

Specifies this task to run every 45 minutes

hourly()

Specifies this task to run every hour.

daily()

Specifies this task to run every day at midnight

weekly()

Specifies this task to run every week on sunday at 00:00

monthly()

Specifies this task to run every first of the month at 00:00

at(17)

Specifies the time to run the job at. Can be used with other options like daily()

run_every('7 minutes')

Specifies the amount of time to run. Can be any combination of time like 7 months, 4 days, 3 weeks.

daily_at(17)

Runs every day at the specified time. Time is in 24-hour time. 8 is "8 am" and 17 is "5pm".

at_twice([8,17])

Runs at 8am and 5pm.

Running The Tasks

To run all the tasks that are registered, we can find and execute the ones that should run depending on the time of the computer / server:

$ python craft schedule:run

To run only a specific registered task we can use --task option:

$ python craft schedule:run --task MyTask

Finally we can force task(s) to run immediately we can use --force option. This can be especially useful when developing locally.

$ python craft schedule:run --task MyTask --force

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 to schedule:run above is useful and needed, we still need a way to run it every minute. We can do this with a cronjob on the server

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.7/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Frameworks/Python.framework/Versions/3.7/bin

* * * * * cd /Users/Masonite/Programming/project_name && source venv/bin/activate && python 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.7/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Frameworks/Python.framework/Versions/3.7/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 -eqcszae3zd4rfdsxs

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.7/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Frameworks/Python.framework/Versions/3.7/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.

Service Providers

Although uncommon, 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:

$ python craft provider DashboardProvider

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

Service Provider Execution

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

from masonite.providers import Provider


class YourProvider(Provider):
    def __init__(self, application):
        self.application = application

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

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

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

Register

In our register method, it's important that we only bind things into the container. When the provider is first registered to the container, the register method is ran and your classes will be registered to the container.

Boot

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

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

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

    def __init__(self, application):
        self.application = application

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

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

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

Registering the Service Provider

Once you create your own service provider, it will need to be registered in the PROVIDERS list. This is likely in your config/providers.py file:

PROVIDERS=[
    #..
    UserModelProvider,
]

Once registered it will take your register and boot method into account when the framework boots up or registers.

Tinker Shell (REPL)

Masonite Tinker is a powerful REPL (Read, Evaluate, Print and Loop) environment for the Masonite framework. It's a supercharged Python interactive shell with access to the container, models and helpers.

Tinker allows you to interact with your entire Masonite project on the command line, including models, jobs, events, and more. To enter the Tinker environment, run the tinker command:

python craft tinker

This will open a Python shell with the application container (under the app variable), the application models and some helpers imported for you.

  • Syntax highlighting

  • Tab completion of python variables and keywords, filenames and function keywords

  • Input history, persistent across sessions

  • Integrated access to the pdb debugger and the Python profiler

  • and much more...

You just need to use -i option and install IPython if not installed yet (pip install IPython):

python craft tinker -i

Configuration

Auto-loading Models

By default your app models are loaded from the location configured in your project Kernel. You can override the directory to load models from with the -d flag. It should be a path relative to your project root. For example you can run the following command if your models are located in a models/ folder located at your project root:

python craft tinker -d models/

Startup script

You can use PYTHONSTARTUP environment variable to add a script that you want to run at the beginning of the shell session.

With IPython you can use this variable or put some Python scripts in ~/.ipython/profile_default/startup/. IPython will run those scripts for you at the beginning of the shell session.

CORS

CORS is sometimes important to activate in your application. CORS allows you to have a stronger layer of security over your application by specifying specific CORS headers in your responses. This is done through "preflight" requests.

These "preflight" requests are OPTIONS requests that are sent at the same time as other non safe requests (POST, PUT, DELETE) and specifically designed to verify the CORS headers before allowing the other request to go through.

You can enable CORS protection by simply adding the CorsMiddleware into you middleware stack.

Getting Started

To enable CORS in Masonite, you just need to add the CorsMiddleware in your middleware stack to your http_middleware key in your Kernel.py class.

It is best to put the CORS middleware as the first middleware to prevent possible errors.

from masonite.middleware import CorsMiddleware

#..
class Kernel:

    http_middleware = [
        CorsMiddleware,
        EncryptCookies
    ]
    #..

Your application will now handle CORS requests to successfully go through.

Configuration

All CORS settings are configured in the dedicated security configuration file config/security.py. The default configuration options are:

CORS = {
    "paths": ["api/*"],
    "allowed_methods": ["*"],
    "allowed_origins": ["*"],
    "allowed_headers": ["*"],
    "exposed_headers": [],
    "max_age": None,
    "supports_credentials": False,
}

Paths

You can define paths for which you want CORS protection to be enabled. Multiple paths can be defined in the list and wildcards (*) can be used to define the paths.

    "paths": ["api/*", "auth/"]

Here all requests made to auth/ and to all API routes will be protected with CORS.

Allowed Methods

The default is to protect all HTTP methods but a list of methods can be specified instead. This will set the Access-Control-Allow-Methods header in the response.

For example CORS can be enabled for sensitive requests:

    "allowed_methods": ["POST", "PUT", "PATCH"]

Allowed Origins

The default is to allow all origins to access a resource on the server. Instead you can define a list of origins allowed to access the resources defined above (paths). Wildcards (*) can be used. This will set the Access-Control-Allow-Origin header in the response.

    "allowed_origins": ["*.example.com"]

Here blog.example.com and forum.example.com will e.g. be authorized to make requests to the application paths defined above.

Allowed Headers

The default is to authorized all request headers during a CORS request, but you can define a list of headers confirming that these are permitted headers to be used with the actual request. This will set the Access-Control-Allow-Headers header in the response.

    "allowed_headers": ["X-Test-1", "X-Test-2"]

Exposed Headers

The default is an empty list but you can define which headers will be accessible to the broswser e.g. with Javascript (with getResponseHeader()). This will set the Access-Control-Expose-Headers header in the response.

    "exposed_headers": ["X-Client-Test-1"]

Max Age

This will set the Access-Control-Max-Age header in the response and will indicates how long the results of a preflight request can be cached. The default is None meaning it preflight request results will never be cached.

You can indicate a cache duration in seconds:

    "max_age": 3600

Supports Credentials

This will set the Access-Control-Allow-Credentials and will indicate whether or not the response to the request can be exposed when the credentials flag is true. The default is False.

    "supports_credentials": True

Service Container

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

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?

Masonite will also resolve your custom, application-specific classes including those that you have not explicitly bound with app.bind().

Continuing with the example above, the following will work out of the box (assuming the relevant classes exist), without needing to bind the custom classes in the container:

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

Console Tests

You can test what has been output to standard console during Masonite unit tests thanks to useful console assertions.

Output here is the standard output often named stdout. Error here is the standard error often named stderr.

External packages, prints in your code can output content in console (as output or error).

Available Assertions

The following assertions are available:

assertConsoleEmpty

Assert that nothing has been printed to the console.

assertConsoleNotEmpty

Assert that something has been printed to the console (output or error).

assertConsoleExactOutput

Assert that console standard output is equal to given output.

assertConsoleOutputContains

Assert that console standard output contains given output.

assertConsoleOutputMissing

Assert that console standard output does not contain the given output.

assertConsoleHasErrors

Assert that something has been output to console standard error.

assertConsoleExactError

Assert that console standard error is equal to given error.

assertConsoleErrorContains

Assert that console standard error contains given error.

Database Tests

By default, your tests are are not ran in isolation from a database point of view. It means that your local database will be modified any time you run your tests and won't be rollbacked at the end of the tests. While this behaviour might be fine in most case you can learn below how to configure your tests cases to reset the database after each test.

Resetting The Database After Each Test

If you want to have a clean database for each test you must subclass the TestCase class with DatabaseTransactions class. Then 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.

Note that you can define the connection that will be used during testing. This will allow you to select a different database that will be used for testing. Here is a standard exemple of database configuration file that you can use.

Available Assertions

Masonite provides several database assertions that can be used during testing.

assertDatabaseCount

Assert that a table in the database contains the given number of records.

assertDatabaseHas

Assert that a table in the database contains records matching the given query.

assertDatabaseMissing

Assert that a table in the database does not contain records matching the given query.

assertDeleted

Assert that the given model instance has been deleted from the database.

assertSoftDeleted

Getting Started

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 lower_case_with_underscores. Just know that all TestCase assertions used during testing is in camelCase form to maintain unittest standards.

Testing Environment

You can create a .env.testing file. Feel free to load any testing environment variables in here. By default they will not be commited. When pytest runs it will additionally load and override any additional environment variables.

Creating Tests

You can simply create a file starting with test_ and then creating a test class inheriting from masonite TestCase class.

You can also directly use the command

to create tests/unit/test_some_feature.py:

That's it! You're ready to start testing. Read on to learn how to build your test cases and run them.

Running Tests

You can run tests by calling

Or a specific test method

Finally you can re-run the last failed tests automatically

Building Tests

Test Life Cycle

When you run a test class each test method of this test class will be ran following a specific life cycle.

Running the above test class will create this output:

Note that tests methods are not always ran in the order specified in the class. Anyway you should not make the assumptions that tests will be run in a given order. You should try to make your tests idempotent.

Chaining Assertions

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.

Asserting Exceptions

Sometimes you need to assert that a given piece of code will raise a given exception. To do this you can use the standard assertRaises() context manager:

Capturing Output

Sometimes you need to test the output of a function that prints to the console. To do this in your tests you can use the captureOutput() context manager:

Overriding Debug Mode

Sometimes you would need to change the debug mode value during the lifetime of a test. To do this you can use the debugMode() context manager:

Dumping Data

During tests execution, print() statements will not be visible. You can use the dump() test helper to dump data to console during a test:

Note that you can provide a second argument to name the dump in console.

Stopping Test

If you want to programmatically stop the test execution you can use the stop() helper. You can even provide a reason.

Test are stopped by returning a pytest 2 exit code (user interruption).

Test Helpers

Masonite comes with different helpers that can ease writing tests. Some of them have already been explained in sections above.

withExceptionsHandling

Enable exceptions handling during testing.

withoutExceptionsHandling

Disable exceptions handling during testing.

Note that exception handling is disabled by default during testing.

withCsrf

Enable CSRF protection during testing.

withoutCsrf

Disable CSRF protection during testing.

Note that CSRF protection is disabled by default during testing.

withCookies

Add cookies that will be used in the next request. This method accepts a dictionary of name / value pairs. Cookies dict is reset between each test.

withHeaders

Add headers that will be used in the next request. This method accepts a dictionary of name / value pairs. Headers dict is reset between each test.

fakeTime

Set a given pendulum instance to be returned when a now (or today, tomorrow yesterday) instance is created. It's really useful during tests to check timestamps logic.

This allow to control which datetime will be returned to be able to always have an expected behaviour in the tests.

When using those helpers you should not forget to reset the default pendulum behaviour with restoreTime() helper to avoid breaking other tests. It can be done directly in the test or in a tearDown() method.

fakeTimeTomorrow

Set the mocked time as tomorrow. (It's a shortcut to avoid doing self.fakeTime(pendulum.tomorrow())).

fakeTimeYesterday

Set the mocked time as yesterday.

fakeTimeInFuture

Set the mocked time as an offset of a given unit of time in the future. Unit can be specified among pendulum units: seconds, minutes, hours, days (default), weeks, months, years.

fakeTimeInPast

Set the mocked time as an offset of a given unit of time in the past. Unit can be specified among pendulum units: seconds, minutes, hours, days (default), weeks, months, years.

restoreTime

Restore the mocked time behaviour to default behaviour of pendulum. When using fake time helpers you should not forget to call this helper at the end.

It can be done directly in the test or in a tearDown() method.

HTTP Tests

To make a request in your tests, you may use the get, post, put, patch, or delete methods within your test. These methods do not actually issue a "real" HTTP request to your application. Instead of returning a Masonit class Response instance, test request methods return a HTTPTestResponseinstance, which provides a variety of helpful assertions that allow you to inspect and assert application's responses.

Getting request and response

The request and responses of a test can be fetch by accessing the request and response attributes.

Registering routes

During tests you can register routes used only for testing purposes. For this you can use the addRoutes() method at beginning of your tests:

Checking if a route exists

To check if a route exists, we can simple use either get or post:

Request Headers

You may use the withHeaders() method to customize the request's headers before it is sent to the application. This method allows you to add any headers you would like to the request by providing them as a dict:

Request Cookies

You may use the withCookies() method to set cookie values before making a request. This method accepts a dictionary of name / value pairs:

Authentication

If you want to make authenticated requests in your tests, you can use the actingAs() method that takes a given User record and authenticate him during the request.

The user will be persisted only during the lifetime of the test. Each request made during the test will be authenticated with the given user. If you need to logout the user in the test, you can use actingAsGuest() method:

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.

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:

Again you can disable this behaviour with withoutCsrf() method.

Exceptions 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 exceptions handling and you will see more useful exceptions during testing. If you want to use Masonite's built in exceptions handling then you can enable it by running:

You can also disable exceptions handling again by using:

Available Assertions

Masonite provides a variety of assertions methods to inspect and verify request/response logic when testing your application. Those assertions are available on the HTTPTestResponse returned by get, post, put, patch, or delete.

assertContains

Assert that returned response contains the given content string. Note that the response content will be eventually decoded if required.

assertNotContains

Assert that returned response does not contain the given content string. Note that the response content will be eventually decoded if required.

assertContainsInOrder

Assert that returned response contains in order the given strings. Note that the response content will be eventually decoded if required.

assertNoContent

Assert that returned response has no content and the given HTTP status code. The default status code that is asserted is 204.

assertIsNamed

Assert that given route has the given name.

assertIsNotNamed

Assert that given route has not the given name.

assertIsStatus

Assert that the response has the given HTTP status code:

assertOk

Assert that the response returns a 200 status code:

assertCreated

Assert that the response returns a 201 status code:

assertSuccessful

Assert that the response has as status code between 200 and 300

assertUnauthorized

Assert that the response has as 401 status code

assertForbidden

Assert that the response has as 403 status code

assertError

Assert that the response has as 500 status code

assertHasHeader

Assert that the response has the given header name and value (if given).

assertHeaderMissing

Assert that the response does not have the given header.

assertLocation

Assert the response has the given URI value in Location header.

assertRedirect

Assert that the response is a redirection to the given URI (if provided) or to the given route name with the given parameters (if provided).

assertCookie

Assert that the request contains the given cookie name and value (if provided).

assertPlainCookie

Assert that the request contains the given unencrypted cookie name

assertCookieExpired

Assert that the request contains the given cookie name and is expired.

assertCookieMissing

Assert that the request does not contain the given cookie.

assertSessionHas

Assert that the session contains the given key and value (if provided).

assertSessionMissing

Assert that the session does not contain the given key.

assertSessionHasErrors

Assert that the session contains an errors key or contains the given list of keys in errors key.

assertSessionHasNoErrors

Assert that the session does not contain an errors key or that this key is empty.

assertViewIs

Assert that the route returned the given view name.

assertViewHas

Assert that view context contains a given data key and value (if provided).

assertViewHasExact

Assert that view context contains exactly the given data keys. It can be a list of keys or a dictionary (here only keys will be verified).

assertViewMissing

Assert that given data key is not available in the view context.

assertAuthenticated

Assert that a user is authenticated after the current request.

If a user instance is given it will assert that this user is authenticated:

assertGuest

Assert that a user is not authenticated after the current request.

assertHasHttpMiddleware

Assert that the request has the given HTTP middleware. An HTTP middleware class should be provided.

assertHasRouteMiddleware

Assert that the request has the given route middleware. The registration key of the middleware should be provided.

assertHasController

Assert that the route used the given controller. A class or a string can be provided. If it's a string it should be formatted as follow ControllerName@method.

assertRouteHasParameter

Assert that the route used has the given parameter name and value (if provided).

assertJson

Assert that response is JSON and contains the given data dictionary (if provided). The assertion will pass even if it is not an exact match.

assertJsonPath

Assert that response is JSON and contains the given path, with eventually the given value if provided. The path can be a dotted path.

assertJsonExact

Assert that response is JSON and is strictly equal to the given dictionary.

assertJsonCount

Assert that response is JSON and has the given count of keys at root level or at the given key (if provided).

assertJsonMissing

Assert that response is JSON and does not contain given path. The path can be a dotted path.

Dump Data During Tests

Handy dump and dd helpers are available on the HTTPTestResponse returned by get, post, put, patch, or delete during a unit test.

dumpRequestHeaders

After getting a test response back from a request you can dump request headers in console by chaining this helper to the response:

dumpResponseHeaders

After getting a test response back from a request you can dump response headers in console:

ddHeaders

After getting a test response back from a request you can dump response and request headers in console and stop the test execution:

Here assertSessionHas will not be executed as the test will be stopped before.

dumpSession

After getting a test response back from a request you can dump session data in console by chaining this helper to the response:

ddSession

After getting a test response back from a request you can dump session data in console and stop the test execution:

Here assertSessionHas will not be executed as the test will be stopped before.

Hashing

Masonite provides secure hashing for storing user passwords or other data. Bcrypt and Argon2 protocols can be used with Masonite (default is Bcrypt).

Configuration

Hashing configuration is located at config/application.py file. In this file, you can configure which protocol to use.

Hashing a string

You can use the Hash facade to easily hash a string (e.g. a password):

Note that you can return a hash as bytes with:

Checking a string matches a Hash

To check that a plain-text string corresponds to a given hash you can do:

Verifying a Hash needs to be re-hashed

You can determine if the work factor used by the hashing protocol has changed since the string was hashed using needs_rehash:

Options

You can change hashing protocol configuration on the fly for all Hash methods:

You can also change protocol on the fly:

This feature is based on the application's features. It will store the number of attempts of a given key and will also store the associated time window.

You can browse Masonite packages (official and community) on: (alpha version).

GitHub template

The is just a GitHub template so you only have to click Use this template to create your own GitHub repository scaffolded with the default package layout and then clone your repository to start developing locally.

The is using cookiecutter package to scaffold your package with configuration options (name, author, url...). The advantages is that you won't need to edit all the occurences of your package name after generation.

The default package layout comes with a test project located in tests/integrations. This project is really useful to directly test your package behaviour. It is scaffolded as a default Masonite project with your package already installed (see section). You can run the project with the usual command:

If your package has migrations and you want to migrate your test project you should first , publish them and then run the usual migrate command:

Once you're satisfied with your package it's time to release it on so that everyone can install it. We made it really easy to do this with the make commands.

If you never uploaded a package on PyPi before you will need to . Verify your email address.

This will install twine if not installed yet, build the package, upload it to PyPi and delete the build artifacts. You should then see a success message and be able to browse your package on .

To make your package available on (alpha version) you need to add Framework :: Masonite classifier in setup.py:

You can find more information on the .

The command will publish the configuration into the defined project configuration folder. With the default project settings it would be in config/super_awesome.py.

The command will publish the migrations files into the defined project migrations folder. With the default project settings it would be in databases/migrations/. Migrations file are published with a timestamp, so here it would result in those two files: {timestamp}_create_some_table.py and {timestamp}_create_other_table.py.

If you want to allow users to publish the view file into their own project so they can tweak them you should add publish=True argument. The command will publish the views files into the defined project views folder. With the default project settings it would be in templates/vendor/super_awesome/admin/index.html.

The command will publish the assets into the defined project resources folder. With the default project settings it would be in resources/vendor/super_awesome/.

registering the package in your project:

Masonite comes with a simple way to store sessions. Masonite supports the following session drivers: and .

Get easily access to :

If you would like to use an other delivery channel, feel free to check if a community driver has been developed for it or !

If you want to send notifications e.g. to your users inside your application, you can . Then you can still use Notification class to send notification:

The notification will be sent using the default mail driver defined in config/mail.py. For more information about options to build mail notifications, please check out .

.send_from('Masonite Bot', icon="")

Slack Incoming Webhooks

Slack Web API

You will need to integration for your Slack workspace. This integration will provide you with a URL you may use when routing Slack notifications. This URL will target a specific Slack channel.

You will need to to interact with your Slack workspace.

Slack notifications can use to build more complex notifications. Before using this you just have to install slackblocks python API to handle Block Kit formatting.

You can find all blocks name and options in and more information in .

a webhook URL or a list of webhook URLs (if you're using )

a channel name/ID or a list of channels names/IDs (if you're using )

Sending SMS notifications in Masonite is powered by (formerly Nexmo). Before you can send notifications via Vonage, you need to install the vonage Python client.

You must then register tasks to the Masonite scheduler. You can do so easily inside your app (if you don't have one, you should ):

Service Providers are the key building blocks to Masonite. The only thing they do is register things into the , or run logic on requests. 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.

Finally you can get an enhanced experience by using the Tinker IPython shell. is an improved Python shell offering some interesting features:

To learn more about CORS please read .

You can read more about it .

If you want to assert content output by a Masonite command you should use assertions instead.

Assert that the given model instance has been from the database.

When running tests, Masonite will automatically set the to testing. You are free to define other testing environment configuration values as necessary.

This will automatically discover your tests following pytest . You can also run a specific test class

The can also be specified to authenticate the user with the given guard:

The can also be specified to check the authentication state on the given guard.

The can also be specified to check the authentication state on the given guard.

Cache
packages.masoniteproject.com
starter-package
cookiecutter template
starter-package
cookiecutter template
PyPi
register
PyPi
packages.masoniteproject.com
website FAQ
Service Provider
application container
create your own driver and share it with the community
more here
more here
configure an "Incoming Webhook"
generate a token
Slack Blocks Kit
slackblocks documentation
Slack blocks list
Vonage
Service Container
IPython
MDN documentation
here
Installing a Package
register your migrations
package publish
package publish
package publish
package publish
Cookie
Redis
define them as notifiables
Incoming Webhooks
Slack Web API
Mailable options
Service Provider
create one
from masonite.provider import ServiceProvider
from app.User import User

class UserModelProvider(ServiceProvider):

    def register(self):
        self.application.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.application.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.application.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()
# elsewhere...
class MyService:
    def __init__(self, some_other_dependency: SomeOtherClass):
        pass
    
    def do_something(self):
        pass


# in a controller...
def show(self, request: Request, service: MyService):
    request.user()
    service.do_something()
from masonite.contracts import UploadContract

def show(self, upload: UploadContract)
    upload # <class masonite.drivers.UploadDiskDriver>
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.application.swap(Mail, manager.driver(self.application.make('MailConfig').DRIVER))
from masonite import Mail
from somewhere import NewObject
...

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

def boot(self):
    self.application.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)
print("Success !")
self.assertConsoleExactOutput("Success !\n")
print("Success !")
self.assertConsoleOutputContains("Success")
print("Success !")
self.assertConsoleOutputMissing("hello")
print("An error occured !", file=sys.stderr)
self.assertConsoleExactError("An error occured !\n")
print("An error occured !", file=sys.stderr)
self.assertConsoleErrorContains("error")
from masonite.tests import TestCase, DatabaseTransactions

class TestSomething(TestCase, DatabaseTransactions):

  connection = "testing"

  def test_can_create_user(self):
      User.create({"name": "john", "email": "john6", "password": "secret"})
# config/database.py
DATABASES = {
    "default": "mysql",
    "mysql": {
        "host": "localhost",
        "driver": "mysql",
        "database": "app",
        "user": "root",
        "password": "",
        "port": 3306
    }
    "testing": {
        "driver": "sqlite",
        "database": "test_database.sqlite3",
    },
}
self.assertDatabaseCount(table, count)
  def test_can_create_user(self):
      User.create({"name": "john", "email": "john6", "password": "secret"})
      self.assertDatabaseCount("users", 1)
self.assertDatabaseHas(table, query_dict)
self.assertDatabaseCount("users", {"name": "John"})
self.assertDatabaseMissing(table, query_dict)
self.assertDatabaseMissing("users", {"name": "Jack"})
user=User.find(1)
user.delete()
self.assertDeleted(user)
self.assertSoftDeleted(user)
$ python craft test SomeFeatureTest
from masonite.tests import TestCase


class SomeFeatureTest(TestCase):
    def setUp(self):
        super().setUp()

    def test_something(self):
        self.assertTrue(True)
$ python -m pytest
$ python -m pytest tests/unit/test_my_feature.py
$ python -m pytest tests/unit/test_my_feature.py::MyFeatureTest::test_feature_is_working
$ python -m pytest --last-failed
class TestFeatures(TestCase):

    @classmethod
    def setUpClass(cls):
        """Called once before all tests of this class are executed."""
        print("Setting up test class")

    @classmethod
    def tearDownClass(cls):
        """Called once after all tests of this class are executed."""
        print("Cleaning up test class")

    def setUp(self):
        """Called once before each test are executed."""
        super().setUp()
        print("Setting up individual unit test")

    def tearDown(self):
        """Called once after each test are executed."""
        super().tearDown()
        print("Cleaning up individual unit test")

    def test_1(self):
        print("Running test 1")

    def test_2(self):
        print("Running test 2")
Setting up test class
Setting up individual unit test
Running test 2
Cleaning up individual unit test
Setting up individual unit test
Running test 1
Cleaning up individual unit test
Cleaning up test class
with self.assertRaises(ValidationError) as e:
    # run some code here
    raise ValidationError("An error occured !")

self.assertEqual(str(e.exception), "An error occured !")
with self.captureOutput() as output:
    # run some code here
    print("Hello World !")

self.assertEqual(output.getvalue().strip(), "Hello World !")
# run the code context with DEBUG enabled
with self.debugMode() as output:
    self.get("/").assertError()

# run the code context with DEBUG disabled
with self.debugMode(False) as output:
    self.get("/").assertError()
def test_can_create_user(self):
    user = User.find(1)
    self.get("/register").assertRedirect()
    self.dump("Hello")
    self.dump(user, "User with ID 1")
def test_can_create_user(self):
    user = User.find(1)
    self.get("/register").assertRedirect()
    self.stop("for debugging")  #== the test will be stopped here.

    self.post("/login", {"email": user.email, "password": "secret"})
self.withExceptionsHandling()
self.withoutExceptionsHandling()
self.withCsrf()
self.withoutCsrf()
self.withCookies(data)
self.withHeaders(data)
given_date = pendulum.datetime(2021, 2, 5)
self.fakeTime(given_date)
self.assertEqual(pendulum.now(), given_date)
tomorrow = pendulum.tomorrow()
self.fakeTimeTomorrow()
self.assertEqual(pendulum.now(), tomorrow)
self.fakeTimeInFuture(offset, unit="days")
real_now = pendulum.now()
self.fakeTimeInFuture(1, "months")
self.assertEqual(pendulum.now().diff(real_now).in_months(), 1)
self.fakeTimeInPast(offset, unit="days")
def tearDown(self):
    super().tearDown()
    self.restoreTime()

def test_creation_date(self):
    self.fakeTimeYesterday()
    # from now on until the end of this unit test, time is mocked and will return yesterday time
def test_basic_request(self):
    self.get("/").assertOk()
def test_request_and_response(self):
    request = self.get('/testing').request # <masonite.requests.Request>
    response = self.get('/testing').response # <masonite.response.Response>
@classmethod
def setUpClass(cls):
    super().setUpClass()
    cls.addRoutes(
        Route.get("/", "TestController@show"),
        Route.post("/", "TestController@show"),
    )
def test_route_exists(self):
    self.assertTrue(self.get('/testing'))
    self.assertTrue(self.post('/testing'))
request = self.withHeaders({"X-TEST": "value"}).get("/").request
self.assertEqual(request.header("X-Test"), "value")
self.withCookies({"test": "value"}).get("/").assertCookie("test", "value")
user = User.find(1)
self.actingAs(user).get("/")
user = User.find(1)
self.actingAs(user, "web").get("/")
def test_auth(self):
    user = User.find(1)
    self.actingAs(user).get("/home")

    self.actingAsGuest().get("/about")
def test_csrf(self):
    self.withCsrf()
    self.post("/unit/test/json", {"test": "testing"})
def setUp(self):
    super().setUp()
    self.withCsrf()
def setUp(self):
    super().setUp()
    self.withExceptionHandling()
def test_something(self):
    self.withExceptionHandling()
    self.get("/").assertUnauthorized()
    self.withoutExceptionHandling()
    self.get("/")
self.get("/").assertContains(content)
self.get("/").assertNotContains(content)
self.get("/").assertContains(string1, string2, ...)
self.get("/").assertNoContent(status=204)
self.get("/").assertIsNamed("home")
self.get("/").assertIsNotNamed("admin")
self.get("/").assertIsStatus(201)
self.get("/").assertOk()
self.get("/").assertCreated()
self.get("/").assertSuccessful()
self.get("/").assertUnauthorized()
self.get("/").assertForbidden()
self.get("/").assertError()
self.get("/").assertHasHeader(name, value=None)
self.get("/").assertHeaderMissing(name)
self.get("/").assertLocation(uri)
self.get("/logout").assertRedirect(url=None, name=None, params={})
self.get("/logout").assertRedirect()
self.get("/logout").assertRedirect("/login")
self.get("/login").assertRedirect(name="profile", params={"user": 1})
self.get("/").assertCookie(name, value=None)
self.get("/").assertPlainCookie(name)
self.get("/").assertCookieExpired(name)
self.get("/").assertCookieMissing(name)
self.get("/").assertSessionHas(name, value=None)
self.get("/").assertSessionMissing(name)
self.get("/").assertSessionHasErrors()
self.get("/").assertSessionHasErrors(["email", "first_name"])
self.get("/").assertSessionHasNoErrors()
self.get("/").assertViewIs("app")
self.get("/").assertViewHas(key, value=None)
self.get("/").assertViewHasExact(keys)
self.get("/").assertViewMissing(key)
self.get("/login").assertAuthenticated()
user = User.find(1)
self.get("/login").assertAuthenticated(user)
self.get("/api/login").assertAuthenticated(user, "jwt")
self.get("/").assertGuest()
self.get("/").assertHasHttpMiddleware(middleware_class)
from app.middleware import MyAppMiddleware
self.get("/").assertHasHttpMiddleware(MyAppMiddleware)
self.get("/").assertHasRouteMiddleware(middleware_name)
# route_middleware = {"web": [SessionMiddleware, VerifyCsrfToken]}
self.get("/").assertHasRouteMiddleware("web")
self.get("/").assertHasController(controller)
from app.controllers import WelcomeController
self.get("/").assertHasController(WelcomeController)
self.get("/").assertHasController("WelcomeController@index")
self.get("/").assertRouteHasParameter(key, value=None)
self.get("/").assertJson(data={})
self.get("/").assertJson()  # check that response is JSON
self.get("/").assertJson({"key": "value", "other": "value"})
self.get("/").assertJsonPath(path, value=None)
self.get("/").assertJsonPath("user.profile.name", "John")
self.get("/").assertJsonExact(data)
self.get("/").assertJsonCount(count, key=None)
self.get("/").assertJsonMissing(path)
self.get("/register").assertRedirect().dumpRequestHeaders().assertSessionHas("success")
self.get("/").dumpResponseHeaders()
self.get("/register").assertRedirect().ddHeaders().assertSessionHas("success")
self.get("/register").assertRedirect().dumpSession().assertSessionHas("success")
self.get("/register").assertRedirect().ddSession().assertSessionHas("success")
config/application.py
HASHING = {
    "default": "bcrypt",
    "bcrypt": {"rounds": 10},
    "argon2": {"memory": 1024, "threads": 2, "time": 2},
}
from masonite.facades import Hash

Hash.make("secret") #== $2b$10$3Nm9sWFYhi.GUJ...
from masonite.facades import Hash

Hash.make_bytes("secret") #== b"$2b$10$3Nm9sWFYhi.GUJ..."
from masonite.facades import Hash

Hash.check("secret", "$2b$10$3Nm9sWFYhi.GUJ...") #== True
from masonite.facades import Hash

Hash.needs_rehash("$2b$10$3Nm9sWFYhi.GUJ...") #== True
from masonite.facades import Hash

Hash.make("secret", options={"rounds": 5})
from masonite.facades import Hash

Hash.make("secret", driver="argon2", options={"memory": 512, "threads": 8, "time": 2})

Masonite 1.6

Introduction

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

HttpOnly Cookies

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

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

Moved Commands

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

pip install masonite-cli==2.0 --user

Drivers Can Now Change To Other Drivers

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

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

Now you can switch drivers from the driver itself:

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

New Absolute Controllers Syntax

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

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

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

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

Changed How Controllers Are Created

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

$ craft controller DashboardController

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

$ craft controller Dashboard

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

$ craft controller Dashboard -e

short for "exact"

Added Resource Controllers

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

You can now create these Resource Controllers like:

craft controller Dashboard -r

Added Global Views

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

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

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

Added Route Groups

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

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

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

Changed Container Resolving

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

from masonite.request import Request

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

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

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

from masonite.request import Request

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

This will now work when previously it did not.

Resolving Instances

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

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

from masonite.contracts.UploadContract import UploadContract

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

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

Container Collection

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

app.collect('Sentry*Hook')

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

Removed Some Dependencies

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

Framework Hooks

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

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 5.0

The primary goal of Masonite 5 was to ...TODO

We will attempt here to go through everything new in Masonite 5. This documentation section will be a mix of both what is new and why it is new.

Features

Logging

Refactoring

Validation

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:

from masonite.response import Download

def show(self):
    return Download('/path/to/file', force=True)

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:

{
    "filter[name]": "Joe",
    "filter[user]": "Bob",
    "email": "user@email.com"
}

It will now parse into:

{
    "email": "user@email.com",
    "filter": {
        "name": "Joe",
        "user": "bob"
    }
}

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:

app.collect('*Command')

Will collect everything in the container where the binding key ends with Command.

You can also collect everything that is a subclass:

from masonite.response import Responsable

app.collect(Responsable)

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:

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

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:

mail.to('user@example.com').reply_to('admin@me.com').template('emails.register').subject(..).send()

You can now do:

from app.mailables import RegistrationMailable
mail.mailable(RegistrationMailable()).send()

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:

def show(self, response: Response):
    return response.json({'error': 'unauthenticated'}, status=401)

Now you can simply return a tuple:

def show(self):
    return {'error': 'unauthenticated'}, 401

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.

👻
soft deleted
environment
automatic tests discovery
assertConsoleEmpty
assertConsoleNotEmpty
assertConsoleExactOutput
assertConsoleOutputContains
assertConsoleOutputMissing
assertConsoleHasErrors
assertConsoleExactError
assertConsoleErrorContains
assertDatabaseCount
assertDatabaseHas
assertDatabaseMissing
assertDeleted
assertSoftDeleted
withExceptionsHandling
withoutExceptionsHandling
withCsrf
withoutCsrf
withCookies
withHeaders
fakeTime
fakeTimeTomorrow
fakeTimeYesterday
fakeTimeInFuture
fakeTimeInPast
restoreTime
authentication guard
authentication guard
authentication guard
assertContains
assertNotContains
assertContainsInOrder
assertNoContent
assertIsNamed
assertIsNotNamed
assertIsStatus
assertNotFound
assertOk
assertCreated
assertSuccessful
assertUnauthorized
assertForbidden
assertError
assertHasHeader
assertHeaderMissing
assertLocation
assertRedirect
assertCookie
assertPlainCookie
assertCookieExpired
assertCookieNotExpired
assertCookieMissing
assertSessionHas
assertSessionMissing
assertSessionHasErrors
assertSessionHasNoErrors
assertViewIs
assertViewHas
assertViewHasExact
assertViewMissing
assertAuthenticated
assertGuest
assertHasHttpMiddleware
assertHasRouteMiddleware
assertHasController
assertRouteHasParameter
assertJson
assertJsonPath
assertJsonExact
assertJsonCount
assertJsonMissing
dumpRequestHeaders
dumpResponseHeaders
ddHeaders
dumpSession
ddSession

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

Masonite Debugbar is a really helpful way to see the stats of your application while you are developing. You can use this information to help debug errors you are getting or even optimize your models and queries for speed and memory issues.

Masonite Debugbar also supports AJAX calls which you will be able to see directly in a dropdown on your toolbar.

Install

Setting up Masonite Debugbar is simple.

First, install the package:

$ pip install masonite-debugbar

Put the provider at the end of your provider list:

from debugbar.providers import DebugProvider
PROVIDERS = [
    #.. 
    DebugProvider
]

Then publish the package:

$ python craft package:publish debugbar

Now when you go to a page in your application, you will see a debug bar at the bottom of the page.

Config

The configuration file is created on collectors

OPTIONS = {
    "models": True,
    "queries": True,
    "request": True,
    "measures": True,
    "messages": True,
    "environment": True,
}

Not all collectors may be equally important to you so you can set anyone of these to either True or False in order to enable or disable them in the debugbar.

Collectors

Masonite Debugbar collects data to show in your debugbar as a tab. Each collector is 1 tab in the debugbar.

Below is a detailed explanation of each collector

Model Collector

The model collector shows how many models are hydrated on that request. Whenever you make a query call, a model instance has to be created to store that rows data. This could be a costly operation depending on how many rows are in the table you are calling.

Queries Collector

The queries collector shows all the queries made during that request as well as the time it took to perform that query. Use this to see where bottle necks are in your application. Slow queries lead to slow load times for your users.

Request Collector

The request collector shows you information related to the request such as inputs, parameters and headers.

Message Collector

The message collector contains messages you can add in your application. Instead of adding a bunch of print statements you can add a message:

from debugbar.facades import Debugbar

Debugbar.get_collector('messages').add_message("a debug message")

You could also add tags which will create a colored tag in the content tab:

Debugbar.get_collector('messages').add_message("a debug message", tags={"color": "green", "message": "tag name"})

Environment Collector

This collector adds all of your environment variables to your debugbar as well as the Python and Masonite versions.

Measures Collector

The measures collector you can use to measure 2 points in your application. By default there is the time it takes for your application to complete the whole request. You can start and stop any measures you want:

from debugbar.facades import Debugbar

Debugbar.start_measure("loop check")
# .. Long running code
Debugbar.stop_measure("loop check")

You will now see the time it takes to run this code in the measures tab

Adding Your Own Collectors

If you find a need to create your own collector, maybe to log information related to exceptions or something similar, you can create your own collector simply:

Creating the Collector

Collectors are simple instances like this:

class YourCollector:
    def __init__(self, name="Your Collector", description="Description"):
        self.messages = []
        self.name = name
        self.description = description

    def restart(self):
        self.messages = []
        return self

The restart method is required to restart your collector have each request so the information doesn't get persisted bewteen requests. If this is not required for your collector then you can simply return self.

Collector Methods

The next part you'll need is a way to add data to your collector. This can be done in any method you want your developers to use. These are your external API's and how you want developers interacting with your collector. You can name these methods whatever you want and can be as complex as you need them to be.

class YourCollector:
    def __init__(self, name="Your Collector", description="Description"):
        self.messages = []
        self.name = name
        self.description = description

    def restart(self):
        self.messages = []
        return self

    def add(self, key, value):
        self.messages.append({key: value})

This method could be as simple or as complex as you need. Some of Masonite Debugbar's collectors use special classes to keep all the information.

Collector Rendering

Next you need a collect and an html method to finalize the collector. First the collect method should just return a dictionary like this:

from jinja2 import Template


class YourCollector:
    def __init__(self, name="Your Collector", description="Description"):
        self.messages = []
        self.name = name
        self.description = description

    def restart(self):
        self.messages = []
        return self

    def add(self, key, value):
        self.messages.append({key: value})
    
    def collect(self):
        collection = []
        for message in self.messages:
            for key, value in message.items():
                collection.append(
                    {
                        "key": key,
                        "value": value
                    }
                )
        # # render data to html
        template = Template(self.html())
        return {
            "description": self.description,
            "count": len(collection),
            "data": collection,
            "html": template.render({"data": collection}),
        }
    
    def html(self):
        return """
        <div>
            <div class="flex justify-between p-4">
                <p>{{ object.key }}: {{ object.value }}</p>
            </div>
        </div>
        """

data here in the html method loop is the collection we built. You can use this to style your tab and content.

In the collect method return dictionary, the description is used to show a quick description at the top of the tabs content. The count here is the number badge shown in the actual tab itself.

Registering the collector

Lastly we just need to register the collector to the debugbar for it to show up. You can do this in your own provider. If you are building this in your own application you can make your own provider. If you are building a collector as part of a package you can have your developers install it in their projects.

class YourProvider(Provider):
    def __init__(self, application):
        self.application = application

    def register(self):
        debugger = self.application.make('debugger')
        debugger.add_collector(YourCollector())

    def boot(self):
        pass

And then finally register the provider with your provider config and your collector will now show up in the debug toolbar with the rest of the collectors.

Masonite 1.3

Introduction

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

Fixed infinite redirection

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

Fixed browser content length

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

Changed how request input data is retrieved

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

Added Upload drivers

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

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

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

Added several helper functions

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

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

Added a way to have global template variables

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

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

Middleware is now resolved by the container

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

Added new domain method to the Get and Post classes

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

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

Added new module method to Get and Post routes

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

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

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

Added Queues and Jobs

Mocking

When it comes to unit testing, you always want to test a unit of your piece of code. Your code might depends on third party services such as an API and you don't want to call it during your local tests or in your CI environment. That's when you should use mocking to mock the external parts or the part you don't want to test.

Masonite comes with some mocking abilities for some of the features relying on third party services. For other parts or you own code you can use Python mocking abilities provided by unittest.mock standard module.

Masonite Features Mocks

Masonite tests case have two helpers method fake() and restore().

You can mock a Masonite feature by doing self.fake(feature) and then restore it to the real feature behaviour by calling self.restore(feature). When a feature is mocked the real behaviour won't be called, instead a quick and simple implementation is ran, often offering the ability to inspect and test what happens.

Available features that can be mocked (for now) are:

Mocking Mail

When mocking emails it will prevent emails from being really sent. Typically, sending mail is unrelated to the code you are actually testing. Most likely, it is sufficient to simply assert that Masonite was instructed to send a given mailable.

Here is an example of how to mock emails sending in your tests:

def setUp(self):
    super().setUp()
    self.fake("mail")

def tearDown(self):
    super().tearDown()
    self.restore("mail")

def test_mock_mail(self):
    welcome_email = self.application.make("mail").mailable(Welcome()).send()
    (
        welcome_email.seeEmailContains("Hello from Masonite!")
        .seeEmailFrom("joe@masoniteproject.com")
        .seeEmailCountEquals(1)
    )

Available assertions are:

  • seeEmailWasSent()

  • seeEmailWasNotSent()

  • seeEmailCountEquals(count)

  • seeEmailTo(string)

  • seeEmailFrom(string)

  • seeEmailReplyTo(string)

  • seeEmailBcc(string)

  • seeEmailCc(string)

  • seeEmailSubjectEquals(string)

  • seeEmailSubjectContains(string)

  • seeEmailSubjectDoesNotContain(string)

  • seeEmailContains(string)

  • seeEmailDoesNotContain(string)

  • seeEmailPriority(string)

Mocking Notification

When mocking notifications it will prevent notifications from being really sent. Typically, sending notification is unrelated to the code you are actually testing. Most likely, it is sufficient to simply assert that Masonite was instructed to send a given notification.

Here is an example of how to mock notifications sending in your tests:

def setUp(self):
    super().setUp()
    self.fake("notification")

def tearDown(self):
    super().tearDown()
    self.restore("notification")

def test_mock_notification(self):
    notification = self.application.make("notification")
    notification.assertNothingSent()
    notification.route("mail", "test@mail.com").send(WelcomeNotification())
    notification.route("mail", "test@mail.com").send(WelcomeNotification())
    notification.assertCount(2)
def test_mock_notification(self):
    self.application.make("notification").route("mail", "test@mail.com").route(
        "slack", "#general"
    ).send(OrderNotification(10))
    self.application.make("notification").assertLast(
        lambda user, notif: (
            notif.assertSentVia("mail")
            .assertEqual(notif.order_id, 10)
            .assertEqual(
                notif.to_slack(user).get_options().get("text"),
                "Order 10 has been shipped !",
            )
        )
    )

Available assertions are:

  • assertNothingSent()

  • assertCount(count)

  • assertSentTo(notifiable, notification_class, callable_assert=None, count=None)

  • assertLast(callable_assert)

  • assertNotSentTo(notifiable, notification_class)

On Notifications instances:

  • assertSentVia(*drivers)

  • assertEqual(value, reference)

  • assertNotEqual(value, reference)

  • assertIn(value, container)

Available helpers are:

  • resetCount()

  • last()

Basic Python mocks

Here is basic example

from unittest.mock import patch

with patch("some.module") as SomeClass:
    SomeClass.return_value.my_method.return_value = 0
    self.assertEqual(SomeClass().my_method(), 0)
import responses

@responses.activate
def test_mock_third_party_api(self):
    responses.add(responses.POST, "api.github.com", body=b"Ok")
    # do something in your code
    self.assertTrue(responses.assert_call_count("api.github.com", 1))

Validation

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
from masonite.request import Request
from masonite.response import Response

def show(self, request: Request, response: Response, validate: Validator):
    """
    Incoming Input: {
        'user': 'username123',
        'email': 'user@example.com',
        'terms': 'on'
    }
    """
    errors = request.validate(
        validate.required(['user', 'email']),
        validate.accepted('terms')
    )

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

This validation 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

Displaying Errors in Views

You can simply display validation errors in views like this:

@if session().has('errors'):
<div class="bg-yellow-400">
  <div class="bg-yellow-200 text-yellow-800 px-4 py-2">
    <ul>
      @for key, error_list in session().get('errors').items():
        @for error in error_list
          <li>{{ error }}</li>
        @endfor
      @endfor
    </ul>
  </div>
</div>
@endif
@if errors.any():
<div class="bg-yellow-400">
  <div class="bg-yellow-200 text-yellow-800 px-4 py-2">
    <ul>
      @for key, message in errors.all().items()
        <li>{{ message }}</li>
      @endfor
    </ul>
  </div>
</div>
@endif

<form method="post" action="/contact">
  {{ csrf_field }}
  <div>
    <label for="name">Name</label>
    <input type="text" name="name" placeholder="Name">
    @if errors.has('name')
    <span>{{ errors.get('name')[0] }}</span>
    @endif
  </div>
  <div>
    <label for="email">Email</label>
    <input type="email" name="email" placeholder="Email">
    @if errors.has('email')
    <span>{{ errors.get('email')[0] }}</span>
    @endif
  </div>
  <div>
    <label for="message">Message</label>
    <textarea name="message" placeholder="Message"></textarea>
    @if errors.has('message')
    <span>{{ errors.get('message')[0] }}</span>
    @endif
  </div>
  <button type="submit">Send</button>
</form>

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:

$ python craft rule equals_masonite

There is no particular reason that rules are lowercase class names. The main reason is that it 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:

$ python 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
    """

    def __init__(self, application):
        self.application = application

    def register(self, validator: Validator):
        """Boots services required by the container
        """

        self.application.make('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.

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:

$ python 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 masonite.request import Request
from masonite.response import Response
from app.rules.LoginForm import AcceptedTerms

def show(self, request: Request, response: Response):
    """
    Incoming Input: {
        'user': 'username123',
        'email': 'user@example.com',
        'terms': 'on'
    }
    """
    errors = request.validate(AcceptedTerms)

    if errors:
        request.session.flash('errors', errors)
        return response.back()

You can also use this in addition to other rules:

from app.rules.LoginForm import AcceptedTerms
from masonite.validations import email
from masonite.request import Request
from masonite.response import Response

def show(self, request: Request, response: Response):
    """
    Incoming Input: {
        'user': 'username123',
        'email': 'user@example.com',
        'terms': 'on'
    }
    """
    errors = request.validate(
        AcceptedTerms,
        email('email')
    )

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

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')
    ) #== <masonite.validation.MessageBag>

Getting All Errors:

You can easily get all errors using the all() method:

errors.all()
"""
{
  'email': ['Your email is required'],
  'name': ['Your name is required']
}
"""

Checking for any errors

errors.any() #== True

Checking if the bag is Empty

This is just the opposite of the any() method.

errors.empty() #== False

Checking For a Specific Error

errors.has('email') #== True

Getting the first Key:

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.count() #== 2

Converting to JSON

errors.json()
"""
'{"email": ["Your email is required"],"name": ["Your name is required"]}'
"""

Get the Amount of Messages:

errors.amount('email') #== 1

Get the Messages:

errors.get('email')
"""
['Your email is required']
"""

Get the Errors

errors.errors()
"""
['email', 'name']
"""

Get all the Messages:

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.merge({'key': 'value'})

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

Masonite 4.0

Masonite 4

The primary goal of Masonite was to restructure the Masonite internals so that I can make sure Masonite is around for the next 5-10 years. Masonite started as a learning project and went through version 1-4 in 4 years. In the very beginning, there really was no clear vision for the direction of the project so the foundation was not well thought out. It wasn't until version 2 that there was a clear vision and major structural changes. Version 1 looked incredibly different than version 3 yet they were built on the same foundation. You can see the issue .. There would be no way we would be able to get to version 6+ on the same foundation as version 1. Masonite needed a complete foundational rewrite. Meet Version 4.

Masonite originally started as a learning project in December of 2017. Over the years it has been a giant learning experience and Masonite has turned into something I never could have imagined. Because of the success that Masonite had, I found it was important to take a step back and make sure that the foundation of Masonite was built in a way that allowed Masonite to be as successful as it can be. Masonite 4 was written from complete scratch with everything I've learned over 4 years of maintaining the framework.

We will attempt here to go through everything new in Masonite 4. This documentation section will be a mix of both what is new and why it is new. Most of Masonite 4 changes are actually internal. Much of the interface and features of Masonite are identical to previous versions.

Foundation

The foundation of Masonite has changed a lot. The IOC container, service providers and features are all the same but how they are registered and how features work together has changed.

As many of you are familiar, when you maintain any application for several years and you go through requirement changes and scope creap it leads to technical debt.

With Masonite, there were times where I would come into contact with a very large company that wanted to use Masonite that maybe didn't have a feature they needed and I would have to build the feature over the weekend or even during the night. I did this to try to capture the company to use Masonite. Many of those companies did go along with using Masonite but creating features this rapidly tends to lead to poor feature structure. Rapid development of features naturally tends to lead to high coupling of code. High coupling of code tends to lead to more difficult maintanance.

One of the goals of the Masonite 4 release is to have very very low coupled code. So one of the things I did with Masonite 4 was to make sure each feature's foundation was identical. So each feature has a main manager class which acts as the main interface for each feature and each feature is built around drivers. This makes expanding features simple. New service? New driver.

Internal Project Structure

One of the downsides to Masonite development was that in the beginning, I had no clue what I was doing. Masonite 1 and Masonite 2 were completely different frameworks but they were built on the same foundation. See the issue?

One of the problems with this is that I thought it would be a great idea to simply make a project inside the Masonite core's code so I can more easily test certain features. The problem with this is that you can't use one of the PIP's most powerful features. Development mode. This is because the Masonite config directory would override the import inheritance. So the alternative with Masonite was to have to uninstall and install it everytime we made a change. This made developing with Masonite a lot longer and harder.

The tricky part is the only way to solve this issue is making everything in the project configurable. This is where the new Kernel file comes in. So previously we had controllers inside app/http/controllers, middleware inside app/middleware, etc.

Now with the Kernel file we register all of our paths in this file. We can then use those registrations to do what we need to do.

Project Structure

The project structure slightly changed. Masonite 4 gets rid of the app/http directory as there was no real benefit to the directory. The http directory was removed and we now just have app/controllers and app/middleware.

The resources/templates directory has been removed. This has been replaced with a templates directory.

Features

Surpringly, the features in Masonite 4 look almost identical to Masonite 3. There are some new or improved features such as:

  • A new Notifications feature

  • Improved broadcasting feature to include a new features like private and presence channels as well as easy authentication support.

  • More powerful email support based around Mailable classes.

  • Several packages like Validation and task scheduling have been moved into the core repository from being separate packages.

Facades

Here would be the same code, one using auto resolving and one using facades:

from masonite.response import Response

def show(self, response: Response):
    return response.redirect('/')

and using a facade:

from masonite.facades import Response

def show(self):
    return Response.redirect('/')

You can see its a bit cleaner and also allows us to use these facades internally to reference other implementations without having code be too rigid.

Service Providers

Service providers have changed slightly. In Masonite 3, providers were:

  • Register each provider to the container

  • Then looped through to call the register method on each of them

  • If the WSGI attribute was false it would call the boot method on those providers

  • The server would then boot up

  • On a request the service providers where WSGI was true have their boot method ran in order

The new service provider flow looks like this:

  • Register each provider to the container

  • Provider register methods are called as each one registers to the container

  • The server will then boot up

  • On each request, all the boot methods are ran in order

The end goals are the same but the order the flow happens has been modified. This is largely an internal change.

Masonite 3.0

Masonite 3 is a big change from Masonite 2. The most obvious change is the fact that we have dropped support for Orator and have created a new package called Masonite ORM that is intended to be a drop in replacement of Orator.

Hopefully many of you may not even tell we are no longer using Orator.

Python Support

Since Python 3.9 came out we are dropping support for Python 3.5. Masonite adds some f string formats so Masonite will break on Python 3.5. Throughout Masonite 3.0 we will be upgrading the codebase to use features available to us that we were unable to do because of Python 3.5 support.

Masonite 3 supports all Python versions 3.6 and above.

Headers

In Masonite 2 the headers were largely controlled oddly. Internally they were just saved as dictionaries and then attached to the response later. Also strangely, the response headers were attached to the request class and not the response class, even though they really had nothing to do with the response class. This also presented an issue because a request header is one sent by a client and a response header is one set by your app but they were both being saved in the same place so it was impossible to be able to tell who set the header.

Now in Masonite 3 we refactored the headers to use a new HeaderBag class which is used to maintain Header classes. We put the exact same class on the response class as well so they can be managed separately.

Now if you want to set headers on the request or response and you can know which header is for which.

Also logically it makes more sense to set response headers on the response class.

This internal rewrite also negates the need to prefix headers using HTTP_.

Cookies

Cookies were suffering the same fate as headers so we changed cookies to use the same class structure as the HeaderBag and there is now a CookieJar class that is used to maintain Cookie classes.

Request Singleton

The request class has also been reworked. Before, the request class was a single class that was created when the framework booted up. This presented some challenges because the class needed to be maintained between requests. Meaning certain inputs and headers set on the request class needed to be reset once the request was over and set back to a state before the request. This obviously created some weird caching bugs between requests and instead of fixing the issues we actually just created hacky work arounds like resetting inputs.

Now the request class is only created once a request is received. Because of this there are now certain places that the request class is no longer accessible. For example, you can no longer fetch the request class in the register method of service providers where the wsgi attribute is False. You mat also not be able to fetch the request class in some of your classes __init__ method depending on when and where the class is being initialized.

If you are upgrading to Masonite 3 and run across an error like the request class is not found in the container then you will need to fetch the request class later down the code.

Class Responsibility

The request class was one of the first classes that was created for Masonite. There hasen't been much that changed on the class so the class slowly got larger and larger and took on more responsibility.

One of the things that the class was used for, like the headers, was the response status code. It did not make sense to set the response status code on the request class so now the response status is whatever status is set on the response class. Requests don't have status codes so we removed the status code on the request class all together.

Start Method

Every Masonite project has had a bootstrap/start.py file. This is a low level method in the masonite app that really handles the entire application and responsible for the final response. It is the entry point everytime a request is received. Many of you were probably not even aware it was there or were confused on what it did.

Since the method was low level and should never be changed we moved this method into the Masonite codebase and instead of an import from the local bootstrap.start import app it is now an import from the masonite codebase from masonite.wsgi import response_handler

It is largely the same exact method but is now maintained by the masonite framework.

Queue improvements

Masonite queueing had a simple table with not a lot of options on it. Because of this we had to make some decisions to prevent the duplication of some jobs. Like sleeping and only fetching 1 job per poll. This made the queue slower than what it should have been. So now we added more columns on the queue jobs table that will allow us to reserve some jobs to prevent duplication in other queue workers. If you still experience duplication of some jobs running via supervisor you may need to specify the option that offsets when supervisor commands are started.

Dropped route helpers

In the past we had route helpers which were functions that wrapped some route logic. These were located in the masonite.helpers.routes namespace. These were deprecated in Masonite 2.3 and now removed in Masonite 3.0.

Dropping Orator

Obviously the biggest change is dropping Orator and picking up Masonite ORM. This new ORM is designed to be a drop in replacement for Orator. I have upgraded several projects to use the new ORM and I had to change very minimal code to get it to work.

Theres a few resons we decided to drop support of Orator but the main one was that Sdispater (the creator of Orator) has his time occupied by other packages like Pendulum (which Masonite ORM still uses) as well as Poetry. These are great packages and more popular than Orator. Sdispater does not know if he will pick Orator back up but the issues and bugs have been piling up and the codebase was not up to my standard of being maintained. Myself and a few maintainers have taken the time to create a new ORM project called Masonite ORM.

Another reason is that we now have completely creative control over the ORM side of Masonite. We don't have to go through someone who has complete control. Releases can now be scheduled whenever we want and we can add whatever features we want. This is a huge deal for Masonite.

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:

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:

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:

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:

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:

and used it inside your views like this:

This is now completely changed to use a better and more sleeker validation. The above validation can now be written like this:

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:

You'll need to move this into the parameter list so it can be resolved:

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:

It previously would have resolved and gave back the class:

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:

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:

Should now look like this:

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:

Should now look like this:

Method naming

Previously all methods were snake_case but to continue with the unittest convention, all testing methods are camelCase.

A method like:

Now becomes:

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:

Has now been replaced with the method name of the route. So to get a GET route you would do:

or a POST route:

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:

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:

should become:

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:

Should now be:

Masonite 2.3 to 3.0

Masonite 2.3 to 3.0

This guide is designed to give you as much information as possible to upgrade your application from Masonite 2.3 to Masonite 3.0.

We will go through each new breaking change in Masonite and code examples on how to upgrade the code to use Masonite 3. If there is any code breaking during the upgrade, please go to our Slack channel and let us know so we can add more information to this documentation.

This document will be broken down into 2 parts, upgrading from Orator to Masonite and upgrading from Masonite 2.3 to 3.0.

Note that there are some large changes from 2.3 to 3.0. Depending on the size of your project it might make more sense to rebuild your project and port your code to a new Masonite 3 app. This guide is for changing your existing projects.

Upgrading Masonite

Requirement Changes

We need to uninstall some packages and install some others

First uninstall masonite and orator and install masonite 3:

That should be all you need to get the requirements up to date.

At this point Masonite will not work because our codebase is not updated to work with Masonite 3

Now we can start changing the application to get our app to run again.

Request init Offset

The request class is now not even initialized until a request is sent. Previously the request class acted as a singleton but it is now a new class that is initialized on every request. Because of this change, any place you previously had been fetching the request class before the request is sent will no longer work. This could be in several places but is likely most common to be in any class __init__ methods that are initialized before a request was sent, like in a service provider where the wsgi attribute is False.

Headers On Response

The response headers are now set on the response class. This makes much more sense now. Previously we set them on the request class which really didn't make sense.

Any place in your code where you want a header to be on the response. The code changes may look something like this:

Should now be written as:

HTTP Header Prefix.

Some headers used to have to be prefixed as HTTP_ to be used correctly. This is no longer required. Code where you have done this:

Can be changed to:

Status Codes

Status codes used to be set on the request class but is now set on the response class. The method and uses are the same:

Should be changed to:

New Providers

Because of the request singleton change we had to offset some other instantiation so we added 2 new providers you need to add. The placement of the RequestHelpersProvider and CsrfProvider needs to be between the AppProvider and the RouteProvider. The change is in the config/providers.py file.

We also need to add the orm provider from Masonite ORM. It doesn't really matter where add this so we can add it at the bottom of the list somewhere:

WSGI.py File Change

There used to be a bootstrap/start.py file in Masonite apps. This file contained a method was used to handle the request and response lifecycle. There was no reason to keep the method inside the application since it was such a low level method and was crucial for the framework to work.

So we need to import the method from the Masonite codebase instead of the start.py file. We also renamed the method response_handler instead of app

Craft File

You may not have to make this change but in some previous versions of Masonite, the craft file that came with new projects was wrong. The file is called craft and is in the root of your project directory. If the changes with the + sign already exist then your project is correct. If you have the code in strikethrough then make this diff change:

Queue Table

If you use Masonite queues, there are 3 new columns on the queue_jobs table. Please make a migration and add these 3 columns:

First make the migration

Then add the migration:

Then run the migration

Dropped route helpers

If you used any helper routes inside your web.py file, these have been removed. You will now need to use only route classes:

If you have code inside your web.py

You will need to remove this import and use the class based routes:

This applied for the helpers: get, post, put, patch, delete and group helpers.

Flashed Messages

In previous versions of Masonite, Masonite would set flashed messages for a 2 second expiration time. This caused numerous different issues like what happens when a page redirection took longer than 2 seconds or what happens when the client times are not synced correctly.

Now we have took a "get and delete" approach. So now the flash data is deleted when it is retrieved. This means that flash data can stay in session until it is fetched.

To do this we have a new method for the "get and delete" of flash data.

If you are using the bag() helper in your templates then this:

If you are using the session() helper than you will need to take a similiar approach:

Upgrading Orator

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

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:

to

Go ahead and install masonite now:

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:

to this:

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:

to this:

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.

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:

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:

and then make it somewhere else:

This will throw a StrictContainerError error. Now you have to import it so will have to do something like this using the example above:

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:

Just completely remove that line. Its no longer needed.

Also around line 19 you will see this line:

You can completely remove that as well.

Lastly, around line 31 you can change this line:

to this:

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:

then query strings will now parse to:

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.

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:

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:

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:

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

Finished

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

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:

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:

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

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.

Finally ensure is enabled else the debugbar will not be displayed !

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.

For mocking any piece of code in Python you can use the standard unittest.mock module. You can find more information in .

For mocking external HTTP requests you can use the responses module. You can find more information in .

You can see a .

If you want to handle errors in views specifically you will need to add the ShareErrorsInSessionMiddleware middleware into your route middlewares. errors will be injected to views as a instance allowing to handle errors easily:

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.

Masonite 4 brings a new concept called facades. Facades are a simple proxy class that call a deeper implementation through Masonite's .

Below is a list of high level changes of things that are new or changed in Masonite 3 from Masonite 2. If you want to see how to upgrade from Masonite 2 to 3 then refer to the

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

Before you go through this document it is highly recommended that you read .

For upgrading from Orator to Masonite please read the

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.

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

Contributing Guide
How To Contribute
Masonite 1.3 to 1.4
About Drivers
Upgrade Guide for Masonite 1.6 to 2.0
Controllers
The Craft Command Introduction
The Craft Command Introduction
The Craft Command
Autoloading
Requests
Requests
Requests
Requests
Upgrade Guide 1.6 to 2.0
Task Scheduling
Database Seeding
Static Files
Encryption
Uploading
Status Codes
Helper Functions
Routing
Queues and Jobs
Mail
Notification
unittest documentation
responses documentation
Pendulum
RFC 4122
Service Container
Upgrade Guide
debug mode
list of available rules here
MessageBag
matches
image
video
exists
pip install masonite==2.2.0
from masonite.helpers.routes import get, post

ROUTES = [
    get('/url/home').name('home')
]
from masonite.routes import Get as get, Post as post

ROUTES = [
    get('/url/home').name('home')
]
from masonite.validation.providers import ValidationProvider

PROVIDERS = [
    ...
    ValidationProvider,
    ...
]
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]
        })
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')
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)
from masonite.auth import Auth
...
def show(self, request: Request):
    Auth(request).user()
from masonite.auth import Auth
...
def show(self, auth: Auth):
    auth.user()
from some.place import SomeClass

class SomeProvider:

    def register(self): 
        self.app.bind('SomeClass', SomeClass)
from some.place import SomeClass

def show(self, request: Request, some: SomeClass):
    some #== <class some.place.SomeClass>
    setup_class = some(request)
from some.place import SomeClass

def show(self, request: Request, some: SomeClass):
    some #== <some.place.SomeClass x9279182>
from masonite.testing import UnitTest

class TestSomeUnit(UnitTest):
    ...
from masonite.testing import TestCase

class TestSomeUnit(TestCase):
    ...
from masonite.testing import UnitTest
from routes.web import ROUTES

class TestSomeUnit(UnitTest):

    def setup_method(self):
        super().setup_method()

        self.routes(ROUTES)
from masonite.testing import TestCase

class TestSomeUnit(TestCase):

    def setUp(self):
        super().setUp()
self.route('/some/protect/route').is_named()
self.get('/some/protect/route').isNamed()
def test_route_has_the_correct_name(self):
    assert self.route('/testing')
def test_route_has_the_correct_name(self):
    assert self.get('/testing')
def test_route_has_the_correct_name(self):
    assert self.post('/testing')
    def setup_method(self):
        super().setup_method()

        self.routes([
            Get().route('/testing', 'SomeController@show').name('testing.route').middleware('auth', 'owner')
        ])
self.json('/test/json/response/1', {'id': 1}, method="POST")
self.json('POST', '/test/json/response/1', {'id': 1})
class MockUser:
    is_admin = 1
​
def test_owner_user_can_view(self):
    assert self.route('/some/protect/route').user(MockUser).can_view()
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')
    )
$ pip uninstall masonite
$ pip uninstall orator
$ pip install masonite==3.0
from masonite.request import Request

class CustomMiddleware(Middleware):
  def __init__(self, request: Request)
      self.request = request
  # ...
  def after(self):
  		self.request.header('IS_INERTIA', 'true')
from masonite.response import Response

class CustomMiddleware(Middleware):
  def __init__(self, response: Response)
      self.response = response
  # ...
  def after(self):
  		self.response.header('IS_INERTIA', 'true')
request.header('HTTP_IS_INERTIA', 'true')
response.header('IS_INERTIA', 'true')
request.status(200)
response.status(200)
from masonite.providers import RequestHelperProvider

# ...
PROVIDERS = [
  # Framework Providers
  AppProvider, # <-- AppProvider Here
  RequestHelpersProvider, # <-- In the middle
  CsrfProvider, # <-- In the middle
  SessionProvider,
  RouteProvider, # <-- RouteProvider here
from masoniteorm.providers import ORMProvider

PROVIDERS = [
  # ..
  # Third Party Providers
  ORMProvider,
  # ..
]
- from bootstrap.start import app
+ from masonite.wsgi import response_handler
from src.masonite.helpers import config

# ..

container = App()

- container.bind('WSGI', app)
+ container.bind('WSGI', response_handler)

container.bind('Container', container)
- from masonite import info
+ from masonite import __version__

from wsgi import container

- application = Application('Masonite Version:', info.VERSION)
+ application = Application('Masonite Version:', __version__)
$ python craft migration add_fields_to_queue_jobs_table --table queue_jobs
  def up(self):
      with self.schema.create("queue_jobs") as table:
          table.string("queue")
          table.timestamp("available_at").nullable()
          table.timestamp("reserved_at").nullable()

  def down(self):
      with self.schema.create("queue_jobs") as table:
          table.drop_column("queue", "available_at", "reserved_at")
$ python craft migrate
from masonite.helpers.routes import get
from masonite.routes import Get
@if bag().any()
-  @for error in bag().messages()
+  @for error in bag().get_errors()
    <div class="alert alert-danger" role="alert">
        {{ error }}
    </div>
  @endfor
@endif
@if session().has('errors')
  @for key, error_list in session().get_flashed('errors').items()
    <div class="alert alert-danger" role="alert">
        {{ error }}
    </div>
  @endfor
@endif
$ pip uninstall masonite-cli
masonite>=2.2,<2.3
masonite>=2.3,<2.4
pip install "masonite>=2.3,<2.4"
return iter([bytes(container.make('Response'), 'utf-8')])
return iter([container.make('Response')])
AUTH={
    'driver': env('AUTH_DRIVER', 'cookie'),
    'model': User,
}
AUTH = {
    'defaults': {
        'guard': 'web'
    },
    'guards': {
        'web': {
            'driver': 'cookie',
            'model': User,
            'drivers': { # 'cookie', 'jwt'
                'jwt': {
                    'reauthentication': True,
                    'lifetime': '5 minutes'
                }
            }
        },
    }
}
from masonite.providers import AuthenticationProvider

PROVIDERS = [
    # Framework Providers
    # ...
    AuthenticationProvider,

    # Third Party Providers
    #...
]
SASSFILES = {
    'importFrom': [
        'storage/static'
    ],
    'includePaths': [
        'storage/static/sass'
    ],
    'compileTo': 'storage/compiled'
}
from config import auth

app.bind('AuthConfig', auth)
class ClassA:

    def __init__(self, container: Container):
        self.auth = container.make('AuthConfig')
from config import auth

class ClassA:

    def __init__(self, container: Container):
        self.auth = auth
container.bind('Application', application)
container.bind('ProvidersConfig', providers)
for provider in container.make('ProvidersConfig').PROVIDERS:
for provider in providers.PROVIDERS:
<input name="options[name]" value="Joe">
<input name="options[user]" value="bob">
{
    "email": "user@email.com",
    "options": {
        "name": "Joe",
        "user": "bob"
    }
}
waitress==1.1.0
masonite>=1.5,<=1.5.99
$ pip install --upgrade masonite-cli
''' 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'
PROVIDERS = [
    # Framework Providers
    'masonite.providers.AppProvider.AppProvider',

    # New Provider
    'masonite.providers.SessionProvider.SessionProvider',
    'masonite.providers.RouteProvider.RouteProvider',
    ....
]
...
masonite>=1.6,<=1.6.99
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)
Whats New in 2.2
Validation documentation
GitHub Repo
Whats New in Masonite 3
Orator to Masonite ORM guide
Masonite Entry
What's New in Masonite 1.6
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
Duplicate Class Names
string binding
create a listener

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!

Masonite 4.x to 5.x

Masonite 5 brings new changes ... TODO

Python Version

Masonite 5 adds support for Python 3.11.

Install Masonite 5

First step of the upgrade guide is to uninstall Masonite 4 and install Masonite 5:

pip uninstall masonite
pip install masonite==5.0.0

Import Path Changes

The import path has changed for the following:

- from masonite.essentials.helpers import hashid
+ from masonite.helpers import hashid

- from masonite.essentials.middleware import HashIDMiddleware
+ from masonite.hashid.middleware import HashIDMiddleware

Config Changes

Cache

Rename memcache driver to memcached to the config/cache.py file.

STORES = {
    "memcached": {
        "driver": "memcached",
        "host": "127.0.0.1",
        "port": "11211",
        "password": "",
        "name": "masonite4",
    },
}

Providers

Two new providers have been created, that you will need to add them in config/providers.pyfile.

from masonite.providers import PresetsProvider, SecurityProvider, LoggingProvider

#...

PROVIDERS = [
    FrameworkProvider,
    HelpersProvider,
    SecurityProvider, # insert here
    LoggingProvider, # insert here
    RouteProvider,
    #..
    ValidationProvider,
    PresetsProvider, # insert here
    AuthorizationProvider,
    ORMProvider,
    AppProvider,
]

Cache

Memcached driver name has been fixed. It was called Memcache as its real name is Memcached. When using this driver you will now need to access it like this:

self.application.make("cache").store("memcached").get("key")

or via the facade:

Cache.store("memcached").get("key")

Helpers

All Masonite helpers can now be imported from helpers module to ease development experience.

from masonite.helpers import config, url, env, app, optional, collect, compact

Masonite 3.0 to 4.0

Masonite 4 is the biggest change in a Masonite release we have ever had. Smaller applications may benefit from creating a new app and then copying and pasting controllers, routes and views to the new installation.

For medium to large scale applications, you will need to go through the codebase and upgrade everything to use the new structures we have available in Masonite 4.

It is highly recommended that you start at Masonite 3 before upgrading to Masonite 4. If you are running any version less than Masonite 3 then please use the upgrade guide to upgrade to each version first.

Python Version

Masonite 4 drops support for Python 3.6. You will have to upgrade to Python 3.7+ in order to run Masonite

Install Masonite 4

First step of the upgrade guide is to uninstall Masonite 3 and install Masonite 4:

$ pip uninstall masonite
$ pip install masonite==4.0.0

Craft File

The next step of the upgrade guide is to replace your craft file. This is a basic file in the root of your project that will be used whenever you run python craft. We will use this to keep testing our server:

Kernel File

Masonite 4 has a new Kernel.py file which is used to customize your application and contains necessary bootstrapping to start your application.

You will also need to put this into the base of your project.

Now go through this file and customize any of the locations. Masonite 4 uses a different file structure than Masonite 3. For example, Masonite 3 put all views in resources/templates while Masonite 4 has them just in templates.

Because of this, you can either change where the files are located by moving the views to a new templates directory or you can change the path they are registered in your Kernel:

Go to your register_configurations method in your Kernel and inside your register_templates method you can change it to

def register_templates(self):
  self.application.bind("views.location", "resources/templates")

Go through the rest of the methods and make sure the paths are set correctly.

Providers

Add the following providers to your project:

from masonite.providers import FrameworkProvider, HelpersProvider, ExceptionProvider, EventProvider, HashServiceProvider

PROVIDERS = [
	FrameworkProvider,
  ExceptionProvider,
  EventProvider,
  HashServiceProvider,
  

Routes

Routes have changed slightly.

First, routes are now all under the Route class. The route classes have moved into methods:

from masonite.routes import Route
ROUTES = [
-     Get('/', 'Controller@method').name('home')
-     Post('/login', 'Controller@method').name('home')
+     Route.get('/', 'Controller@method').name('home')
+     Route.post('/', 'Controller@method').name('home')
]

WSGI File

The WSGI file has also changed.

Import Path Changes

The import path has changed for the following:

- from masonite.helpers import random_string
+ from masonite.utils.str import random_string

- from masonite import Mail
+ from masonite.mail import Mail

- from masonite.view import View
+ from masonite.views import View

- from masonite.auth import Auth
+ from masonite.authentication import Auth

- from masonite import env
+ from masonite.environment import env

- from masonite.helpers import password
+ from masonite.facades import Hash #== See Hashing documentation

- from masonite.middleware import CsrfMiddleware as Middleware
+ from masonite.middleware import VerifyCsrfToken as Middleware

- from masonite.helpers import config
+ from masonite.configuration import config

- from masonite.drivers import Mailable
+ from masonite.mail import Mailable

- from masonite.provider import ServiceProvider
+ from masonite.providers import Provider

Request and Response redirects

Previously when we had to do a redirection we would use the request class:

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

This has now been changed to the response class:

from masonite.response import Response

def store(self, response: Response):
	return response.redirect('/home')

Same applies to back redirection:

- return request.back()
+ return response.back()

Middleware

Middleware has been moved to this new Kernel file. Middleware now works a little different in M4. Middleware has changed in the following ways:

  1. middleware no longer needs an __init__ method.

  2. Middleware requires the request and response parameters inside the before and after methods.

  3. Middleware requires either the request or response to be returned

Middleware will change in the following example:

Old:

class AdminMiddleware:
		def __init__(self, request: Request):
        """Inject Any Dependencies From The Service Container.
        Arguments:
            Request {masonite.request.Request} -- The Masonite request object
        """
        self.request = request

    def before(self):
      if not optional(self.request.user()).admin == 1:
            self.request.redirect('/')
    
    def after(self):
      pass

To this:

class AdminMiddleware:

    def before(self, request, response):
      if not optional(request.user()).admin == 1:
            return response.redirect('/')
    
    def after(self, request, response):
      pass

User Model & Authentication

Masonite 4 uses the same Masonite ORM package as Masonite 3 but changes a lot of the authentication.

Inherit a new Authenticates class

from masonite.authentication import Authenticates
from masoniteorm.models import Model

class User(Model, Authenticates):
  # ..

Authentication has also changes slightly. Whenever you are logging in a user the following UI has changed:

- auth.login(request.input('email'), request.input('password'))
+ auth.attempt(request.input('email'), request.input('password'))

Controllers

Controllers have not changes much but in order for routes to pick up your string controllers, you must inherit from Masonite controller class:

from masonite.controllers import Controller

class DashboardController(Controller):
  # ..

The rest of your controller structure remains the same.

Routes

Config Changes

Application

Add the following to the config/application.py file.

HASHING = {
    "default": "bcrypt",
    "bcrypt": {"rounds": 10},
    "argon2": {"memory": 1024, "threads": 2, "time": 2},
}

Auth

Change the AUTH constant to the new GUARDS configuration:

GUARDS = {
    "default": "web",
    "web": {"model": User},
    "password_reset_table": "password_resets",
    "password_reset_expiration": 1440,  # in minutes. 24 hours. None if disabled
}

Add a new config/exceptions.py file:

HANDLERS = {"stack_overflow": True, "solutions": True}

Storage -> Filesystem

The config/storage.py file has been replaced with a config/filesystem.py file:

Session

Change the config/session.py to the following:

DRIVERS = {
    "default": "cookie",
    "cookie": {},
}

Middleware

After you upgrade all your middleware, you will need to move them from the config/middleware.py file to the top of your Kernel file:

from some.place import CustomMiddleware
class Kernel:

    # ...
    route_middleware = {"web": [
        # ...
        CustomMiddleware
    ],

Session

get_flashed method has changed to just get. Here is an example in your templates:

- {{ session().get_flashed('success') }}
+ session().get('success')

Static Helper

The static helper has changed to asset:

- {{ static('s3.uploads', 'invoice.pdf') }}
+ {{ asset('s3.uploads', 'invoice.pdf') }}

Finally after all the above changes attempt to run your server:

$ python craft serve

Routing

Masonite ships with a really powerful routing engine. Routing helps link a browser URL to its controller and controller action.

Creating a Route

Routes are created by importing the Route class and defining the HTTP verb you would like with the URL and the controller you would like to use. These routes need to be wrapped in a ROUTES list inside your routes file.

from masonite.routes import Route

ROUTES = [
	Route.get('/welcome', 'WelcomeController@show')
]

The first parameter is the URL you would like to be available in your application. In the above example, this will allow anybody to go to the /welcome URL.

Available Route Methods

You may choose to define any one of the available verbs:

Route.get('/welcome', 'WelcomeController@show')
Route.post('/welcome', 'WelcomeController@show')
Route.put('/welcome', 'WelcomeController@show')
Route.patch('/welcome', 'WelcomeController@show')
Route.delete('/welcome', 'WelcomeController@show')
Route.options('/welcome', 'WelcomeController@show')
Route.view('/url', 'view.name', {'key': 'value'})
Route.resource('/users', 'UsersController')
Route.api('/users', 'UsersApiController')

In addition to these route verbs you can use built in routes:

Route.redirect('/old', '/new', status=301)
Route.permanent_redirect('/old', '/new')

Controller Binding

There are multiple ways to bind a controller to a route.

String Binding

You can use a string binding defining the controller class and its method {ControllerClass}@{controller_method}:

Route.get('/welcome', 'WelcomeController@show')

Note that this is the prefered way as it will avoid circular dependencies as no import is required in your route file.

Class Binding

You can import your controllers in your route file and provide a class name or a method class:

from app.controllers import WelcomeController

Route.get('/welcome', WelcomeController)

Here as no method has been defined the __call__ method of the class will be bound to this route. It means that you should define this method in your controller:

class WelcomeController(Controller):

    def __call__(self, request:Request):
        return "Welcome"

For convenience, you can provide the method class instead:

from app.controllers import WelcomeController

Route.get('/welcome', WelcomeController.show)

Instance Binding

You can also bind the route to a controller instance:

from app.controllers import WelcomeController

controller = WelcomeController()

Route.get('/welcome', controller)

Here as no method has been defined the __call__ method of the class will be bound to this route. It means that you should define this method in your controller:

class WelcomeController(Controller):

    def __call__(self, request:Request):
        return "Welcome"

Route Options

You may define several available methods on your routes to modify their behavior during the request.

Middlewares

Route.get('/welcome', 'WelcomeController@show').middleware('web')
Route.get('/settings', 'WelcomeController@settings').middleware('auth', 'web')

This will attach the middleware key(s) to the route which will be picked up from your middleware configuration later in the request.

Route.get('/about', 'WelcomeController@about').exclude_middleware('auth', 'custom')

Name

You can specify a name for your route. This is used to easily compile route information in other parts of your application by using the route name which is much more static than a URL.

Route.get('/welcome', 'WelcomeController@show').name('welcome')

Parameters

You can specify the parameters in the URL that will later be able to be retrieved in other parts of your application. You can do this easily by specify the parameter name attached to a @ symbol:

Route.get('/dashboard/@user_id', 'WelcomeController@show')

Optional 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 @ symbol with a ?:

Route.get('/dashboard/?option', 'WelcomeController@show')

Domain

You can specify the subdomain you want this route to be matched to. If you only want this route to be matched on a "docs" subdomain (docs.example.com):

Route.get('/dashboard/@user_id', 'WelcomeController@show').domain('docs')

Route Compilers

Route compilers are a way to match on a certain route parameter by a specific type. For example, if you only watch to match where the @user_id is an integer. You can do this by appending a : character and compiler name to the parameter:

Route.get('/dashboard/@user_id:string', 'WelcomeController@show')

Available route compilers are:

  • integer

  • int (alias for integer)

  • string

  • signed

  • uuid

Creating Route Compilers

You can also create your own route compilers if you want to be able to support specific regex matches for routes.

All route compilers will need to be added to the top of your register_routes() method in your Kernel.py file.

    def register_routes(self):
        Route.set_controller_locations(self.application.make("controllers.location"))
        Route.compile("handle", r"([\@\w\-=]+)")

        #..

Note: The compile methods need to happen before the routes are loaded in this method so make sure it is at the top. You may also put it in any method that appears before the register_routes() method.

Route Groups

Route groups are a great way to group mulitple routes together that have similiar options like a prefix, or multiple routes with the same middleware.

A route uses the group() method that accepts a list of routes and keyword arguments for the options:

ROUTES = [
  Route.group([
    Route.get('/settings', 'DashboardController@settings').name('settings'),
    Route.get('/monitor', 'DashboardController@monitor').name('monitor'),
  ],
  prefix="/dashboard",
  middleware=['web', 'cors'],
  name="dashboard."),
  domain="docs"
]

The prefix and name options will prefix the options set in the routes inside the group. In the above example, the names of the routes would dashboard.settings with a URL of /dashboard/settings and dashboard.monitor and a URL of /dashboard/monitor.

Route Views

Route views are a quick way to return a view quickly without needing to build a controller just to return a view:

ROUTES = [
  Route.view("/url", "view.name", {"key": "value"})
]

You could optionally pass in the methods you want this to be able to support if you needed to:

ROUTES = [
  Route.view("/url", "view.name", {"key": "value"}, method=["get", "post"])
]

List Routes

Application routes can be listed with the routes:list Masonite command. Routes will be displayed in a table with relevant info such as route name, methods, controller and enabled middlewares for this route.

Routes can be filtered by methods:

python craft routes:list -M POST,PUT

Routes can be filtered by name:

python craft routes:list -N users

Events

Masonite ships with a "pub and sub" style events feature that allows you to subscirbe to various events and run listeners, or additional logic, when those events get emitted.

Creating an Event

The first step in events is creating an event to listen to.

Events are simple classes that you can create wherever you like:

$ python craft event UserAdded

This will create a simple class we can later emit.

You can also fire events without an Event class. The event will just be a specific key you can listen to.

Creating A Listener

The listener will run the logic when the event is emitted. You can create as many listeners as you like and register as many listeners to events as you need to.

To create a listener simply run the command:

$ python craft listener WelcomeEmail

This will create a class like this:

class WelcomeEmail:
    def handle(self, event):
        pass

Handle Method

The handle method will run when the listener runs. It will pass the event as the first parameter and any additional arguments that are emitted from the event as additional parameters.

Registering Events and Listeners

After your events and listeners are created you will need to register them to the event class.

class EventsProvider(Provider):
    def register(self):
        self.application.make('event').listen(UserAddedEvent, [WelcomeListener])

You can also listen to events without Event classes:

class EventsProvider(Provider):
    def register(self):
        self.application.make('event').listen("users.added", [WelcomeListener])

Using event strings allow to use wildcard event listening. For example if the application is emitting multiple events related to users such as users.added, users.updated and users.deleted you can listen to all of those events at once:

event.listen("users.*", [UsersListener])

Firing Events

To fire an event with an event class you can use the fire method from the Event class:

from app.events import UserAddedEvent
from masonite.events import Event

class RegisterController:
    def register(self, event: Event):
        # Register user
        event.fire(UserAddedEvent)

To fire a simple event without a class you will use the same method:

from app.events import UserAddedEvent
from masonite.events import Event

class RegisterController:
    def register(self, event: Event):
        # ...
        # Register user
        event.fire("users.added", user)

Building a Welcome Email Listener

As an example, to build a listener that sends an email:

First, create the listener:

$ python craft listener WelcomeEmail

Then we can build out the listener.

To send an email we will need to import the mailable class and send the email using the mail key from the container:

from app.mailables.WelcomeMailable import WelcomeMailable

class WelcomeEmail:
  def handle(self, event):
    from wsgi import application

    application.make("mail").send(
      WelcomeMailable().to('idmann509@gmail.com')
    )

You can then register the event inside the provider:

class EventsProvider(Provider):
    def register(self):
        self.application.make('event').listen(UserAddedEvent, [WelcomeListener])

When you emit the UserAdded event inside the controller, or somewhere else in the project, it will now send this email out.

You can register as many listeners to the events as you like by simply adding more listeners to the list.

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.

A new helper to access application container app has been introduced ! More information in documentation.

Go to and copy and paste it into your own craft file in the root of your project. If you are running Masonite 3 then you should already have this file.

Go to and paste it into your own Kernel.py file in the root of your project.

Go to and replace your own wsgi.py file

Go to and copy it into your project. Then move the STATICFILES from your storage config into this new filesystem config

The second parameter is the you want to bind this route to.

When using string binding, you must ensure that this controller class can be imported correctly and that the controller class is in a .

You can add one or multiple :

You can exclude one or multiple for a specific route:

You can do this via the AppProvider or a you will create yourself:

Service Providers
MasoniteFramework/masonite
Service Container
Service Container
changes in Masonite 2
this file
this file
this file
this file
Controller
Service Provider
Helpers
registered controller location
Routes Middlewares
Routes Middlewares

Queues and Jobs

Masonite ships with a powerful queue system. This feature is useful for running those tasks that take a while like sending emails, processing videos, creating invoices, updating records and anything else you don't need you users to wait for or jobs that need .

First, jobs are creating with the logic required to make the job run. The jobs are then "pushed" onto the queue where they will run later using "queue workers". You can specify as many queue workers as your server can run.

In addition to running jobs, some drivers allow you to monitor any jobs that fail. Job data will save into the database where they can be monitored and reran if needed.

Configuration

You can easily modify the behavior of your queue system by changing the queue configuration:

The available queue drivers are: async, database and amqp.

A full queue configuration will look like this:

DRIVERS = {
    "default": "async",
    "database": {
        "connection": "mysql",
        "table": "jobs",
        "failed_table": "failed_jobs",
        "attempts": 3,
        "poll": 5,
        "tz": "UTC"
    },
    "amqp": {
        "username": "guest",
        "password": "guest",
        "port": "5672",
        "vhost": "",
        "host": "localhost",
        "channel": "default",
        "queue": "masonite4",
    },
    "async": {
        "blocking": False,
        "callback": "handle",
        "mode": "threading",
        "workers": 1,
    },
}

Default Queue

The default key is used to specify which queue driver in the configuration to use by default. This needs to be the same value as one of the other keys in the configuration dictionary.

Database Driver

To use the database driver you should first create a jobs table:

$ python craft queue:table

This will create a migration file in your migrations directory.

If you want to save failed jobs you should also create a migration for the failed jobs table:

$ python craft queue:failed

You should then migrate your project:

$ python craft migrate

The database driver is used to process jobs and failed job via a database connection.

Option
Description

connection

Specifies the connection to use for finding the jobs and failed jobs table.

table

Specifies the name of the table to store jobs in

failed_jobs

Specifies the table to store failed_jobs in. Set to None to not save failed jobs.

attempts

Specifies the default number of times to attempt a job before considering it a failed job.

poll

Specifies the time in seconds to wait before calling the database to find new jobs

tz

The timezone that the database should save and find timestamps in

AMQP Driver

The AMQP driver is used for connection that use the AMQP protocol, such as RabbitMQ.

The available options include:

Option
Description

username

The username of your AMQP connection

password

The password of your AMQP connection

port

The port of your AMQP connection

vhost

The name of your virtual host. Can get this through your AMQP connection dashboard

host

The IP address or host name of your connection.

channel

The channel to push the queue jobs onto

queue

The default name of the queue to push the jobs onto.

Async Driver

The async driver will simply run the jobs in memory using processes or threading. This is the simplest driver as it does not need any special software or setup.

The available options include:

Option
Description

blocking

A boolean value on whether jobs should run synchronously. Useful for debugging purposes.

callback

The name of the method on the job that should run.

mode

Whether the queue should spawn processes or threads. Options are threading or multiprocess

workers

The numbers of processes or threads that should spawn to run the jobs.

Creating Jobs

In order to process things on the queue, you will need to create a job. This job will be treated as an entity that can be serialized and ran later.

For a shortcut you can run the job command to create the job:

$ python craft job CreateInvoice

You will now have a job class you can build out the logic for:

from masonite.queues import Queueable

class CreateInvoice(Queueable):
    def handle(self):
        pass

Any logic should be inside the handle method:

class CreateInvoice(Queueable):

    def __init__(self, order_id):
        self.order_id = order_id

    def handle(self):
        # Generate invoice documents
        pass

Queueing Jobs

You can put jobs on the queue to process by simply passing them onto the queue:

from masonite.queues import Queue
from app.jobs.CreateInvoice import CreateInvoice

class InvoiceController:

  def generate(self, queue: Queue):
    # Jobs with no parameters
    queue.push(CreateInvoice())

    # Jobs with paramaters
    # Create an invoice from a payment
    queue.push(CreateInvoice(Order.find(1).id))

You can also specify any number of options using keyword arguments on the push method:

queue.push(
  CreateInvoice(Order.find(1).id)
  driver="async" # The queue driver to use
  queue="invoices" # The queue name to put the job on
)

Queue Workers

To run a queue worker, which is a terminal process than runs the jobs, you can use the queue:work command:

$ python craft queue:work

This will start up a worker using the default queue configurations. You can also modify the options:

Option
Description

--driver database

Specifies which driver to use for this worker.

--queue invoices

Specifis which queue to use for this worker.

--connection mysql

Specifies the connection to use for the worker.

--poll 5

Specifies the time in seconds to wait to fetch new jobs. Default is 1 second.

--attempts 5

Specifies the number of attempts to retry a job before considering it a failed job. Default is 3 times.

A command with modified options will look like this:

$ python craft queue:work --driver database --connection mysql --poll 5 --attempts 2

Failed Jobs

If you configurations are setup properly, when jobs fail, they will go into a failed jobs table. This is where you can monitor why your jobs are failing and choose to rerun them or remove them.

If you choose to rerun your jobs, they will be placed back onto the queue at the end and rerun with the normal job queuing process.

To rerun jobs that failed you can use the command:

$ python craft queue:retry

You can specify a few options as well:

Option
Description

--driver database

Specifies which driver to use to find the failed jobs.

--queue invoices

Specifis which queue to put the failed jobs back onto the queue with.

--connection mysql

Specifies the connection to use to fetch the failed jobs.

Commands Tests

Commands Tests

def test_my_command(self):
    self.craft("my_command", "arg1 arg2").assertSuccess()

This will programmatically run the command if it has been registered in your project and assert that no errors has been reported.

Available Assertions

The following assertions are available when testing command with craft.

assertSuccess

Assert that command exited with code 0 meaning that it ran successfully.

self.craft("my_command").assertSuccess()

assertHasErrors

Assert command output has errors.

self.craft("my_command").assertHasErrors()

assertOutputContains

Assert command output contains the given string.

self.craft("my_command").assertOutputContains(output)

assertExactOutput

Assert command output to be exactly the same as the given reference output. Be careful to add eventual line endings characters when using this assertion method.

self.craft("my_command").assertExactOutput(output)

assertOutputMissing

Assert command output does not contain the given reference output.

self.craft("my_command").assertOutputMissing(output)

assertExactErrors

Assert command output has exactly the given errors.

self.craft("my_command").assertExactErrors(errors)

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

You can test your running in console with craft test helper.

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.

custom commands
Masonite Entry
Routing
Requests
Routing
Requests
Requests
Caching
The Craft Command
The Craft Command
Creating Packages
assertSuccess
assertHasErrors
assertOutputContains
assertExactOutput
assertOutputMissing
assertExactErrors

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 1.4 brings a new config/cache.py and config/broadcast.py files. These files can be found on the page and can be copied and pasted into your project. Take a look at the new file and the file. Just copy and paste those configuration files into your project.

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

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

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

Masonite 2.2

Route Prefixes

Previously you had to append all routes with a / character. This would look something like:

Get('/some/url')

You can now optionally prefix this without a / character:

Get('some/url')

URL parameters can now optionally be retrieved from the controller definition

Previously we had to do something like:

def show(self, view: View, request: Request):
    user = User.find(request.param('user_id'))
    return view.render('some.template', {'user': user})

Now we can optionally get the parameter from the method definition:

def show(self, user_id, view: View):
    user = User.find(user_id)
    return view.render('some.template', {'user': user})

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:

from masonite.routes import Connect, Trace
ROUTES = [
    Connect('..'),
    Trace('..'),
]

JSON error responses

If the incoming request is a JSON request, Masonite will now return all errors as JSON

{
  "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"
    ]
  }
}

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:

$ craft serve -r

Now we can just specify the serve command it will default to auto-reloading:

$ craft serve

You can now specify it to NOT auto-reload by passing in 1 of these 2 commands:

$ craft serve -d
$ craft serve --dont-reload

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:

def show(self, upload: Upload):
    upload.accept('*').store(request.input('file'))

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:

<form ..>
    {{ back(request().path) }}
</form>

We can now do:

<form ..>
    {{ back() }}
</form>

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:

from masonite.auth import Auth
from masonite.request import Request

def show(self, request: Request):
    Auth(request).login(..)

Now we have it a bit cleaner and you can just resolve it and the request class will be injected for you

from masonite.auth import Auth
from masonite.request import Request

def show(self, request: Request, auth: Auth):
    auth.login(..)

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:

from masonite.auth import Auth

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

It will be resolved when you resolve it:

from masonite.auth import Auth

def show(self, auth: Auth):
    auth.login(..)

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:

from masonite.auth import Auth

def show(self, auth: Auth):
    auth.register({
        'name': 'Joe',
        'email': 'joe@email.com',
        'password': 'secret'
    })

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:

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

we can now shorten down the flashing of errors and do:

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)

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 .

Controllers documentation here
Queues documentation here
Craft commands documentation here
Uploading documentation here
Views documentation here
Testing documentation here
documentation here
Requests documentation here
validation section here
Validation documentation here
Authentication documentation here
Service Container documentation here
Requests documentation here
Routes documentation here

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

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.

What's New in 2.1
Whats New in 2.1